diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 380e45ed2..d18d7639c 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -79,6 +79,7 @@ jobs: echo SECRET_KEY=xHjFeFlo$k= >> etc/env.sh sed -i "s#/path/to#${deploy_dir}#g" etc/trac-platform.yaml + sed -i "s#jwtIssuer: trac_platform#disableAuth: true#" etc/trac-platform.yaml bin/secret-tool run --task init_secrets bin/secret-tool run --task create_root_auth_key EC 256 diff --git a/dev/config/trac-devlocal.yaml b/dev/config/trac-devlocal.yaml index ad64dc9da..c4069138f 100644 --- a/dev/config/trac-devlocal.yaml +++ b/dev/config/trac-devlocal.yaml @@ -87,61 +87,63 @@ jobCache: webServer: - # To use the TRAC web server, set the enabled flag and uncomment the configuration + # To use the TRAC web server, enable it in the services section then add a content root - enabled: false - -# contentRoot: -# protocol: LOCAL -# properties: -# rootPath: path/to/web/content/root -# -# rewriteRules: -# -# redirects: -# - source: / -# target: /index.html -# status: 302 + # contentRoot: + # protocol: LOCAL + # properties: + # rootPath: path/to/web/content/root + # + # rewriteRules: + # + # redirects: + # - source: / + # target: /index.html + # status: 302 gateway: - idleTimeout: 10 - # Routing for the TRAC services will be configured automatically # To add custom routes, uncomment this configuration and modify as required -# routes: -# -# - routeName: Local Web Server -# routeType: HTTP -# -# match: -# host: localhost -# path: /local/app -# -# target: -# scheme: http -# host: localhost -# port: 9090 -# path: / + # routes: + # + # - routeName: Local Development Server + # routeType: HTTP + # + # match: + # host: localhost + # path: /local=app/ + # + # target: + # scheme: http + # host: localhost + # port: 3000 + # path: / services: gateway: port: 8080 + properties: + network.idleTimeout: 10 - metadata: + authentication: port: 8081 - data: + metadata: port: 8082 - orchestrator: + data: port: 8083 + orchestrator: + port: 8084 + webServer: - port: 8090 + enabled: false + port: 8085 deployment: diff --git a/dist/template/etc/trac-platform.yaml b/dist/template/etc/trac-platform.yaml index f135c4e83..152735053 100644 --- a/dist/template/etc/trac-platform.yaml +++ b/dist/template/etc/trac-platform.yaml @@ -88,61 +88,63 @@ jobCache: webServer: - # To use the TRAC web server, set the enabled flag and uncomment the configuration + # To use the TRAC web server, enable it in the services section then add a content root - enabled: false - -# contentRoot: -# protocol: LOCAL -# properties: -# rootPath: path/to/web/content/root -# -# rewriteRules: -# -# redirects: -# - source: / -# target: /index.html -# status: 302 + # contentRoot: + # protocol: LOCAL + # properties: + # rootPath: path/to/web/content/root + # + # rewriteRules: + # + # redirects: + # - source: / + # target: /index.html + # status: 302 gateway: - idleTimeout: 10 - -# Routing for the TRAC services will be configured automatically -# To add custom routes, uncomment this configuration and modify as required - -# routes: -# -# - routeName: Local Web Server -# routeType: HTTP -# -# match: -# host: localhost -# path: /local/app -# -# target: -# scheme: http -# host: localhost -# port: 9090 -# path: / + # Routing for the TRAC services will be configured automatically + # To add custom routes, uncomment this configuration and modify as required + + # routes: + # + # - routeName: Local Development Server + # routeType: HTTP + # + # match: + # host: localhost + # path: /local=app/ + # + # target: + # scheme: http + # host: localhost + # port: 3000 + # path: / services: gateway: port: 8080 + properties: + network.idleTimeout: 10 - metadata: + authentication: port: 8081 - data: + metadata: port: 8082 - orchestrator: + data: port: 8083 + orchestrator: + port: 8084 + webServer: - port: 8090 + enabled: false + port: 8085 deployment: diff --git a/settings.gradle b/settings.gradle index 506ef7f08..8b2686cfb 100644 --- a/settings.gradle +++ b/settings.gradle @@ -61,12 +61,14 @@ project(":tracdap-lib-test").projectDir = file("tracdap-libs/tracdap-lib-test") // Java services +include 'tracdap-svc-auth' include 'tracdap-svc-meta' include 'tracdap-svc-data' include 'tracdap-svc-orch' include 'tracdap-gateway' include 'tracdap-webserver' +project(":tracdap-svc-auth").projectDir = file("tracdap-services/tracdap-svc-auth") project(":tracdap-svc-meta").projectDir = file("tracdap-services/tracdap-svc-meta") project(":tracdap-svc-data").projectDir = file("tracdap-services/tracdap-svc-data") project(":tracdap-svc-orch").projectDir = file("tracdap-services/tracdap-svc-orch") diff --git a/tracdap-api/packages/web/wrapper.js b/tracdap-api/packages/web/wrapper.js index 51e5d71fb..74d87738d 100644 --- a/tracdap-api/packages/web/wrapper.js +++ b/tracdap-api/packages/web/wrapper.js @@ -165,7 +165,7 @@ this.urlPrefix = options["browser"] ? "" : this.hostAddress; this.rpcMetadata = { - "trac_auth_cookies": "true" // request the auth response is sent back in cookies + "trac-auth-cookies": "true" // request the auth response is sent back in cookies } this.grpcWeb = new grpc.GrpcWebClientBase({format: 'binary'}); @@ -240,7 +240,7 @@ "accept": "application/grpc-web+proto", "x-grpc-web": 1, "x-user-agent": "trac-web-transport", // TODO: version - "trac_auth_cookies": "true" // request the auth response is sent back in cookies + "trac-auth-cookies": "true" // request the auth response is sent back in cookies } const FILTER_RESPONSE_HEADERS = ["cookie", "set-cookie", "authorization"] diff --git a/tracdap-api/tracdap-config/src/main/proto/tracdap/config/common.proto b/tracdap-api/tracdap-config/src/main/proto/tracdap/config/common.proto index cde0534cb..043e190c8 100644 --- a/tracdap-api/tracdap-config/src/main/proto/tracdap/config/common.proto +++ b/tracdap-api/tracdap-config/src/main/proto/tracdap/config/common.proto @@ -53,7 +53,11 @@ message AuthenticationConfig { sint32 jwtLimit = 6; sint32 jwtRefresh = 7; - optional PluginConfig provider = 3; + PluginConfig provider = 3; + + optional string loginPath = 12; + optional string refreshPath = 13; + optional string returnPath = 14; bool disableAuth = 4; bool disableSigning = 5; @@ -62,6 +66,8 @@ message AuthenticationConfig { string systemUserName = 9; sint32 systemTicketDuration = 10; sint32 systemTicketRefresh = 11; + + map externalSystems = 15; } @@ -82,4 +88,6 @@ message ServiceConfig { string alias = 2; uint32 port = 3; + + map properties = 4; } diff --git a/tracdap-api/tracdap-config/src/main/proto/tracdap/config/platform.proto b/tracdap-api/tracdap-config/src/main/proto/tracdap/config/platform.proto index 2c9fff471..9039d9232 100644 --- a/tracdap-api/tracdap-config/src/main/proto/tracdap/config/platform.proto +++ b/tracdap-api/tracdap-config/src/main/proto/tracdap/config/platform.proto @@ -44,9 +44,9 @@ message PlatformConfig { map tenants = 10; - optional WebServerConfig webServer = 11; + WebServerConfig webServer = 11; - optional GatewayConfig gateway = 13; + GatewayConfig gateway = 13; map services = 4; @@ -71,7 +71,9 @@ message TenantConfig { message WebServerConfig { - bool enabled = 1; + // Setting removed, use service config instead + reserved "enabled"; + reserved 1; PluginConfig contentRoot = 3; repeated WebServerRewriteRule rewriteRules = 4; @@ -93,10 +95,11 @@ message WebServerRedirect { message GatewayConfig { - uint32 idleTimeout = 1; + // Idle timeout moved into common service properties + reserved 1; + reserved "idleTimeout"; repeated RouteConfig routes = 2; - repeated WebServerRedirect redirects = 3; } diff --git a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/Http1LoginHandler.java b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/Http1LoginHandler.java new file mode 100644 index 000000000..d2ca45e41 --- /dev/null +++ b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/Http1LoginHandler.java @@ -0,0 +1,337 @@ +/* + * Licensed to the Fintech Open Source Foundation (FINOS) under one or + * more contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright ownership. + * FINOS licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.tracdap.auth.login; + +import org.finos.tracdap.common.auth.AuthHelpers; +import org.finos.tracdap.common.auth.JwtProcessor; +import org.finos.tracdap.common.auth.SessionInfo; +import org.finos.tracdap.common.exception.EUnexpected; +import org.finos.tracdap.common.http.CommonHttpResponse; +import org.finos.tracdap.common.http.Http1Headers; +import org.finos.tracdap.common.http.CommonHttpRequest; +import org.finos.tracdap.config.AuthenticationConfig; + +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.CompositeByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.*; +import io.netty.util.ReferenceCountUtil; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nonnull; +import java.net.URI; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; + + +public class Http1LoginHandler extends ChannelInboundHandlerAdapter { + + private static final int PENDING_CONTENT_LIMIT = 64 * 1024; + + private final Logger log = LoggerFactory.getLogger(getClass()); + + private final AuthenticationConfig authConfig; + private final JwtProcessor jwtProcessor; + private final ILoginProvider loginProvider; + + private final String defaultReturnPath; + + private HttpRequest pendingRequest; + private CompositeByteBuf pendingContent; + + public Http1LoginHandler( + AuthenticationConfig authConfig, + JwtProcessor jwtProcessor, + ILoginProvider loginProvider) { + + this.authConfig = authConfig; + this.jwtProcessor = jwtProcessor; + this.loginProvider = loginProvider; + + this.defaultReturnPath = authConfig.hasReturnPath() + ? authConfig.getReturnPath() + : "/"; + } + + @Override + public void channelRead(@Nonnull ChannelHandlerContext ctx, @Nonnull Object msg) { + + try { + + // Some auth mechanisms require content as well as headers + // These mechanisms set the result NEED_CONTENT, to trigger aggregation + // Aggregated messages are fed through the normal flow once they are ready + + if (!(msg instanceof HttpObject)) + throw new EUnexpected(); + + if (pendingContent != null) + msg = handleAggregateContent(msg); + + if (msg instanceof HttpRequest) { + + var request = (HttpRequest) msg; + var requestUri = URI.create(request.uri()); + + if (requestUri.getPath().equals(LoginContent.LOGIN_URL)) + processLogin(ctx, request); + + else if (requestUri.getPath().equals(LoginContent.REFRESH_URL)) + processRefresh(ctx, request); + + else if (requestUri.getPath().startsWith(LoginContent.LOGIN_PATH_PREFIX)) + serveStaticContent(ctx, request); + + else + serveNotFound(ctx, request); + } + } + finally { + ReferenceCountUtil.release(msg); + } + } + + private Object handleAggregateContent(Object msg) { + + if (!(msg instanceof HttpContent) || pendingContent.readableBytes() > PENDING_CONTENT_LIMIT) { + pendingContent.release(); + throw new EUnexpected(); + } + + var content = (HttpContent) msg; + pendingContent.addComponent(content.content()); + pendingContent.writerIndex(pendingContent.writerIndex() + content.content().writerIndex()); + + if (content instanceof LastHttpContent) { + + var fullRequest = new DefaultFullHttpRequest( + pendingRequest.protocolVersion(), + pendingRequest.method(), + pendingRequest.uri(), + pendingContent, + pendingRequest.headers(), + ((LastHttpContent) content).trailingHeaders()); + + pendingRequest = null; + pendingContent = null; + + return fullRequest; + } + + return null; + } + + private void processLogin(ChannelHandlerContext ctx, HttpRequest request) { + + // Only one auth provider available atm, for both browser and API routes + + var commonRequest = CommonHttpRequest.fromHttpRequest(request); + var authResult = loginProvider.attemptLogin(commonRequest); + + switch (authResult.getCode()) { + + case AUTHORIZED: + + // If primary auth succeeded, set up the session token + var session = SessionBuilder.newSession(authResult.getUserInfo(), authConfig); + var token = jwtProcessor.encodeToken(session); + + serveLoginOk(ctx, request, session, token); + + break; + + case FAILED: + + log.error("authentication failed ({})", authResult.getMessage()); + + // Send a basic error response for authentication failures for now + // If the result is REDIRECTED the auth provider already responded, so no need to respond again here + var failedResponse = buildFailedResponse(request, authResult); + ctx.writeAndFlush(failedResponse); + ctx.close(); + + break; + + case OTHER_RESPONSE: + + var otherResponse = buildOtherResponse(request, authResult); + ctx.writeAndFlush(otherResponse); + + break; + + case NEED_CONTENT: + + pendingRequest = request; + pendingContent = ByteBufAllocator.DEFAULT.compositeBuffer(); + + break; + + default: + throw new EUnexpected(); + } + } + + private void processRefresh(ChannelHandlerContext ctx, HttpRequest request) { + + var headers = Http1Headers.wrapHttpHeaders(request.headers()); + var token = AuthHelpers.findTracAuthToken(headers, AuthHelpers.SERVER_COOKIE); + var session = (token != null) ? jwtProcessor.decodeAndValidate(token) : null; + + if (session != null && session.isValid()) { + + var sessionUpdate = SessionBuilder.refreshSession(session, authConfig); + var tokenUpdate = jwtProcessor.encodeToken(sessionUpdate); + + serveLoginOk(ctx, request, sessionUpdate, tokenUpdate); + } + else if (AuthHelpers.isBrowserRequest(headers)) { + + var redirect = buildLoginRedirect(request); + ctx.writeAndFlush(redirect); + } + else { + + var failedResponse = buildFailedResponse(request, LoginResult.FAILED()); + ctx.writeAndFlush(failedResponse); + ctx.close(); + } + } + + private void serveLoginOk(ChannelHandlerContext ctx, HttpRequest request, SessionInfo session, String token) { + + var requestHeaders = Http1Headers.wrapHttpHeaders(request.headers()); + + CommonHttpResponse content; + Http1Headers headers; + + if (AuthHelpers.isBrowserRequest(requestHeaders)) { + + var uri = URI.create(request.uri()); + var query = uri.getQuery(); + + var queryParams = query != null + ? Arrays.asList(query.split("&")) + : List.of(); + + var returnPath = queryParams.stream() + .filter(p -> p.startsWith("return-path=")) + .findFirst() + .map(s -> s.substring(s.indexOf('=') + 1)) + .map(s -> URLDecoder.decode(s, StandardCharsets.UTF_8)) + .orElse(defaultReturnPath); + + content = LoginContent.getLoginOkPage(returnPath); + headers = Http1Headers.fromGenericHeaders(content.headers()); + + AuthHelpers.addClientAuthCookies(headers, token, session); + } + else { + + headers = new Http1Headers(); + content = new CommonHttpResponse(HttpResponseStatus.OK, headers, Unpooled.EMPTY_BUFFER); + + if (AuthHelpers.wantCookies(requestHeaders)) + AuthHelpers.addClientAuthCookies(headers, token, session); + else + AuthHelpers.addClientAuthHeaders(headers, token, session); + } + + var response = new DefaultFullHttpResponse( + request.protocolVersion(), + content.status(), + content.content(), + headers.toHttpHeaders(), + EmptyHttpHeaders.INSTANCE); + + ctx.writeAndFlush(response); + } + + private void serveStaticContent(ChannelHandlerContext ctx, HttpRequest request) { + + var content = LoginContent.getStaticContent(request); + var headers = Http1Headers.fromGenericHeaders(content.headers()); + + var response = new DefaultFullHttpResponse( + request.protocolVersion(), + content.status(), + content.content(), + headers.toHttpHeaders(), + EmptyHttpHeaders.INSTANCE); + + ctx.writeAndFlush(response); + } + + private void serveNotFound(ChannelHandlerContext ctx, HttpRequest request) { + + var response = new DefaultFullHttpResponse( + request.protocolVersion(), + HttpResponseStatus.NOT_FOUND); + + ctx.writeAndFlush(response); + } + + private FullHttpResponse buildLoginRedirect(HttpRequest request) { + + var status = LoginContent.LOGIN_REDIRECT_STATUS; + var headers = new Http1Headers(); + headers.set(HttpHeaderNames.LOCATION, LoginContent.LOGIN_URL); + + return new DefaultFullHttpResponse( + request.protocolVersion(), status, + Unpooled.EMPTY_BUFFER, + headers.toHttpHeaders(), + EmptyHttpHeaders.INSTANCE); + } + + private FullHttpResponse buildFailedResponse(HttpRequest request, LoginResult loginResult) { + + var statusMessage = loginResult.getMessage(); + var status = statusMessage != null + ? HttpResponseStatus.valueOf(HttpResponseStatus.UNAUTHORIZED.code(), statusMessage) + : HttpResponseStatus.UNAUTHORIZED; + + // Needs a real headers instance even if no headers are set + // Otherwise encoded HTTP response will not be valid + var headers = new Http1Headers(); + + return new DefaultFullHttpResponse( + request.protocolVersion(), status, + Unpooled.EMPTY_BUFFER, + headers.toHttpHeaders(), + EmptyHttpHeaders.INSTANCE); + } + + private FullHttpResponse buildOtherResponse(HttpRequest request, LoginResult loginResult) { + + var content = loginResult.getOtherResponse(); + var headers = Http1Headers.fromGenericHeaders(content.headers()); + + return new DefaultFullHttpResponse( + request.protocolVersion(), + content.status(), + content.content(), + headers.toHttpHeaders(), + EmptyHttpHeaders.INSTANCE); + } +} diff --git a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/IAuthProvider.java b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/ILoginProvider.java similarity index 71% rename from tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/IAuthProvider.java rename to tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/ILoginProvider.java index 6e383ff74..a274dbb47 100644 --- a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/IAuthProvider.java +++ b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/ILoginProvider.java @@ -15,16 +15,12 @@ * limitations under the License. */ -package org.finos.tracdap.common.auth.external; +package org.finos.tracdap.auth.login; +import org.finos.tracdap.common.http.CommonHttpRequest; -import org.finos.tracdap.common.auth.internal.UserInfo; -public interface IAuthProvider { +public interface ILoginProvider { - AuthResult attemptAuth(AuthRequest authRequest); - - boolean postAuthMatch(String method, String uri); - - AuthResponse postAuth(AuthRequest authRequest, UserInfo userInfo); + LoginResult attemptLogin(CommonHttpRequest loginRequest); } diff --git a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/LoginAuthProvider.java b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/LoginAuthProvider.java new file mode 100644 index 000000000..1d7b3e30d --- /dev/null +++ b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/LoginAuthProvider.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Fintech Open Source Foundation (FINOS) under one or + * more contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright ownership. + * FINOS licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.tracdap.auth.login; + +import org.finos.tracdap.auth.provider.IAuthProvider; +import org.finos.tracdap.common.auth.JwtProcessor; +import org.finos.tracdap.config.AuthenticationConfig; + + +import io.netty.channel.ChannelInboundHandler; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http2.Http2Headers; + +public class LoginAuthProvider implements IAuthProvider { + + private final AuthenticationConfig authConfig; + private final JwtProcessor jwtProcessor; + private final ILoginProvider loginProvider; + + public LoginAuthProvider( + AuthenticationConfig authConfig, + JwtProcessor jwtProcessor, + ILoginProvider loginProvider) { + + this.authConfig = authConfig; + this.jwtProcessor = jwtProcessor; + this.loginProvider = loginProvider; + } + + @Override + public boolean canHandleHttp1(HttpRequest request) { + return request.uri().startsWith(LoginContent.LOGIN_PATH_PREFIX); + } + + @Override + public ChannelInboundHandler createHttp1Handler() { + return new Http1LoginHandler(authConfig, jwtProcessor, loginProvider); + } + + @Override + public boolean canHandleHttp2(Http2Headers headers) { + return false; + } + + @Override + public ChannelInboundHandler createHttp2Handler() { + return null; + } +} diff --git a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/LoginContent.java b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/LoginContent.java new file mode 100644 index 000000000..8df7503ca --- /dev/null +++ b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/LoginContent.java @@ -0,0 +1,154 @@ +/* + * Licensed to the Fintech Open Source Foundation (FINOS) under one or + * more contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright ownership. + * FINOS licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.tracdap.auth.login; + +import io.netty.handler.codec.http.HttpResponseStatus; +import org.finos.tracdap.common.exception.EUnexpected; +import org.finos.tracdap.common.http.Http1Headers; +import org.finos.tracdap.common.http.CommonHttpResponse; +import org.finos.tracdap.common.util.ResourceHelpers; + +import io.netty.buffer.Unpooled; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpRequest; + +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.stream.Collectors; + + +public final class LoginContent { + + public static final String LOGIN_PATH_PREFIX = "/login/"; + public static final String LOGIN_URL = "/login/browser"; + public static final String REFRESH_URL = "/login/refresh"; + + public static final HttpResponseStatus LOGIN_REDIRECT_STATUS = HttpResponseStatus.valueOf( + HttpResponseStatus.TEMPORARY_REDIRECT.code(), "Login redirect"); + + private static final String STATIC_CONTENT_PATH = "/login/static/"; + private static final String PAGE_CONTENT_PATH = "/login/pages/"; + private static final String LOGIN_OK_PAGE = "login_ok.html"; + private static final String LOGIN_FORM_PAGE = "login_form.html"; + private static final String REDIRECT_VARIABLE = "${REDIRECT}"; + + private static final Map STATIC_CONTENT = preloadContent(STATIC_CONTENT_PATH); + private static final Map PAGE_CONTENT = stringValues(preloadContent(PAGE_CONTENT_PATH)); + + private LoginContent() {} + + private static Map preloadContent(String resourceDir) { + + var resourceNames = ResourceHelpers.getResourcesNames(resourceDir, LoginContent.class); + + if (resourceNames == null) + throw new EUnexpected(); + + var resources = new HashMap(); + + for (var resource : resourceNames) { + + var path = resourceDir + resource; + var bytes = ResourceHelpers.loadResourceAsBytes(path, LoginContent.class); + + resources.put(resource, bytes); + } + + return resources; + } + + private static Map stringValues(Map byteMap) { + + return byteMap.entrySet().stream() + .map(kv -> Map.entry(kv.getKey(), new String(kv.getValue(), StandardCharsets.UTF_8))) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + } + + public static CommonHttpResponse getStaticContent(HttpRequest request) { + + var uri = URI.create(request.uri()); + var path = uri.getPath(); + var fileKey = path.replace(LOGIN_PATH_PREFIX, "").toLowerCase(); + + var content = STATIC_CONTENT.get(fileKey); + + if (content != null) { + + var buffer = Unpooled.wrappedBuffer(content, 0, content.length); + var mimeType = mimeTypeMapping(fileKey); + var length = content.length; + var headers = new Http1Headers(); + headers.add(HttpHeaderNames.CONTENT_TYPE, mimeType); + headers.add(HttpHeaderNames.CONTENT_LENGTH, Integer.toString(length)); + + return new CommonHttpResponse(HttpResponseStatus.OK, headers, buffer); + } + else { + + var buffer = Unpooled.EMPTY_BUFFER; + var headers = new Http1Headers(); + + return new CommonHttpResponse(HttpResponseStatus.NOT_FOUND, headers, buffer); + } + } + + public static CommonHttpResponse getLoginOkPage(String returnPath) { + + var templatePage = PAGE_CONTENT.get(LOGIN_OK_PAGE); + var page = templatePage.replace(REDIRECT_VARIABLE, returnPath); + + return servePage(page); + } + + public static CommonHttpResponse getLoginFormPage() { + + var page = PAGE_CONTENT.get(LOGIN_FORM_PAGE); + + return servePage(page); + } + + private static CommonHttpResponse servePage(String page) { + + var content = page.getBytes(StandardCharsets.UTF_8); + var buffer = Unpooled.wrappedBuffer(content, 0, content.length); + var headers = new Http1Headers(); + headers.add(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=utf-8"); + headers.add(HttpHeaderNames.CONTENT_LENGTH, Integer.toString(content.length)); + + return new CommonHttpResponse(HttpResponseStatus.OK, headers, buffer); + } + + private static String mimeTypeMapping(String path) { + + var sep = path.lastIndexOf("."); + var ext = path.substring(sep + 1); + + switch (ext) { + case "html": + return "text/html"; + case "css": + return "text/css"; + case "png": + return "image/png"; + } + + return "text/html"; + } +} diff --git a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/AuthResult.java b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/LoginResult.java similarity index 55% rename from tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/AuthResult.java rename to tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/LoginResult.java index 50587393b..f87d9a23e 100644 --- a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/AuthResult.java +++ b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/LoginResult.java @@ -15,60 +15,61 @@ * limitations under the License. */ -package org.finos.tracdap.common.auth.external; +package org.finos.tracdap.auth.login; -import org.finos.tracdap.common.auth.internal.UserInfo; +import org.finos.tracdap.common.auth.UserInfo; +import org.finos.tracdap.common.http.CommonHttpResponse; -public class AuthResult { +public class LoginResult { - private final AuthResultCode code; + private final LoginResultCode code; private final UserInfo userInfo; private final String message; - private final AuthResponse otherResponse; + private final CommonHttpResponse otherResponse; - public static AuthResult AUTHORIZED(UserInfo userInfo) { - return new AuthResult(AuthResultCode.AUTHORIZED, userInfo); + public static LoginResult AUTHORIZED(UserInfo userInfo) { + return new LoginResult(LoginResultCode.AUTHORIZED, userInfo); } - public static AuthResult FAILED() { - return new AuthResult(AuthResultCode.FAILED, (String) null); + public static LoginResult FAILED() { + return new LoginResult(LoginResultCode.FAILED, (String) null); } - public static AuthResult FAILED(String message) { - return new AuthResult(AuthResultCode.FAILED, message); + public static LoginResult FAILED(String message) { + return new LoginResult(LoginResultCode.FAILED, message); } - public static AuthResult OTHER_RESPONSE(AuthResponse response) { - return new AuthResult(AuthResultCode.OTHER_RESPONSE, response); + public static LoginResult OTHER_RESPONSE(CommonHttpResponse response) { + return new LoginResult(LoginResultCode.OTHER_RESPONSE, response); } - public static AuthResult NEED_CONTENT() { - return new AuthResult(AuthResultCode.NEED_CONTENT, (UserInfo) null); + public static LoginResult NEED_CONTENT() { + return new LoginResult(LoginResultCode.NEED_CONTENT, (UserInfo) null); } - private AuthResult(AuthResultCode code, UserInfo userInfo) { + private LoginResult(LoginResultCode code, UserInfo userInfo) { this.code = code; this.userInfo = userInfo; this.message = null; this.otherResponse = null; } - private AuthResult(AuthResultCode code, String message) { + private LoginResult(LoginResultCode code, String message) { this.code = code; this.userInfo = null; this.message = message; this.otherResponse = null; } - private AuthResult(AuthResultCode code, AuthResponse otherResponse) { + private LoginResult(LoginResultCode code, CommonHttpResponse otherResponse) { this.code = code; this.userInfo = null; this.message = null; this.otherResponse = otherResponse; } - public AuthResultCode getCode() { + public LoginResultCode getCode() { return code; } @@ -80,7 +81,7 @@ public UserInfo getUserInfo() { return userInfo; } - public AuthResponse getOtherResponse() { + public CommonHttpResponse getOtherResponse() { return otherResponse; } } diff --git a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/AuthResultCode.java b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/LoginResultCode.java similarity index 91% rename from tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/AuthResultCode.java rename to tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/LoginResultCode.java index b45005be0..2460acbc9 100644 --- a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/AuthResultCode.java +++ b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/LoginResultCode.java @@ -15,10 +15,10 @@ * limitations under the License. */ -package org.finos.tracdap.common.auth.external; +package org.finos.tracdap.auth.login; -public enum AuthResultCode { +public enum LoginResultCode { AUTHORIZED, FAILED, OTHER_RESPONSE, diff --git a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/SessionBuilder.java b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/SessionBuilder.java new file mode 100644 index 000000000..81a9c6118 --- /dev/null +++ b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/SessionBuilder.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Fintech Open Source Foundation (FINOS) under one or + * more contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright ownership. + * FINOS licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.tracdap.auth.login; + +import org.finos.tracdap.common.auth.SessionInfo; +import org.finos.tracdap.common.auth.UserInfo; +import org.finos.tracdap.common.config.ConfigDefaults; +import org.finos.tracdap.config.AuthenticationConfig; + +import java.time.Instant; + + +public class SessionBuilder { + + public static SessionInfo newSession(UserInfo userInfo, AuthenticationConfig authConfig) { + + var configExpiry = ConfigDefaults.readOrDefault(authConfig.getJwtExpiry(), ConfigDefaults.DEFAULT_JWT_EXPIRY); + var configLimit = ConfigDefaults.readOrDefault(authConfig.getJwtLimit(), ConfigDefaults.DEFAULT_JWT_LIMIT); + + var issue = Instant.now(); + var expiry = issue.plusSeconds(configExpiry); + var limit = issue.plusSeconds(configLimit); + + var session = new SessionInfo(); + session.setUserInfo(userInfo); + session.setIssueTime(issue); + session.setExpiryTime(expiry); + session.setExpiryLimit(limit); + session.setValid(true); + + return session; + } + + public static SessionInfo refreshSession(SessionInfo session, AuthenticationConfig authConfig) { + + var latestIssue = session.getIssueTime(); + var originalLimit = session.getExpiryLimit(); + + var configRefresh = ConfigDefaults.readOrDefault(authConfig.getJwtRefresh(), ConfigDefaults.DEFAULT_JWT_REFRESH); + var configExpiry = ConfigDefaults.readOrDefault(authConfig.getJwtExpiry(), ConfigDefaults.DEFAULT_JWT_EXPIRY); + + // If the refresh time hasn't elapsed yet, return the original session without modification + if (latestIssue.plusSeconds(configRefresh).isAfter(Instant.now())) + return session; + + var newIssue = Instant.now(); + var newExpiry = newIssue.plusSeconds(configExpiry); + var limitedExpiry = newExpiry.isBefore(originalLimit) ? newExpiry : originalLimit; + + var newSession = new SessionInfo(); + newSession.setUserInfo(session.getUserInfo()); + newSession.setIssueTime(newIssue); + newSession.setExpiryTime(limitedExpiry); + newSession.setExpiryLimit(originalLimit); + + // Session remains valid until time ticks past the original limit time, i.e. issue < limit + newSession.setValid(newIssue.isBefore(originalLimit)); + + return newSession; + } +} diff --git a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/common/BasicAuthProvider.java b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/simple/BasicLoginProvider.java similarity index 69% rename from tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/common/BasicAuthProvider.java rename to tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/simple/BasicLoginProvider.java index 12c0f5c67..e45d8474c 100644 --- a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/common/BasicAuthProvider.java +++ b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/simple/BasicLoginProvider.java @@ -15,11 +15,13 @@ * limitations under the License. */ -package org.finos.tracdap.common.auth.external.common; +package org.finos.tracdap.auth.login.simple; -import org.finos.tracdap.common.auth.external.*; -import org.finos.tracdap.common.auth.internal.UserInfo; +import org.finos.tracdap.auth.login.*; import org.finos.tracdap.common.config.ConfigManager; +import org.finos.tracdap.common.http.Http1Headers; +import org.finos.tracdap.common.http.CommonHttpRequest; +import org.finos.tracdap.common.http.CommonHttpResponse; import io.netty.buffer.Unpooled; import io.netty.handler.codec.http.*; @@ -31,23 +33,29 @@ import java.util.Base64; -public class BasicAuthProvider implements IAuthProvider { +class BasicLoginProvider implements ILoginProvider { - private static final Logger log = LoggerFactory.getLogger(BasicAuthProvider.class); + private static final Logger log = LoggerFactory.getLogger(BasicLoginProvider.class); + private static final String BASIC_AUTH_HEADER = "Basic realm=\"%s\", charset=\"%s\""; private static final String BASIC_AUTH_PREFIX = "basic "; + private static final String BASIC_AUTH_REALM = "trac-auth-realm"; + private static final String BASIC_AUTH_CHARSET = "UTF-8"; + + private final String authenticateHeader; private final IUserDatabase userDb; - public BasicAuthProvider(ConfigManager configManager) { + public BasicLoginProvider(ConfigManager configManager) { - this.userDb = CommonAuthPlugin.createUserDb(configManager); + this.authenticateHeader = String.format(BASIC_AUTH_HEADER, BASIC_AUTH_REALM, BASIC_AUTH_CHARSET); + this.userDb = SimpleLoginPlugin.createUserDb(configManager); } @Override - public AuthResult attemptAuth(AuthRequest authRequest) { + public LoginResult attemptLogin(CommonHttpRequest authRequest) { - var headers = authRequest.getHeaders(); + var headers = authRequest.headers(); if (!headers.contains(HttpHeaderNames.AUTHORIZATION)) { log.info("No authorization provided, new authorization required"); @@ -80,37 +88,24 @@ public AuthResult attemptAuth(AuthRequest authRequest) { if (LocalUsers.checkPassword(userDb, username, password, log)) { var userInfo = LocalUsers.getUserInfo(userDb, username); - return AuthResult.AUTHORIZED(userInfo); + return LoginResult.AUTHORIZED(userInfo); } else { return requestAuth(); } } - @Override - public boolean postAuthMatch(String method, String uri) { - return false; - } - - @Override - public AuthResponse postAuth(AuthRequest authRequest, UserInfo userInfo) { - return null; - } - - public AuthResult requestAuth() { + public LoginResult requestAuth() { log.info("AUTHENTICATION: Using basic authentication"); - var headers = new Http1AuthHeaders(); - headers.add(HttpHeaderNames.WWW_AUTHENTICATE, "Basic realm=\"trac_auth_realm\", charset=\"UTF-8\""); + var headers = new Http1Headers(); + headers.add(HttpHeaderNames.WWW_AUTHENTICATE, authenticateHeader); - var response = new AuthResponse( - HttpResponseStatus.UNAUTHORIZED.code(), - HttpResponseStatus.UNAUTHORIZED.reasonPhrase(), + var response = new CommonHttpResponse( + HttpResponseStatus.UNAUTHORIZED, headers, Unpooled.EMPTY_BUFFER); - return AuthResult.OTHER_RESPONSE(response); + return LoginResult.OTHER_RESPONSE(response); } - - } diff --git a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/simple/BuiltInLoginProvider.java b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/simple/BuiltInLoginProvider.java new file mode 100644 index 000000000..e1cdab990 --- /dev/null +++ b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/simple/BuiltInLoginProvider.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Fintech Open Source Foundation (FINOS) under one or + * more contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright ownership. + * FINOS licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.finos.tracdap.auth.login.simple; + +import org.finos.tracdap.auth.login.*; +import org.finos.tracdap.common.auth.AuthHelpers; +import org.finos.tracdap.common.config.ConfigManager; +import org.finos.tracdap.common.http.CommonHttpRequest; +import org.finos.tracdap.common.http.Http1Headers; + +import io.netty.handler.codec.http.*; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.charset.StandardCharsets; + + +class BuiltInLoginProvider implements ILoginProvider { + + private static final Logger log = LoggerFactory.getLogger(BuiltInLoginProvider.class); + + private final IUserDatabase userDb; + + public BuiltInLoginProvider(ConfigManager configManager) { + + this.userDb = SimpleLoginPlugin.createUserDb(configManager); + } + + @Override + public LoginResult attemptLogin(CommonHttpRequest request) { + + var headers = Http1Headers.fromGenericHeaders(request.headers()); + + // Only browser-based auth is supported with the built-in login provider + if (!AuthHelpers.isBrowserRequest(headers)) + return LoginResult.FAILED("Session expired or not available"); + + // Wait for content if it is not already available + if (request.content() == null) + return LoginResult.NEED_CONTENT(); + + return checkLoginRequest(request); + } + + private LoginResult checkLoginRequest(CommonHttpRequest request) { + + var content = request.content().toString(StandardCharsets.US_ASCII); + var decoder = new QueryStringDecoder(LoginContent.LOGIN_URL + "?" + content); + var loginParams = decoder.parameters(); + + var usernameParam = loginParams.get("username"); + var passwordParam = loginParams.get("password"); + + if (usernameParam == null || usernameParam.size() != 1 || + passwordParam == null || passwordParam.size() != 1) { + + var loginFormPage = LoginContent.getLoginFormPage(); + return LoginResult.OTHER_RESPONSE(loginFormPage); + } + + var username = usernameParam.get(0); + var password = passwordParam.get(0); + + if (LocalUsers.checkPassword(userDb, username, password, log)) { + + var userInfo = LocalUsers.getUserInfo(userDb, username); + return LoginResult.AUTHORIZED(userInfo); + } + else { + + var loginFormPage = LoginContent.getLoginFormPage(); + return LoginResult.OTHER_RESPONSE(loginFormPage); + } + } +} diff --git a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/common/GuestAuthProvider.java b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/simple/GuestLoginProvider.java similarity index 74% rename from tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/common/GuestAuthProvider.java rename to tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/simple/GuestLoginProvider.java index 72d8843d4..3c3dd01dd 100644 --- a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/common/GuestAuthProvider.java +++ b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/simple/GuestLoginProvider.java @@ -15,22 +15,22 @@ * limitations under the License. */ -package org.finos.tracdap.common.auth.external.common; +package org.finos.tracdap.auth.login.simple; -import org.finos.tracdap.common.auth.external.*; -import org.finos.tracdap.common.auth.internal.UserInfo; +import org.finos.tracdap.auth.login.*; +import org.finos.tracdap.common.auth.UserInfo; import org.finos.tracdap.common.exception.EStartup; +import org.finos.tracdap.common.http.CommonHttpRequest; -import io.netty.channel.ChannelHandlerContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Properties; -public class GuestAuthProvider implements IAuthProvider { +class GuestLoginProvider implements ILoginProvider { - private static final Logger log = LoggerFactory.getLogger(GuestAuthProvider.class); + private static final Logger log = LoggerFactory.getLogger(GuestLoginProvider.class); public static final String USER_ID_CONFIG_KEY = "userId"; public static final String USER_NAME_CONFIG_KEY = "userName"; @@ -38,7 +38,7 @@ public class GuestAuthProvider implements IAuthProvider { private final String guestId; private final String guestName; - GuestAuthProvider(Properties properties) { + GuestLoginProvider(Properties properties) { if (!properties.containsKey(USER_ID_CONFIG_KEY) || !properties.containsKey(USER_NAME_CONFIG_KEY)) { @@ -53,7 +53,7 @@ public class GuestAuthProvider implements IAuthProvider { } @Override - public AuthResult attemptAuth(AuthRequest authRequest) { + public LoginResult attemptLogin(CommonHttpRequest authRequest) { log.info("AUTHENTICATION: Using guest authentication [{}]", guestId); @@ -61,16 +61,6 @@ public AuthResult attemptAuth(AuthRequest authRequest) { user.setUserId(guestId); user.setDisplayName(guestName); - return AuthResult.AUTHORIZED(user); - } - - @Override - public boolean postAuthMatch(String method, String uri) { - return false; - } - - @Override - public AuthResponse postAuth(AuthRequest authRequest, UserInfo userInfo) { - return null; + return LoginResult.AUTHORIZED(user); } } diff --git a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/IUserDatabase.java b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/simple/IUserDatabase.java similarity index 91% rename from tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/IUserDatabase.java rename to tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/simple/IUserDatabase.java index 6e3e024b1..623b11b3d 100644 --- a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/IUserDatabase.java +++ b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/simple/IUserDatabase.java @@ -15,10 +15,10 @@ * limitations under the License. */ -package org.finos.tracdap.common.auth.external; +package org.finos.tracdap.auth.login.simple; -public interface IUserDatabase { +interface IUserDatabase { UserDbRecord getUserDbRecord(String userId); } diff --git a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/common/JksUserDb.java b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/simple/JksUserDb.java similarity index 89% rename from tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/common/JksUserDb.java rename to tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/simple/JksUserDb.java index aea904492..8d02e0c4e 100644 --- a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/common/JksUserDb.java +++ b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/simple/JksUserDb.java @@ -15,17 +15,15 @@ * limitations under the License. */ -package org.finos.tracdap.common.auth.external.common; +package org.finos.tracdap.auth.login.simple; -import org.finos.tracdap.common.auth.external.IUserDatabase; -import org.finos.tracdap.common.auth.external.UserDbRecord; import org.finos.tracdap.common.config.ConfigManager; import org.finos.tracdap.common.config.ISecretLoader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class JksUserDb implements IUserDatabase { +class JksUserDb implements IUserDatabase { private static final String DISPLAY_NAME_ATTR = "displayName"; diff --git a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/common/LocalUsers.java b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/simple/LocalUsers.java similarity index 90% rename from tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/common/LocalUsers.java rename to tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/simple/LocalUsers.java index b2388faac..ccb483d74 100644 --- a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/common/LocalUsers.java +++ b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/simple/LocalUsers.java @@ -15,15 +15,14 @@ * limitations under the License. */ -package org.finos.tracdap.common.auth.external.common; +package org.finos.tracdap.auth.login.simple; -import org.finos.tracdap.common.auth.external.IUserDatabase; -import org.finos.tracdap.common.auth.internal.UserInfo; +import org.finos.tracdap.common.auth.UserInfo; import org.finos.tracdap.common.config.CryptoHelpers; import org.finos.tracdap.common.exception.EAuthorization; import org.slf4j.Logger; -public class LocalUsers { +class LocalUsers { static boolean checkPassword(IUserDatabase userDb, String user, String pass, Logger log) { diff --git a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/common/CommonAuthPlugin.java b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/simple/SimpleLoginPlugin.java similarity index 55% rename from tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/common/CommonAuthPlugin.java rename to tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/simple/SimpleLoginPlugin.java index a89022d35..4d0c85e59 100644 --- a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/common/CommonAuthPlugin.java +++ b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/simple/SimpleLoginPlugin.java @@ -15,10 +15,9 @@ * limitations under the License. */ -package org.finos.tracdap.common.auth.external.common; +package org.finos.tracdap.auth.login.simple; -import org.finos.tracdap.common.auth.external.IUserDatabase; -import org.finos.tracdap.common.auth.external.IAuthProvider; +import org.finos.tracdap.auth.login.ILoginProvider; import org.finos.tracdap.common.config.ConfigManager; import org.finos.tracdap.common.exception.EPluginNotAvailable; import org.finos.tracdap.common.exception.EStartup; @@ -30,21 +29,17 @@ import java.util.Properties; -public class CommonAuthPlugin extends TracPlugin { +public class SimpleLoginPlugin extends TracPlugin { - private static final String PLUGIN_NAME = "COMMON_AUTH"; - private static final String GUEST_AUTH_PROVIDER = "GUEST_AUTH_PROVIDER"; - private static final String BASIC_AUTH_PROVIDER = "BASIC_AUTH_PROVIDER"; - private static final String BUILT_IN_AUTH_PROVIDER = "BUILT_IN_AUTH_PROVIDER"; - private static final String JKS_USER_DATABASE = "JKS_USER_DATABASE"; - private static final String SQL_USER_DATABASE = "SQL_USER_DATABASE"; + private static final String PLUGIN_NAME = "SIMPLE_LOGIN"; + private static final String GUEST_LOGIN_PROVIDER = "GUEST_LOGIN_PROVIDER"; + private static final String BASIC_LOGIN_PROVIDER = "BASIC_LOGIN_PROVIDER"; + private static final String BUILT_IN_LOGIN_PROVIDER = "BUILT_IN_LOGIN_PROVIDER"; private static final List serviceInfo = List.of( - new PluginServiceInfo(IAuthProvider.class, GUEST_AUTH_PROVIDER, List.of("guest")), - new PluginServiceInfo(IAuthProvider.class, BASIC_AUTH_PROVIDER, List.of("basic")), - new PluginServiceInfo(IAuthProvider.class, BUILT_IN_AUTH_PROVIDER, List.of("builtin")), - new PluginServiceInfo(IUserDatabase.class, JKS_USER_DATABASE, List.of("JKS", "PKCS12")), - new PluginServiceInfo(IUserDatabase.class, SQL_USER_DATABASE, List.of("H2"))); + new PluginServiceInfo(ILoginProvider.class, GUEST_LOGIN_PROVIDER, List.of("guest")), + new PluginServiceInfo(ILoginProvider.class, BASIC_LOGIN_PROVIDER, List.of("basic")), + new PluginServiceInfo(ILoginProvider.class, BUILT_IN_LOGIN_PROVIDER, List.of("builtin"))); @Override public String pluginName() { @@ -59,23 +54,27 @@ public List serviceInfo() { @Override @SuppressWarnings("unchecked") protected T createService(String serviceName, Properties properties, ConfigManager configManager) { - if (serviceName.equals(GUEST_AUTH_PROVIDER)) - return (T) new GuestAuthProvider(properties); + switch (serviceName) { - if (serviceName.equals(BASIC_AUTH_PROVIDER)) - return (T) new BasicAuthProvider(configManager); + case GUEST_LOGIN_PROVIDER: + return (T) new GuestLoginProvider(properties); - if (serviceName.equals(BUILT_IN_AUTH_PROVIDER)) - return (T) new BuiltInAuthProvider(properties, configManager); + case BASIC_LOGIN_PROVIDER: + return (T) new BasicLoginProvider(configManager); - var message = String.format("Plugin [%s] does not support the service [%s]", pluginName(), serviceName); - throw new EPluginNotAvailable(message); + case BUILT_IN_LOGIN_PROVIDER: + return (T) new BuiltInLoginProvider(configManager); + + default: + var message = String.format("Plugin [%s] does not support the service [%s]", pluginName(), serviceName); + throw new EPluginNotAvailable(message); + } } static IUserDatabase createUserDb(ConfigManager configManager) { - // TODO: Use plugin manager to get the user DB plugin - // Requires passing plugin manager into createService for non-config plugins + // IUserDatabase is an implementation detail of the simple login provider + // Not intended for extension / re-use in its current form var config = configManager.loadRootConfigObject(PlatformConfig.class); var secretType = config.getConfigOrDefault("users.type", "PKCS12"); diff --git a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/common/SqlUserDb.java b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/simple/SqlUserDb.java similarity index 93% rename from tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/common/SqlUserDb.java rename to tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/simple/SqlUserDb.java index 2aa4e7ecc..0e550590d 100644 --- a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/common/SqlUserDb.java +++ b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/simple/SqlUserDb.java @@ -15,10 +15,8 @@ * limitations under the License. */ -package org.finos.tracdap.common.auth.external.common; +package org.finos.tracdap.auth.login.simple; -import org.finos.tracdap.common.auth.external.IUserDatabase; -import org.finos.tracdap.common.auth.external.UserDbRecord; import org.finos.tracdap.common.config.ConfigManager; import org.finos.tracdap.common.db.JdbcSetup; import org.finos.tracdap.common.exception.EAuthorization; @@ -31,7 +29,7 @@ import java.util.Properties; -public class SqlUserDb implements IUserDatabase { +class SqlUserDb implements IUserDatabase { public static SqlUserDb getUserDb(ConfigManager configManager, String dialect, String usersUrl) { diff --git a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/UserDbRecord.java b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/simple/UserDbRecord.java similarity index 94% rename from tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/UserDbRecord.java rename to tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/simple/UserDbRecord.java index 7af9681b1..2ebe6cc80 100644 --- a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/UserDbRecord.java +++ b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/login/simple/UserDbRecord.java @@ -15,10 +15,10 @@ * limitations under the License. */ -package org.finos.tracdap.common.auth.external; +package org.finos.tracdap.auth.login.simple; -public class UserDbRecord { +class UserDbRecord { private final String userId; private final String userName; diff --git a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/IAuthHeaders.java b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/provider/IAuthProvider.java similarity index 66% rename from tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/IAuthHeaders.java rename to tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/provider/IAuthProvider.java index 8dd01df29..7fc0a26dc 100644 --- a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/IAuthHeaders.java +++ b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/auth/provider/IAuthProvider.java @@ -15,19 +15,18 @@ * limitations under the License. */ -package org.finos.tracdap.common.auth.external; +package org.finos.tracdap.auth.provider; -import java.util.List; -import java.util.Map; +import io.netty.channel.ChannelInboundHandler; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http2.Http2Headers; -public interface IAuthHeaders extends Iterable> { +public interface IAuthProvider { - void add(CharSequence name, CharSequence value); + boolean canHandleHttp1(HttpRequest request); + ChannelInboundHandler createHttp1Handler(); - boolean contains(CharSequence name); - - CharSequence get(CharSequence name); - - List getAll(CharSequence name); -} \ No newline at end of file + boolean canHandleHttp2(Http2Headers headers); + ChannelInboundHandler createHttp2Handler(); +} diff --git a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/AuthLogic.java b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/AuthLogic.java deleted file mode 100644 index f794dc358..000000000 --- a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/AuthLogic.java +++ /dev/null @@ -1,340 +0,0 @@ -/* - * Licensed to the Fintech Open Source Foundation (FINOS) under one or - * more contributor license agreements. See the NOTICE file distributed - * with this work for additional information regarding copyright ownership. - * FINOS licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with the - * License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.finos.tracdap.common.auth.external; - -import io.netty.handler.codec.http.cookie.*; -import org.finos.tracdap.common.auth.internal.AuthConstants; -import org.finos.tracdap.common.auth.internal.SessionInfo; -import org.finos.tracdap.common.auth.internal.UserInfo; -import org.finos.tracdap.common.config.ConfigDefaults; -import org.finos.tracdap.config.AuthenticationConfig; - -import io.netty.handler.codec.http.HttpHeaderNames; - -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.time.Instant; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.List; - - -public class AuthLogic { - - public static final String TRAC_AUTH_TOKEN_HEADER = AuthConstants.TRAC_AUTH_TOKEN; - public static final String TRAC_AUTH_EXPIRY_HEADER = "trac_auth_expiry"; - public static final String TRAC_AUTH_COOKIES_HEADER = "trac_auth_cookies"; - public static final String TRAC_USER_ID_HEADER = "trac_user_id"; - public static final String TRAC_USER_NAME_HEADER = "trac_user_name"; - - public static final String TRAC_AUTH_PREFIX = "trac_auth_"; - public static final String TRAC_USER_PREFIX = "trac_user_"; - - public static final boolean CLIENT_COOKIE = true; - public static final boolean SERVER_COOKIE = false; - - private static final List RESTRICTED_HEADERS = List.of( - HttpHeaderNames.AUTHORIZATION.toString(), - HttpHeaderNames.COOKIE.toString(), - HttpHeaderNames.SET_COOKIE.toString()); - - private static final String BEARER_PREFIX = "bearer "; - - private static final String NULL_AUTH_TOKEN = null; - - - // Logic class, do not allow creating instances - private AuthLogic() {} - - public static String findTracAuthToken(IAuthHeaders headers, boolean cookieDirection) { - - var rawToken = findRawAuthToken(headers, cookieDirection); - - if (rawToken == null) - return null; - - // Remove the "bearer" prefix if the auth token header is stored that way - - if (rawToken.toLowerCase().startsWith(BEARER_PREFIX)) - return rawToken.substring(BEARER_PREFIX.length()); - else - return rawToken; - } - - private static String findRawAuthToken(IAuthHeaders headers, boolean cookieDirection) { - - var cookies = extractCookies(headers, cookieDirection); - - var tracAuthHeader = findHeader(headers, TRAC_AUTH_TOKEN_HEADER); - if (tracAuthHeader != null) return tracAuthHeader; - - var tracAuthCookie = findCookie(cookies, TRAC_AUTH_TOKEN_HEADER); - if (tracAuthCookie != null) return tracAuthCookie; - - var authorizationHeader = findHeader(headers, HttpHeaderNames.AUTHORIZATION.toString()); - if (authorizationHeader != null) return authorizationHeader; - - var authorizationCookie = findCookie(cookies, HttpHeaderNames.AUTHORIZATION.toString()); - if (authorizationCookie != null) return authorizationCookie; - - // Run out of places to look! - - return NULL_AUTH_TOKEN; - } - - private static String findHeader(IAuthHeaders headers, String headerName) { - - if (headers.contains(headerName)) - return headers.get(headerName).toString(); - - return null; - } - - private static String findCookie(List cookies, String cookieName) { - - for (var cookie : cookies) { - if (cookie.name().equals(cookieName)) { - return cookie.value(); - } - } - - return null; - } - - private static List extractCookies(IAuthHeaders headers, boolean cookieDirection) { - - var cookieHeader = cookieDirection == CLIENT_COOKIE ? HttpHeaderNames.SET_COOKIE : HttpHeaderNames.COOKIE; - - var cookieHeaders = headers.getAll(cookieHeader); - var cookies = new ArrayList(); - - for (var header : cookieHeaders) - cookies.addAll(ServerCookieDecoder.LAX.decodeAll(header.toString())); - - return cookies; - } - - public static SessionInfo newSession(UserInfo userInfo, AuthenticationConfig authConfig) { - - var configExpiry = ConfigDefaults.readOrDefault(authConfig.getJwtExpiry(), ConfigDefaults.DEFAULT_JWT_EXPIRY); - var configLimit = ConfigDefaults.readOrDefault(authConfig.getJwtLimit(), ConfigDefaults.DEFAULT_JWT_LIMIT); - - var issue = Instant.now(); - var expiry = issue.plusSeconds(configExpiry); - var limit = issue.plusSeconds(configLimit); - - var session = new SessionInfo(); - session.setUserInfo(userInfo); - session.setIssueTime(issue); - session.setExpiryTime(expiry); - session.setExpiryLimit(limit); - session.setValid(true); - - return session; - } - - public static SessionInfo refreshSession(SessionInfo session, AuthenticationConfig authConfig) { - - var latestIssue = session.getIssueTime(); - var originalLimit = session.getExpiryLimit(); - - var configRefresh = ConfigDefaults.readOrDefault(authConfig.getJwtRefresh(), ConfigDefaults.DEFAULT_JWT_REFRESH); - var configExpiry = ConfigDefaults.readOrDefault(authConfig.getJwtExpiry(), ConfigDefaults.DEFAULT_JWT_EXPIRY); - - // If the refresh time hasn't elapsed yet, return the original session without modification - if (latestIssue.plusSeconds(configRefresh).isAfter(Instant.now())) - return session; - - var newIssue = Instant.now(); - var newExpiry = newIssue.plusSeconds(configExpiry); - var limitedExpiry = newExpiry.isBefore(originalLimit) ? newExpiry : originalLimit; - - var newSession = new SessionInfo(); - newSession.setUserInfo(session.getUserInfo()); - newSession.setIssueTime(newIssue); - newSession.setExpiryTime(limitedExpiry); - newSession.setExpiryLimit(originalLimit); - - // Session remains valid until time ticks past the original limit time, i.e. issue < limit - newSession.setValid(newIssue.isBefore(originalLimit)); - - return newSession; - } - - public static - THeaders setPlatformAuthHeaders(THeaders headers, THeaders emptyHeaders, String token) { - - var filtered = removeAuthHeaders(headers, emptyHeaders, SERVER_COOKIE); - - return addPlatformAuthHeaders(filtered, token); - } - - public static - THeaders setClientAuthHeaders( - THeaders headers, THeaders emptyHeaders, - String token, SessionInfo session, boolean wantCookies) { - - var filtered = removeAuthHeaders(headers, emptyHeaders, CLIENT_COOKIE); - - if (wantCookies) - return addClientAuthCookies(filtered, token, session); - else - return addClientAuthHeaders(filtered, token, session); - } - - private static - THeaders removeAuthHeaders(THeaders headers, THeaders emptyHeaders, boolean cookieDirection) { - - var cookies = extractCookies(headers, cookieDirection); - - var filteredHeaders = filterHeaders(headers, emptyHeaders); - var filteredCookies = filterCookies(cookies); - - var cookieHeader = cookieDirection == CLIENT_COOKIE ? HttpHeaderNames.SET_COOKIE : HttpHeaderNames.COOKIE; - - for (var cookie : filteredCookies) { - filteredHeaders.add(cookieHeader, ServerCookieEncoder.STRICT.encode(cookie)); - } - - return filteredHeaders; - } - - private static - THeaders filterHeaders(THeaders headers, THeaders newHeaders) { - - for (var header : headers) { - - var headerName = header.getKey().toString().toLowerCase(); - - if (headerName.startsWith(TRAC_AUTH_PREFIX) || headerName.startsWith(TRAC_USER_PREFIX)) - continue; - - if (RESTRICTED_HEADERS.contains(headerName)) - continue; - - newHeaders.add(header.getKey(), header.getValue()); - } - - return newHeaders; - } - - private static List filterCookies(List cookies) { - - var filtered = new ArrayList(); - - for (var cookie : cookies) { - - var cookieName = cookie.name().toLowerCase(); - - if (cookieName.startsWith(TRAC_AUTH_PREFIX) || cookieName.startsWith(TRAC_USER_PREFIX)) - continue; - - if (RESTRICTED_HEADERS.contains(cookieName)) - continue; - - filtered.add(cookie); - } - - return filtered; - } - - private static - THeaders addPlatformAuthHeaders(THeaders headers, String token) { - - // The platform only cares about the token, that is the definitive source of session info - - headers.add(TRAC_AUTH_TOKEN_HEADER, token); - - return headers; - } - - private static - THeaders addClientAuthHeaders(THeaders headers, String token, SessionInfo session) { - - // For API calls send session info back in headers, these come through as gRPC metadata - // The web API package will use JavaScript to store these as cookies (cookies get lost over grpc-web) - // They are also easier to work with in non-browser contexts - - // We use URL encoding to avoid issues with non-ascii characters - // This also matches the way auth headers are sent back to browsers in cookies - - var expiry = DateTimeFormatter.ISO_INSTANT.format(session.getExpiryTime()); - var userId = URLEncoder.encode(session.getUserInfo().getUserId(), StandardCharsets.US_ASCII); - var userName = URLEncoder.encode(session.getUserInfo().getDisplayName(), StandardCharsets.US_ASCII); - - headers.add(TRAC_AUTH_TOKEN_HEADER, token); - headers.add(TRAC_AUTH_EXPIRY_HEADER, expiry); - headers.add(TRAC_USER_ID_HEADER, userId); - headers.add(TRAC_USER_NAME_HEADER, userName); - - return headers; - } - - private static - THeaders addClientAuthCookies(THeaders headers, String token, SessionInfo session) { - - // For browser requests, send the session info back as cookies, this is by far the easiest approach - // The web API package will look for a valid auth token cookie and send it as a header if available - - // We use URL encoding to avoid issues with non-ascii characters - // Cookies are a lot stricter than regular headers so this is required - // The other option is base 64, but URL encoding is more readable for humans - - var expiry = DateTimeFormatter.ISO_INSTANT.format(session.getExpiryTime()); - var userId = URLEncoder.encode(session.getUserInfo().getUserId(), StandardCharsets.US_ASCII); - var userName = URLEncoder.encode(session.getUserInfo().getDisplayName(), StandardCharsets.US_ASCII); - - setClientCookie(headers, TRAC_AUTH_TOKEN_HEADER, token, session.getExpiryTime(), true); - setClientCookie(headers, TRAC_AUTH_EXPIRY_HEADER, expiry, session.getExpiryTime(), false); - setClientCookie(headers, TRAC_USER_ID_HEADER, userId, session.getExpiryTime(), false); - setClientCookie(headers, TRAC_USER_NAME_HEADER, userName, session.getExpiryTime(), false); - - return headers; - } - - private static void setClientCookie( - IAuthHeaders headers, CharSequence cookieName, String cookieValue, - Instant expiry, boolean isAuthToken) { - - var cookie = new DefaultCookie(cookieName.toString(), cookieValue); - - // TODO: Can we know the value to set for domain? - - // Do not allow sending TRAC tokens to other end points - cookie.setSameSite(CookieHeaderNames.SameSite.Strict); - - // Make sure cookies are sent to the API endpoints, even if the UI is served from a sub path - cookie.setPath("/"); - - // TRAC session cookies will expire when the session expires - if (expiry != null) { - var secondsToExpiry = Duration.between(Instant.now(), expiry).getSeconds(); - var maxAge = Math.max(secondsToExpiry, 0); - cookie.setMaxAge(maxAge); - } - // Otherwise let the cookie live for the lifetime of the browser session - else - cookie.setMaxAge(Cookie.UNDEFINED_MAX_AGE); // remove cookie on browser close - - // Restrict to HTTP access for the auth token, allow JavaScript access for everything else - cookie.setHttpOnly(isAuthToken); - - headers.add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(cookie)); - } -} diff --git a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/AuthRequest.java b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/AuthRequest.java deleted file mode 100644 index 13600c669..000000000 --- a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/AuthRequest.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Licensed to the Fintech Open Source Foundation (FINOS) under one or - * more contributor license agreements. See the NOTICE file distributed - * with this work for additional information regarding copyright ownership. - * FINOS licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with the - * License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.finos.tracdap.common.auth.external; - - -import io.netty.handler.codec.http.FullHttpRequest; -import io.netty.handler.codec.http.HttpRequest; - -public class AuthRequest { - - private final String method; - private final String url; - private final IAuthHeaders headers; - private final byte[] content; - - public static AuthRequest forHttp1Request(HttpRequest request, IAuthHeaders headers) { - - byte[] content = null; - - if (request instanceof FullHttpRequest) { - var fullRequest = (FullHttpRequest) request; - content = new byte[fullRequest.content().readableBytes()]; - fullRequest.content().readBytes(content); - } - - return new AuthRequest(request.method().toString(), request.uri(), headers, content); - } - - public AuthRequest(String method, String url, IAuthHeaders headers, byte[] content) { - this.method = method; - this.url = url; - this.headers = headers; - this.content = content; - } - - public String getMethod() { - return method; - } - - public String getUrl() { - return url; - } - - public IAuthHeaders getHeaders() { - return headers; - } - - public byte[] getContent() { - return content; - } -} diff --git a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/Http1AuthHandler.java b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/Http1AuthHandler.java deleted file mode 100644 index 6226eaf9f..000000000 --- a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/Http1AuthHandler.java +++ /dev/null @@ -1,372 +0,0 @@ -/* - * Licensed to the Fintech Open Source Foundation (FINOS) under one or - * more contributor license agreements. See the NOTICE file distributed - * with this work for additional information regarding copyright ownership. - * FINOS licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with the - * License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.finos.tracdap.common.auth.external; - -import io.netty.buffer.ByteBufAllocator; -import io.netty.buffer.CompositeByteBuf; -import io.netty.buffer.Unpooled; -import org.finos.tracdap.common.auth.internal.JwtProcessor; -import org.finos.tracdap.common.auth.internal.SessionInfo; -import org.finos.tracdap.common.exception.EUnexpected; -import org.finos.tracdap.config.AuthenticationConfig; - -import io.netty.channel.ChannelDuplexHandler; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelPromise; -import io.netty.handler.codec.http.*; -import io.netty.util.ReferenceCountUtil; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.annotation.Nonnull; - - -public class Http1AuthHandler extends ChannelDuplexHandler { - - private static final int PENDING_CONTENT_LIMIT = 64 * 1024; - - private final Logger log = LoggerFactory.getLogger(getClass()); - - private final AuthenticationConfig authConfig; - - private final int connId; - - private final JwtProcessor jwtProcessor; - private final IAuthProvider authProvider; - - private AuthResult authResult = AuthResult.FAILED(); - private SessionInfo session; - private String token; - private boolean wantCookies; - - private HttpRequest pendingRequest; - private CompositeByteBuf pendingContent; - - public Http1AuthHandler( - AuthenticationConfig authConfig, int connId, - JwtProcessor jwtProcessor, - IAuthProvider authProvider) { - - this.authConfig = authConfig; - this.connId = connId; - - this.jwtProcessor = jwtProcessor; - this.authProvider = authProvider; - } - - @Override - public void channelRead(@Nonnull ChannelHandlerContext ctx, @Nonnull Object msg) { - - try { - - // Some auth mechanisms require content as well as headers - // These mechanisms set the result NEED_CONTENT, to trigger aggregation - // Aggregated messages are fed through the normal flow once they are ready - - msg = handleAggregateContent(msg); - - if (msg == null) - return; - - // HTTP/1 auth works purely on the request object - // Each new request will re-run the auth processing - - if ((msg instanceof HttpRequest)) { - var request = (HttpRequest) msg; - processAuthentication(ctx, request); - } - - // If authorization failed a response has already been sent - // Do not pass any further messages down the pipe - - if (authResult.getCode() != AuthResultCode.AUTHORIZED) - return; - - // Authentication succeeded, allow messages to flow on the connection - - // Special handling for request objects, apply translation to the headers - if (msg instanceof HttpRequest) { - var request = (HttpRequest) msg; - if (authProvider.postAuthMatch(request.method().name(), request.uri())) - processPostAuthMatch(ctx, request); - else - processRequest(ctx, request); - } - - // Everything else flows straight through - else { - ReferenceCountUtil.retain(msg); - ctx.fireChannelRead(msg); - } - } - finally { - ReferenceCountUtil.release(msg); - } - } - - @Override - public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) { - - try { - - // Authentication has always succeeded by this point - // Otherwise no request is made to the platform, so no response would be sent - - // This does not account for pipelining, which is disabled by default in modern browsers - - // Special handling for response objects, apply translation to the headers - if (msg instanceof HttpResponse) { - var response = (HttpResponse) msg; - processResponse(ctx, response, promise); - } - - // Everything else flows straight through - else { - ReferenceCountUtil.retain(msg); - ctx.write(msg, promise); - } - } - finally { - ReferenceCountUtil.release(msg); - } - } - - private Object handleAggregateContent(Object msg) { - - if (authResult.getCode() != AuthResultCode.NEED_CONTENT) - return msg; - - if (!(msg instanceof HttpContent) || pendingContent.readableBytes() > PENDING_CONTENT_LIMIT) { - pendingContent.release(); - throw new EUnexpected(); - } - - var content = (HttpContent) msg; - pendingContent.addComponent(content.content()); - pendingContent.writerIndex(pendingContent.writerIndex() + content.content().writerIndex()); - - if (content instanceof LastHttpContent) - - return new DefaultFullHttpRequest( - pendingRequest.protocolVersion(), - pendingRequest.method(), - pendingRequest.uri(), - pendingContent, - pendingRequest.headers(), - ((LastHttpContent) content).trailingHeaders()); - - else - - return null; - } - - private void processAuthentication(ChannelHandlerContext ctx, HttpRequest request) { - - // Start the auth process by looking for the TRAC auth token - // If there is already a valid session, this takes priority - - var headers = new Http1AuthHeaders(request.headers()); - - // Decide whether to send the auth response as headers or cookies - // Always send cookies for browser routes - // For API routes the client can set a header to prefer cookies in the response - - var isApi = - headers.contains(HttpHeaderNames.CONTENT_TYPE) && - headers.get(HttpHeaderNames.CONTENT_TYPE).startsWith("application/") && - !headers.get(HttpHeaderNames.CONTENT_TYPE).equals("application/x-www-form-urlencoded"); - - wantCookies = !isApi || headers.contains(AuthLogic.TRAC_AUTH_COOKIES_HEADER); - - // Look for an existing session token in the request - // If the token gives a valid session then authentication has succeeded - - token = AuthLogic.findTracAuthToken(headers, AuthLogic.SERVER_COOKIE); - session = (token != null) ? jwtProcessor.decodeAndValidate(token) : null; - - if (session != null && session.isValid()) { - - // Check to see if the token needs refreshing - var sessionUpdate = AuthLogic.refreshSession(session, authConfig); - - if (sessionUpdate != session) { - token = jwtProcessor.encodeToken(sessionUpdate); - session = sessionUpdate; - } - - authResult = AuthResult.AUTHORIZED(session.getUserInfo()); - return; - } - - // If the TRAC token is not available or not valid, fall back to the primary auth mechanism - - if (authResult == null || authResult.getCode() != AuthResultCode.NEED_CONTENT) { - var reason = (session == null) ? "no session available" : session.getErrorMessage(); - log.info("conn = {}, authentication required ({})", connId, reason); - } - - // Only one auth provider available atm, for both browser and API routes - var authRequest = AuthRequest.forHttp1Request(request, headers); - authResult = authProvider.attemptAuth(authRequest); - - // If primary auth succeeded, set up the session token - if (authResult.getCode() == AuthResultCode.AUTHORIZED) { - - session = AuthLogic.newSession(authResult.getUserInfo(), authConfig); - token = jwtProcessor.encodeToken(session); - } - - // Send a basic error response for authentication failures for now - // If the result is REDIRECTED the auth provider already responded, so no need to respond again here - - if (authResult.getCode() == AuthResultCode.FAILED) { - - log.error("conn = {}, authentication failed ({})", connId, authResult.getMessage()); - - var response = buildFailedResponse(request, authResult); - - ctx.write(response); - ctx.flush(); - ctx.close(); - } - - if (authResult.getCode() == AuthResultCode.OTHER_RESPONSE) { - - var response = buildAuthResponse(request, authResult.getOtherResponse()); - - ctx.write(response); - ctx.flush(); - } - - if (authResult.getCode() == AuthResultCode.NEED_CONTENT) { - - pendingRequest = request; - pendingContent = ByteBufAllocator.DEFAULT.compositeBuffer(); - } - } - - private void processPostAuthMatch(ChannelHandlerContext ctx, HttpRequest request) { - - var postAuthHeaders = new Http1AuthHeaders(request.headers()); - var postAuthRequest = AuthRequest.forHttp1Request(request, postAuthHeaders); - var postAuthResponse = authProvider.postAuth(postAuthRequest, session.getUserInfo()); - - if (postAuthResponse != null) { - - authResult = AuthResult.OTHER_RESPONSE(postAuthResponse); - var response = buildAuthResponse(request, postAuthResponse); - - processResponse(ctx, response, ctx.newPromise()); - ctx.flush(); - } - } - - private void processRequest(ChannelHandlerContext ctx, HttpRequest request) { - - var headers = new Http1AuthHeaders(request.headers()); - var emptyHeaders = new Http1AuthHeaders(); - - var relayHeaders = AuthLogic.setPlatformAuthHeaders(headers, emptyHeaders, token); - - if (request instanceof FullHttpRequest) { - - var relayContent = ((FullHttpRequest) request).content().retain(); - - var relayRequest = new DefaultFullHttpRequest( - request.protocolVersion(), - request.method(), - request.uri(), - relayContent, - relayHeaders.headers(), - new DefaultHttpHeaders()); - - ctx.fireChannelRead(relayRequest); - } - else { - - var relayRequest = new DefaultHttpRequest( - request.protocolVersion(), - request.method(), - request.uri(), - relayHeaders.headers()); - - ctx.fireChannelRead(relayRequest); - } - } - - private void processResponse(ChannelHandlerContext ctx, HttpResponse response, ChannelPromise promise) { - - var headers = new Http1AuthHeaders(response.headers()); - var emptyHeaders = new Http1AuthHeaders(); - - var relayHeaders = AuthLogic.setClientAuthHeaders(headers, emptyHeaders, token, session, wantCookies); - - if (response instanceof FullHttpResponse) { - - var relayContent = ((FullHttpResponse) response).content().retain(); - - var relayResponse = new DefaultFullHttpResponse( - response.protocolVersion(), - response.status(), - relayContent, - relayHeaders.headers(), - new DefaultHttpHeaders()); - - ctx.write(relayResponse, promise); - } - else { - - var relayResponse = new DefaultHttpResponse( - response.protocolVersion(), - response.status(), - relayHeaders.headers()); - - ctx.write(relayResponse, promise); - } - } - - private FullHttpResponse buildAuthResponse(HttpRequest request, AuthResponse responseDetails) { - - var responseCode = HttpResponseStatus.valueOf( - responseDetails.getStatusCode(), - responseDetails.getStatusMessage()); - - var responseHeaders = new DefaultHttpHeaders(); - for (var header : responseDetails.getHeaders()) - responseHeaders.add(header.getKey(), header.getValue()); - - return new DefaultFullHttpResponse( - request.protocolVersion(), responseCode, - responseDetails.getContent(), responseHeaders, - new DefaultHttpHeaders()); - } - - private FullHttpResponse buildFailedResponse(HttpRequest request, AuthResult authResult) { - - var responseCode = HttpResponseStatus.valueOf( - HttpResponseStatus.UNAUTHORIZED.code(), - authResult.getMessage()); - - return new DefaultFullHttpResponse( - request.protocolVersion(), responseCode, - Unpooled.EMPTY_BUFFER, - new DefaultHttpHeaders(), - new DefaultHttpHeaders()); - } -} diff --git a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/Http1AuthHeaders.java b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/Http1AuthHeaders.java deleted file mode 100644 index 3f145635f..000000000 --- a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/Http1AuthHeaders.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Licensed to the Fintech Open Source Foundation (FINOS) under one or - * more contributor license agreements. See the NOTICE file distributed - * with this work for additional information regarding copyright ownership. - * FINOS licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with the - * License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.finos.tracdap.common.auth.external; - -import io.netty.handler.codec.http.DefaultHttpHeaders; -import io.netty.handler.codec.http.HttpHeaders; - -import java.util.Iterator; -import java.util.List; -import java.util.Map; - - -public class Http1AuthHeaders implements IAuthHeaders { - - private final HttpHeaders headers; - - public Http1AuthHeaders() { - this.headers = new DefaultHttpHeaders(); - } - - public Http1AuthHeaders(HttpHeaders headers) { - this.headers = headers; - } - - public HttpHeaders headers() { - return this.headers; - } - - @Override - public void add(CharSequence name, CharSequence value) { - headers.add(name, value); - } - - @Override - public boolean contains(CharSequence name) { - return headers.contains(name); - } - - @Override - public String get(CharSequence name) { - return headers.get(name); - } - - @Override - public List getAll(CharSequence name) { - return headers.getAll(name); - } - - @Override - public Iterator> iterator() { - return headers.iteratorCharSequence(); - } -} diff --git a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/Http2AuthHeaders.java b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/Http2AuthHeaders.java deleted file mode 100644 index fcd2a9c1f..000000000 --- a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/Http2AuthHeaders.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Licensed to the Fintech Open Source Foundation (FINOS) under one or - * more contributor license agreements. See the NOTICE file distributed - * with this work for additional information regarding copyright ownership. - * FINOS licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with the - * License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.finos.tracdap.common.auth.external; - -import io.netty.handler.codec.http2.DefaultHttp2Headers; -import io.netty.handler.codec.http2.Http2Headers; - -import java.util.Iterator; -import java.util.List; -import java.util.Map; - - -public class Http2AuthHeaders implements IAuthHeaders { - - private final Http2Headers headers; - - public Http2AuthHeaders() { - this.headers = new DefaultHttp2Headers(); - } - - public Http2AuthHeaders(Http2Headers headers) { - this.headers = headers; - } - - @Override - public void add(CharSequence name, CharSequence value) { - headers.add(name, value); - } - - @Override - public boolean contains(CharSequence name) { - return headers.contains(name); - } - - @Override - public CharSequence get(CharSequence name) { - return headers.get(name); - } - - @Override - public List getAll(CharSequence name) { - return headers.getAll(name); - } - - @Override - public Iterator> iterator() { - return headers.iterator(); - } -} diff --git a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/common/BuiltInAuthProvider.java b/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/common/BuiltInAuthProvider.java deleted file mode 100644 index 6d7c8bc90..000000000 --- a/tracdap-libs/tracdap-lib-auth/src/main/java/org/finos/tracdap/common/auth/external/common/BuiltInAuthProvider.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Licensed to the Fintech Open Source Foundation (FINOS) under one or - * more contributor license agreements. See the NOTICE file distributed - * with this work for additional information regarding copyright ownership. - * FINOS licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with the - * License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.finos.tracdap.common.auth.external.common; - -import org.finos.tracdap.common.auth.external.*; -import org.finos.tracdap.common.auth.internal.UserInfo; -import org.finos.tracdap.common.config.ConfigManager; -import org.finos.tracdap.common.exception.EStartup; -import org.finos.tracdap.common.util.ResourceHelpers; - -import io.netty.buffer.Unpooled; -import io.netty.handler.codec.http.*; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.util.MissingResourceException; -import java.util.Properties; - - -public class BuiltInAuthProvider implements IAuthProvider { - - public static final String MAIN_PAGE_KEY = "mainPage"; - - public static final String BUILT_IN_AUTH_ROOT = "/trac-auth/"; - public static final String BUILT_IN_AUTH_PAGE = "/trac-auth/login"; - - public static final String BUILT_IN_CONTENT_PATH = "/builtin/content/"; - public static final String BUILT_IN_LOGIN_PAGE = "/builtin/content/login.html"; - public static final String BUILT_IN_LOGIN_OK_PAGE = "/builtin/content/login_ok.html"; - - - private static final Logger log = LoggerFactory.getLogger(BuiltInAuthProvider.class); - - private final String mainPage; - private final IUserDatabase userDb; - - public BuiltInAuthProvider(Properties properties, ConfigManager configManager) { - - if (!properties.containsKey(MAIN_PAGE_KEY)) { - - var messageTemplate = "The [BUILTIN] auth provider is missing required config property [%s]"; - var message = String.format(messageTemplate, MAIN_PAGE_KEY); - log.error(message); - throw new EStartup(message); - } - - mainPage = properties.getProperty(MAIN_PAGE_KEY); - - this.userDb = CommonAuthPlugin.createUserDb(configManager); - } - - @Override - public AuthResult attemptAuth(AuthRequest request) { - - // API auth by redirect does not work nicely out of the box! - // For now send an auth failure on API routes - // Anyway for browser re-directs there should be some control in the client layer - // Perhaps this can be done in the web bindings package, with suitable config options available? - - var headers = request.getHeaders(); - var isApi = - headers.contains(HttpHeaderNames.CONTENT_TYPE) && - headers.get(HttpHeaderNames.CONTENT_TYPE).toString().startsWith("application/") && - !headers.get(HttpHeaderNames.CONTENT_TYPE).equals("application/x-www-form-urlencoded"); - - if (isApi) - return AuthResult.FAILED("Session expired or not available"); - - if (!request.getUrl().startsWith(BUILT_IN_AUTH_ROOT)) - return redirectToLogin(request); - - if (request.getMethod().equals(HttpMethod.POST.name()) && - request.getUrl().equals(BUILT_IN_AUTH_PAGE)) { - - if (request.getContent() == null) - return AuthResult.NEED_CONTENT(); - - return checkLoginRequest(request); - } - else { - - return serveLoginContent(request, false); - } - } - - @Override - public boolean postAuthMatch(String method, String uri) { - - return uri.startsWith(BUILT_IN_AUTH_ROOT); - } - - @Override - public AuthResponse postAuth(AuthRequest request, UserInfo userInfo) { - - if (request.getUrl().startsWith(BUILT_IN_AUTH_ROOT)) { - - return serveLoginContent(request, true).getOtherResponse(); - } - else { - - return null; - } - } - - private AuthResult redirectToLogin(AuthRequest request) { - - log.info("AUTHENTICATION: Using built-in authentication"); - - if (request.getUrl().equals(BUILT_IN_AUTH_PAGE)) - return serveLoginContent(request, false); - - else { - - var headers = new Http1AuthHeaders(); - headers.add(HttpHeaderNames.LOCATION, BUILT_IN_AUTH_PAGE); - - var response = new AuthResponse( - HttpResponseStatus.TEMPORARY_REDIRECT.code(), "Login redirect", - headers, Unpooled.EMPTY_BUFFER); - - return AuthResult.OTHER_RESPONSE(response); - } - } - - private AuthResult checkLoginRequest(AuthRequest request) { - - var content = new String(request.getContent(), StandardCharsets.US_ASCII); - var decoder = new QueryStringDecoder(BUILT_IN_AUTH_PAGE + "?" + content); - var loginParams = decoder.parameters(); - - var usernameParam = loginParams.get("username"); - var passwordParam = loginParams.get("password"); - - if (usernameParam == null || usernameParam.size() != 1 || - passwordParam == null || passwordParam.size() != 1) { - return redirectToLogin(request); - } - - var username = usernameParam.get(0); - var password = passwordParam.get(0); - - if (LocalUsers.checkPassword(userDb, username, password, log)) { - var userInfo = LocalUsers.getUserInfo(userDb, username); - return AuthResult.AUTHORIZED(userInfo); - } - else { - return redirectToLogin(request); - } - } - - private AuthResult serveLoginContent(AuthRequest request, boolean loggedIn) { - - try { - var uri = URI.create(request.getUrl()); - - var resourcePath = uri.getPath().replace(BUILT_IN_AUTH_ROOT, BUILT_IN_CONTENT_PATH); - - if (uri.getPath().equals(BUILT_IN_AUTH_PAGE)) - resourcePath = loggedIn ? BUILT_IN_LOGIN_OK_PAGE : BUILT_IN_LOGIN_PAGE; - - var resourceBytes = ResourceHelpers.loadResourceAsBytes(resourcePath, getClass()); - - if (loggedIn && resourcePath.equals(BUILT_IN_LOGIN_OK_PAGE)) - resourceBytes = insertRedirect(resourceBytes); - - var resourceContent = Unpooled.wrappedBuffer(resourceBytes); - var resourceType = mimeTypeMapping(resourcePath); - - var headers = new Http1AuthHeaders(); - headers.add(HttpHeaderNames.CONTENT_TYPE, resourceType); - headers.add(HttpHeaderNames.CONTENT_LENGTH, Integer.toString(resourceContent.readableBytes())); - - var response = new AuthResponse( - HttpResponseStatus.OK.code(), - HttpResponseStatus.OK.reasonPhrase(), - new Http1AuthHeaders(), - resourceContent); - - return AuthResult.OTHER_RESPONSE(response); - } - catch (IllegalArgumentException | MissingResourceException e) { - return redirectToLogin(request); - } - } - - private byte[] insertRedirect(byte[] pageBytes) { - - var pageText = new String(pageBytes, StandardCharsets.UTF_8); - pageText = pageText.replace("${REDIRECT}", mainPage); - return pageText.getBytes(StandardCharsets.UTF_8); - } - - private String mimeTypeMapping(String path) { - - var sep = path.lastIndexOf("."); - var ext = path.substring(sep + 1); - - if (ext.equals("html")) - return "text/html"; - - if (ext.equals("css")) - return "text/css"; - - if (ext.equals("png")) - return "image/png"; - - return "text/html"; - - } -} diff --git a/tracdap-libs/tracdap-lib-auth/src/main/resources/META-INF/services/org.finos.tracdap.common.plugin.ITracPlugin b/tracdap-libs/tracdap-lib-auth/src/main/resources/META-INF/services/org.finos.tracdap.common.plugin.ITracPlugin index bf1c56afc..ccfd8ab52 100644 --- a/tracdap-libs/tracdap-lib-auth/src/main/resources/META-INF/services/org.finos.tracdap.common.plugin.ITracPlugin +++ b/tracdap-libs/tracdap-lib-auth/src/main/resources/META-INF/services/org.finos.tracdap.common.plugin.ITracPlugin @@ -14,4 +14,4 @@ # limitations under the License. # Include a number of common auth providers as part of the core implementation -org.finos.tracdap.common.auth.external.common.CommonAuthPlugin +org.finos.tracdap.auth.login.simple.SimpleLoginPlugin diff --git a/tracdap-libs/tracdap-lib-auth/src/main/resources/builtin/content/login.html b/tracdap-libs/tracdap-lib-auth/src/main/resources/login/pages/login_form.html similarity index 72% rename from tracdap-libs/tracdap-lib-auth/src/main/resources/builtin/content/login.html rename to tracdap-libs/tracdap-lib-auth/src/main/resources/login/pages/login_form.html index d6749dd16..43f59231e 100644 --- a/tracdap-libs/tracdap-lib-auth/src/main/resources/builtin/content/login.html +++ b/tracdap-libs/tracdap-lib-auth/src/main/resources/login/pages/login_form.html @@ -1,10 +1,10 @@