mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 05:01:07 +00:00
Working version of authentication for static & dynamic (process) route providers
This commit is contained in:
@ -77,8 +77,8 @@ public class QApplicationJavalinServer
|
|||||||
private boolean serveLegacyUnversionedMiddlewareAPI = true;
|
private boolean serveLegacyUnversionedMiddlewareAPI = true;
|
||||||
private List<AbstractMiddlewareVersion> middlewareVersionList = List.of(new MiddlewareVersionV1());
|
private List<AbstractMiddlewareVersion> middlewareVersionList = List.of(new MiddlewareVersionV1());
|
||||||
private List<QJavalinRouteProviderInterface> additionalRouteProviders = null;
|
private List<QJavalinRouteProviderInterface> additionalRouteProviders = null;
|
||||||
private Consumer<Javalin> javalinConfigurationCustomizer = null;
|
private Consumer<Javalin> javalinConfigurationCustomizer = null;
|
||||||
private QJavalinMetaData javalinMetaData = null;
|
private QJavalinMetaData javalinMetaData = null;
|
||||||
|
|
||||||
private long lastQInstanceHotSwapMillis;
|
private long lastQInstanceHotSwapMillis;
|
||||||
private long millisBetweenHotSwaps = 2500;
|
private long millisBetweenHotSwaps = 2500;
|
||||||
@ -197,6 +197,15 @@ public class QApplicationJavalinServer
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
// also pass the javalin service into any additionalRouteProviders, //
|
||||||
|
// in case they need additional setup, e.g., before/after handlers. //
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
for(QJavalinRouteProviderInterface routeProvider : CollectionUtils.nonNullList(additionalRouteProviders))
|
||||||
|
{
|
||||||
|
routeProvider.acceptJavalinService(service);
|
||||||
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////
|
||||||
// per system property, set the server to hot-swap the q instance before all routes //
|
// per system property, set the server to hot-swap the q instance before all routes //
|
||||||
//////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -228,7 +237,7 @@ public class QApplicationJavalinServer
|
|||||||
/***************************************************************************
|
/***************************************************************************
|
||||||
** initial tests with the SimpleFileSystemDirectoryRouter would sometimes
|
** initial tests with the SimpleFileSystemDirectoryRouter would sometimes
|
||||||
** have a Content-Type:text/html;charset=null !
|
** have a Content-Type:text/html;charset=null !
|
||||||
** which doesn't seem every valid (and at least it broke our unit test).
|
** which doesn't seem ever valid (and at least it broke our unit test).
|
||||||
** so, if w see charset=null in contentType, replace it with the system
|
** so, if w see charset=null in contentType, replace it with the system
|
||||||
** default, which may not be 100% right, but has to be better than "null"...
|
** default, which may not be 100% right, but has to be better than "null"...
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
@ -242,7 +251,6 @@ public class QApplicationJavalinServer
|
|||||||
contentType = contentType.replace("charset=null", "charset=" + Charset.defaultCharset().name());
|
contentType = contentType.replace("charset=null", "charset=" + Charset.defaultCharset().name());
|
||||||
context.res().setContentType(contentType);
|
context.res().setContentType(contentType);
|
||||||
}
|
}
|
||||||
System.out.println();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -630,6 +638,7 @@ public class QApplicationJavalinServer
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for javalinMetaData
|
** Getter for javalinMetaData
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -659,5 +668,4 @@ public class QApplicationJavalinServer
|
|||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ package com.kingsrook.qqq.middleware.javalin;
|
|||||||
|
|
||||||
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
|
import io.javalin.Javalin;
|
||||||
import io.javalin.apibuilder.EndpointGroup;
|
import io.javalin.apibuilder.EndpointGroup;
|
||||||
import io.javalin.config.JavalinConfig;
|
import io.javalin.config.JavalinConfig;
|
||||||
|
|
||||||
@ -53,7 +54,9 @@ public interface QJavalinRouteProviderInterface
|
|||||||
|
|
||||||
|
|
||||||
/***************************************************************************
|
/***************************************************************************
|
||||||
**
|
** when the javalin service is being configured as part of its boot up,
|
||||||
|
** accept the javalinConfig object, to perform whatever setup you need,
|
||||||
|
** such as setting up routes.
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
default void acceptJavalinConfig(JavalinConfig config)
|
default void acceptJavalinConfig(JavalinConfig config)
|
||||||
{
|
{
|
||||||
@ -61,4 +64,17 @@ public interface QJavalinRouteProviderInterface
|
|||||||
// noop at default //
|
// noop at default //
|
||||||
/////////////////////
|
/////////////////////
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
** when the javalin service is being configured as part of its boot up,
|
||||||
|
** accept the Javalin service object, to perform whatever setup you need,
|
||||||
|
** such as setting up before/after handlers.
|
||||||
|
***************************************************************************/
|
||||||
|
default void acceptJavalinService(Javalin service)
|
||||||
|
{
|
||||||
|
/////////////////////
|
||||||
|
// noop at default //
|
||||||
|
/////////////////////
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ package com.kingsrook.qqq.middleware.javalin.metadata;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -38,6 +39,8 @@ public class JavalinRouteProviderMetaData implements QMetaDataObject
|
|||||||
|
|
||||||
private List<String> methods;
|
private List<String> methods;
|
||||||
|
|
||||||
|
private QCodeReference routeAuthenticator;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -172,4 +175,35 @@ public class JavalinRouteProviderMetaData implements QMetaDataObject
|
|||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for routeAuthenticator
|
||||||
|
*******************************************************************************/
|
||||||
|
public QCodeReference getRouteAuthenticator()
|
||||||
|
{
|
||||||
|
return (this.routeAuthenticator);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for routeAuthenticator
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setRouteAuthenticator(QCodeReference routeAuthenticator)
|
||||||
|
{
|
||||||
|
this.routeAuthenticator = routeAuthenticator;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for routeAuthenticator
|
||||||
|
*******************************************************************************/
|
||||||
|
public JavalinRouteProviderMetaData withRouteAuthenticator(QCodeReference routeAuthenticator)
|
||||||
|
{
|
||||||
|
this.routeAuthenticator = routeAuthenticator;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -27,22 +27,31 @@ import java.io.Serializable;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||||
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
|
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.tables.StorageAction;
|
||||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
|
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.storage.StorageInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession;
|
||||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||||
import com.kingsrook.qqq.backend.javalin.QJavalinImplementation;
|
import com.kingsrook.qqq.backend.javalin.QJavalinImplementation;
|
||||||
import com.kingsrook.qqq.backend.javalin.QJavalinUtils;
|
import com.kingsrook.qqq.backend.javalin.QJavalinUtils;
|
||||||
import com.kingsrook.qqq.middleware.javalin.QJavalinRouteProviderInterface;
|
import com.kingsrook.qqq.middleware.javalin.QJavalinRouteProviderInterface;
|
||||||
import com.kingsrook.qqq.middleware.javalin.metadata.JavalinRouteProviderMetaData;
|
import com.kingsrook.qqq.middleware.javalin.metadata.JavalinRouteProviderMetaData;
|
||||||
|
import com.kingsrook.qqq.middleware.javalin.routeproviders.authentication.RouteAuthenticatorInterface;
|
||||||
import io.javalin.apibuilder.ApiBuilder;
|
import io.javalin.apibuilder.ApiBuilder;
|
||||||
import io.javalin.apibuilder.EndpointGroup;
|
import io.javalin.apibuilder.EndpointGroup;
|
||||||
import io.javalin.http.Context;
|
import io.javalin.http.Context;
|
||||||
import io.javalin.http.HttpStatus;
|
import io.javalin.http.HttpStatus;
|
||||||
|
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -55,7 +64,10 @@ public class ProcessBasedRouter implements QJavalinRouteProviderInterface
|
|||||||
private final String hostedPath;
|
private final String hostedPath;
|
||||||
private final String processName;
|
private final String processName;
|
||||||
private final List<String> methods;
|
private final List<String> methods;
|
||||||
private QInstance qInstance;
|
|
||||||
|
private QCodeReference routeAuthenticator;
|
||||||
|
|
||||||
|
private QInstance qInstance;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -76,6 +88,7 @@ public class ProcessBasedRouter implements QJavalinRouteProviderInterface
|
|||||||
public ProcessBasedRouter(JavalinRouteProviderMetaData routeProvider)
|
public ProcessBasedRouter(JavalinRouteProviderMetaData routeProvider)
|
||||||
{
|
{
|
||||||
this(routeProvider.getHostedPath(), routeProvider.getProcessName(), routeProvider.getMethods());
|
this(routeProvider.getHostedPath(), routeProvider.getProcessName(), routeProvider.getMethods());
|
||||||
|
setRouteAuthenticator(routeProvider.getRouteAuthenticator());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -145,41 +158,36 @@ public class ProcessBasedRouter implements QJavalinRouteProviderInterface
|
|||||||
RunProcessInput input = new RunProcessInput();
|
RunProcessInput input = new RunProcessInput();
|
||||||
input.setProcessName(processName);
|
input.setProcessName(processName);
|
||||||
|
|
||||||
try
|
QContext.init(qInstance, new QSystemUserSession());
|
||||||
|
|
||||||
|
boolean isAuthenticated = false;
|
||||||
|
if(routeAuthenticator == null)
|
||||||
{
|
{
|
||||||
QJavalinImplementation.setupSession(context, input);
|
isAuthenticated = true;
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
else
|
||||||
{
|
{
|
||||||
context.header("WWW-Authenticate", "Basic realm=\"Access to this QQQ site\"");
|
try
|
||||||
context.status(HttpStatus.UNAUTHORIZED);
|
{
|
||||||
|
RouteAuthenticatorInterface routeAuthenticator = QCodeLoader.getAdHoc(RouteAuthenticatorInterface.class, this.routeAuthenticator);
|
||||||
|
isAuthenticated = routeAuthenticator.authenticateRequest(context);
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
context.skipRemainingHandlers();
|
||||||
|
QJavalinImplementation.handleException(context, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!isAuthenticated)
|
||||||
|
{
|
||||||
|
LOG.info("Request is not authenticated, so returning before running process", logPair("processName", processName), logPair("path", context.path()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
boolean authorized = false;
|
|
||||||
String authorization = context.header("Authorization");
|
|
||||||
if(authorization != null && authorization.matches("^Basic .+"))
|
|
||||||
{
|
|
||||||
String base64Authorization = authorization.substring("Basic ".length());
|
|
||||||
String decoded = new String(Base64.getDecoder().decode(base64Authorization), StandardCharsets.UTF_8);
|
|
||||||
String[] parts = decoded.split(":", 2);
|
|
||||||
|
|
||||||
QAuthenticationModuleDispatcher qAuthenticationModuleDispatcher = new QAuthenticationModuleDispatcher();
|
|
||||||
QAuthenticationModuleInterface authenticationModule = qAuthenticationModuleDispatcher.getQModule(qInstance.getAuthentication());
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!authorized)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo - not always system-user session!!
|
|
||||||
QContext.init(this.qInstance, new QSystemUserSession());
|
|
||||||
*/
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
LOG.info("Running [" + processName + "] to serve [" + context.path() + "]...");
|
LOG.info("Running process to serve route", logPair("processName", processName), logPair("path", context.path()));
|
||||||
|
|
||||||
/////////////////////
|
/////////////////////
|
||||||
// run the process //
|
// run the process //
|
||||||
@ -190,16 +198,10 @@ public class ProcessBasedRouter implements QJavalinRouteProviderInterface
|
|||||||
input.addValue("pathParams", new HashMap<>(context.pathParamMap()));
|
input.addValue("pathParams", new HashMap<>(context.pathParamMap()));
|
||||||
input.addValue("queryParams", new HashMap<>(context.queryParamMap()));
|
input.addValue("queryParams", new HashMap<>(context.queryParamMap()));
|
||||||
input.addValue("formParams", new HashMap<>(context.formParamMap()));
|
input.addValue("formParams", new HashMap<>(context.formParamMap()));
|
||||||
RunProcessOutput runProcessOutput = new RunProcessAction().execute(input);
|
input.addValue("cookies", new HashMap<>(context.cookieMap()));
|
||||||
|
input.addValue("requestHeaders", new HashMap<>(context.headerMap()));
|
||||||
|
|
||||||
/////////////////
|
RunProcessOutput runProcessOutput = new RunProcessAction().execute(input);
|
||||||
// status code //
|
|
||||||
/////////////////
|
|
||||||
Integer statusCode = runProcessOutput.getValueInteger("statusCode");
|
|
||||||
if(statusCode != null)
|
|
||||||
{
|
|
||||||
context.status(statusCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////////////
|
/////////////////
|
||||||
// headers map //
|
// headers map //
|
||||||
@ -217,26 +219,46 @@ public class ProcessBasedRouter implements QJavalinRouteProviderInterface
|
|||||||
// maybe via the callback object??? input.setCallback(new QProcessCallback() {});
|
// maybe via the callback object??? input.setCallback(new QProcessCallback() {});
|
||||||
// context.resultInputStream();
|
// context.resultInputStream();
|
||||||
|
|
||||||
///////////////////
|
//////////////
|
||||||
// response body //
|
// response //
|
||||||
///////////////////
|
//////////////
|
||||||
Serializable response = runProcessOutput.getValue("response");
|
Integer statusCode = runProcessOutput.getValueInteger("statusCode");
|
||||||
if(response instanceof String s)
|
String redirectURL = runProcessOutput.getValueString("redirectURL");
|
||||||
|
String responseString = runProcessOutput.getValueString("responseString");
|
||||||
|
byte[] responseBytes = runProcessOutput.getValueByteArray("responseBytes");
|
||||||
|
StorageInput responseStorageInput = (StorageInput) runProcessOutput.getValue("responseStorageInput");
|
||||||
|
|
||||||
|
if(StringUtils.hasContent(redirectURL))
|
||||||
{
|
{
|
||||||
context.result(s);
|
context.redirect(redirectURL, statusCode == null ? HttpStatus.FOUND : HttpStatus.forStatus(statusCode));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else if(response instanceof byte[] ba)
|
|
||||||
|
if(statusCode != null)
|
||||||
{
|
{
|
||||||
context.result(ba);
|
context.status(statusCode);
|
||||||
}
|
}
|
||||||
else if(response instanceof InputStream is)
|
|
||||||
|
if(StringUtils.hasContent(responseString))
|
||||||
{
|
{
|
||||||
context.result(is);
|
context.result(responseString);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
if(responseBytes != null && responseBytes.length > 0)
|
||||||
{
|
{
|
||||||
context.result(ValueUtils.getValueAsString(response));
|
context.result(responseBytes);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(responseStorageInput != null)
|
||||||
|
{
|
||||||
|
InputStream inputStream = new StorageAction().getInputStream(responseStorageInput);
|
||||||
|
context.result(inputStream);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw (new QException("No response value was set in the process output state."));
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
@ -248,4 +270,35 @@ public class ProcessBasedRouter implements QJavalinRouteProviderInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for routeAuthenticator
|
||||||
|
*******************************************************************************/
|
||||||
|
public QCodeReference getRouteAuthenticator()
|
||||||
|
{
|
||||||
|
return (this.routeAuthenticator);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for routeAuthenticator
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setRouteAuthenticator(QCodeReference routeAuthenticator)
|
||||||
|
{
|
||||||
|
this.routeAuthenticator = routeAuthenticator;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for routeAuthenticator
|
||||||
|
*******************************************************************************/
|
||||||
|
public ProcessBasedRouter withRouteAuthenticator(QCodeReference routeAuthenticator)
|
||||||
|
{
|
||||||
|
this.routeAuthenticator = routeAuthenticator;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,412 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.middleware.javalin.routeproviders;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessState;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.processes.QProcessPayload;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** process payload shared the processes which are used as process-based-router
|
||||||
|
** processes. e.g., the fields here are those written to and read by
|
||||||
|
** ProcessBasedRouter.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class ProcessBasedRouterPayload extends QProcessPayload
|
||||||
|
{
|
||||||
|
private String path;
|
||||||
|
private String method;
|
||||||
|
private Map<String, String> pathParams;
|
||||||
|
private Map<String, String> queryParams;
|
||||||
|
private Map<String, String> formParams;
|
||||||
|
private Map<String, String> cookies;
|
||||||
|
|
||||||
|
private Integer statusCode;
|
||||||
|
private String redirectURL;
|
||||||
|
private Map<String, String> responseHeaders;
|
||||||
|
private String responseString;
|
||||||
|
private byte[] responseBytes;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Constructor
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public ProcessBasedRouterPayload()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Constructor
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public ProcessBasedRouterPayload(ProcessState processState)
|
||||||
|
{
|
||||||
|
this.populateFromProcessState(processState);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for path
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getPath()
|
||||||
|
{
|
||||||
|
return (this.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for path
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setPath(String path)
|
||||||
|
{
|
||||||
|
this.path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for path
|
||||||
|
*******************************************************************************/
|
||||||
|
public ProcessBasedRouterPayload withPath(String path)
|
||||||
|
{
|
||||||
|
this.path = path;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for method
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getMethod()
|
||||||
|
{
|
||||||
|
return (this.method);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for method
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setMethod(String method)
|
||||||
|
{
|
||||||
|
this.method = method;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for method
|
||||||
|
*******************************************************************************/
|
||||||
|
public ProcessBasedRouterPayload withMethod(String method)
|
||||||
|
{
|
||||||
|
this.method = method;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for pathParams
|
||||||
|
*******************************************************************************/
|
||||||
|
public Map<String, String> getPathParams()
|
||||||
|
{
|
||||||
|
return (this.pathParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for pathParams
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setPathParams(Map<String, String> pathParams)
|
||||||
|
{
|
||||||
|
this.pathParams = pathParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for pathParams
|
||||||
|
*******************************************************************************/
|
||||||
|
public ProcessBasedRouterPayload withPathParams(Map<String, String> pathParams)
|
||||||
|
{
|
||||||
|
this.pathParams = pathParams;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for queryParams
|
||||||
|
*******************************************************************************/
|
||||||
|
public Map<String, String> getQueryParams()
|
||||||
|
{
|
||||||
|
return (this.queryParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for queryParams
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setQueryParams(Map<String, String> queryParams)
|
||||||
|
{
|
||||||
|
this.queryParams = queryParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for queryParams
|
||||||
|
*******************************************************************************/
|
||||||
|
public ProcessBasedRouterPayload withQueryParams(Map<String, String> queryParams)
|
||||||
|
{
|
||||||
|
this.queryParams = queryParams;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for formParams
|
||||||
|
*******************************************************************************/
|
||||||
|
public Map<String, String> getFormParams()
|
||||||
|
{
|
||||||
|
return (this.formParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for formParams
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setFormParams(Map<String, String> formParams)
|
||||||
|
{
|
||||||
|
this.formParams = formParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for formParams
|
||||||
|
*******************************************************************************/
|
||||||
|
public ProcessBasedRouterPayload withFormParams(Map<String, String> formParams)
|
||||||
|
{
|
||||||
|
this.formParams = formParams;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for cookies
|
||||||
|
*******************************************************************************/
|
||||||
|
public Map<String, String> getCookies()
|
||||||
|
{
|
||||||
|
return (this.cookies);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for cookies
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setCookies(Map<String, String> cookies)
|
||||||
|
{
|
||||||
|
this.cookies = cookies;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for cookies
|
||||||
|
*******************************************************************************/
|
||||||
|
public ProcessBasedRouterPayload withCookies(Map<String, String> cookies)
|
||||||
|
{
|
||||||
|
this.cookies = cookies;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for statusCode
|
||||||
|
*******************************************************************************/
|
||||||
|
public Integer getStatusCode()
|
||||||
|
{
|
||||||
|
return (this.statusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for statusCode
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setStatusCode(Integer statusCode)
|
||||||
|
{
|
||||||
|
this.statusCode = statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for statusCode
|
||||||
|
*******************************************************************************/
|
||||||
|
public ProcessBasedRouterPayload withStatusCode(Integer statusCode)
|
||||||
|
{
|
||||||
|
this.statusCode = statusCode;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for responseHeaders
|
||||||
|
*******************************************************************************/
|
||||||
|
public Map<String, String> getResponseHeaders()
|
||||||
|
{
|
||||||
|
return (this.responseHeaders);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for responseHeaders
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setResponseHeaders(Map<String, String> responseHeaders)
|
||||||
|
{
|
||||||
|
this.responseHeaders = responseHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for responseHeaders
|
||||||
|
*******************************************************************************/
|
||||||
|
public ProcessBasedRouterPayload withResponseHeaders(Map<String, String> responseHeaders)
|
||||||
|
{
|
||||||
|
this.responseHeaders = responseHeaders;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for responseString
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getResponseString()
|
||||||
|
{
|
||||||
|
return (this.responseString);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for responseString
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setResponseString(String responseString)
|
||||||
|
{
|
||||||
|
this.responseString = responseString;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for responseString
|
||||||
|
*******************************************************************************/
|
||||||
|
public ProcessBasedRouterPayload withResponseString(String responseString)
|
||||||
|
{
|
||||||
|
this.responseString = responseString;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for responseBytes
|
||||||
|
*******************************************************************************/
|
||||||
|
public byte[] getResponseBytes()
|
||||||
|
{
|
||||||
|
return (this.responseBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for responseBytes
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setResponseBytes(byte[] responseBytes)
|
||||||
|
{
|
||||||
|
this.responseBytes = responseBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for responseBytes
|
||||||
|
*******************************************************************************/
|
||||||
|
public ProcessBasedRouterPayload withResponseBytes(byte[] responseBytes)
|
||||||
|
{
|
||||||
|
this.responseBytes = responseBytes;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for redirectURL
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getRedirectURL()
|
||||||
|
{
|
||||||
|
return (this.redirectURL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for redirectURL
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setRedirectURL(String redirectURL)
|
||||||
|
{
|
||||||
|
this.redirectURL = redirectURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for redirectURL
|
||||||
|
*******************************************************************************/
|
||||||
|
public ProcessBasedRouterPayload withRedirectURL(String redirectURL)
|
||||||
|
{
|
||||||
|
this.redirectURL = redirectURL;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -22,22 +22,40 @@
|
|||||||
package com.kingsrook.qqq.middleware.javalin.routeproviders;
|
package com.kingsrook.qqq.middleware.javalin.routeproviders;
|
||||||
|
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||||
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession;
|
||||||
|
import com.kingsrook.qqq.backend.javalin.QJavalinImplementation;
|
||||||
import com.kingsrook.qqq.middleware.javalin.QJavalinRouteProviderInterface;
|
import com.kingsrook.qqq.middleware.javalin.QJavalinRouteProviderInterface;
|
||||||
import com.kingsrook.qqq.middleware.javalin.metadata.JavalinRouteProviderMetaData;
|
import com.kingsrook.qqq.middleware.javalin.metadata.JavalinRouteProviderMetaData;
|
||||||
|
import com.kingsrook.qqq.middleware.javalin.routeproviders.authentication.RouteAuthenticatorInterface;
|
||||||
|
import io.javalin.Javalin;
|
||||||
import io.javalin.config.JavalinConfig;
|
import io.javalin.config.JavalinConfig;
|
||||||
|
import io.javalin.http.Context;
|
||||||
import io.javalin.http.staticfiles.Location;
|
import io.javalin.http.staticfiles.Location;
|
||||||
import io.javalin.http.staticfiles.StaticFileConfig;
|
import io.javalin.http.staticfiles.StaticFileConfig;
|
||||||
|
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
** javalin route provider that hosts a path in the http server via a path on
|
||||||
|
** the file system
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class SimpleFileSystemDirectoryRouter implements QJavalinRouteProviderInterface
|
public class SimpleFileSystemDirectoryRouter implements QJavalinRouteProviderInterface
|
||||||
{
|
{
|
||||||
private final String hostedPath;
|
private static final QLogger LOG = QLogger.getLogger(SimpleFileSystemDirectoryRouter.class);
|
||||||
private final String fileSystemPath;
|
|
||||||
private QInstance qInstance;
|
private final String hostedPath;
|
||||||
|
private final String fileSystemPath;
|
||||||
|
|
||||||
|
private QCodeReference routeAuthenticator;
|
||||||
|
|
||||||
|
private QInstance qInstance;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -59,6 +77,7 @@ public class SimpleFileSystemDirectoryRouter implements QJavalinRouteProviderInt
|
|||||||
public SimpleFileSystemDirectoryRouter(JavalinRouteProviderMetaData routeProvider)
|
public SimpleFileSystemDirectoryRouter(JavalinRouteProviderMetaData routeProvider)
|
||||||
{
|
{
|
||||||
this(routeProvider.getHostedPath(), routeProvider.getFileSystemPath());
|
this(routeProvider.getHostedPath(), routeProvider.getFileSystemPath());
|
||||||
|
setRouteAuthenticator(routeProvider.getRouteAuthenticator());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -74,17 +93,132 @@ public class SimpleFileSystemDirectoryRouter implements QJavalinRouteProviderInt
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
private void handleJavalinStaticFileConfig(StaticFileConfig staticFileConfig)
|
||||||
|
{
|
||||||
|
URL resource = getClass().getClassLoader().getResource(fileSystemPath);
|
||||||
|
if(resource == null)
|
||||||
|
{
|
||||||
|
String message = "Could not find file system path: " + fileSystemPath;
|
||||||
|
if(fileSystemPath.startsWith("/") && getClass().getClassLoader().getResource(fileSystemPath.replaceFirst("^/+", "")) != null)
|
||||||
|
{
|
||||||
|
message += ". For non-absolute paths, do not prefix with a leading slash.";
|
||||||
|
}
|
||||||
|
throw new RuntimeException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!hostedPath.startsWith("/"))
|
||||||
|
{
|
||||||
|
LOG.warn("hostedPath [" + hostedPath + "] should probably start with a leading slash...");
|
||||||
|
}
|
||||||
|
|
||||||
|
staticFileConfig.directory = resource.getFile();
|
||||||
|
staticFileConfig.hostedPath = hostedPath;
|
||||||
|
staticFileConfig.location = Location.EXTERNAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
private void before(Context context) throws QException
|
||||||
|
{
|
||||||
|
LOG.debug("In before handler for simpleFileSystemRouter", logPair("hostedPath", hostedPath));
|
||||||
|
QContext.init(qInstance, new QSystemUserSession());
|
||||||
|
|
||||||
|
if(routeAuthenticator != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
RouteAuthenticatorInterface routeAuthenticator = QCodeLoader.getAdHoc(RouteAuthenticatorInterface.class, this.routeAuthenticator);
|
||||||
|
boolean isAuthenticated = routeAuthenticator.authenticateRequest(context);
|
||||||
|
if(!isAuthenticated)
|
||||||
|
{
|
||||||
|
LOG.info("Static file request is not authenticated, so telling javalin to skip remaining handlers", logPair("path", context.path()));
|
||||||
|
context.skipRemainingHandlers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
context.skipRemainingHandlers();
|
||||||
|
QJavalinImplementation.handleException(context, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
private void after(Context context)
|
||||||
|
{
|
||||||
|
LOG.debug("In after handler for simpleFileSystemRouter", logPair("hostedPath", hostedPath));
|
||||||
|
QContext.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/***************************************************************************
|
/***************************************************************************
|
||||||
**
|
**
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
@Override
|
@Override
|
||||||
public void acceptJavalinConfig(JavalinConfig config)
|
public void acceptJavalinConfig(JavalinConfig config)
|
||||||
{
|
{
|
||||||
config.staticFiles.add((StaticFileConfig userConfig) ->
|
config.staticFiles.add(this::handleJavalinStaticFileConfig);
|
||||||
{
|
|
||||||
userConfig.hostedPath = hostedPath;
|
|
||||||
userConfig.directory = fileSystemPath;
|
|
||||||
userConfig.location = Location.EXTERNAL;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
@Override
|
||||||
|
public void acceptJavalinService(Javalin service)
|
||||||
|
{
|
||||||
|
String javalinPath = hostedPath;
|
||||||
|
if(!javalinPath.endsWith("/"))
|
||||||
|
{
|
||||||
|
javalinPath += "/";
|
||||||
|
}
|
||||||
|
javalinPath += "<subPath>";
|
||||||
|
|
||||||
|
service.before(javalinPath, this::before);
|
||||||
|
service.before(javalinPath, this::after);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for routeAuthenticator
|
||||||
|
*******************************************************************************/
|
||||||
|
public QCodeReference getRouteAuthenticator()
|
||||||
|
{
|
||||||
|
return (this.routeAuthenticator);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for routeAuthenticator
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setRouteAuthenticator(QCodeReference routeAuthenticator)
|
||||||
|
{
|
||||||
|
this.routeAuthenticator = routeAuthenticator;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for routeAuthenticator
|
||||||
|
*******************************************************************************/
|
||||||
|
public SimpleFileSystemDirectoryRouter withRouteAuthenticator(QCodeReference routeAuthenticator)
|
||||||
|
{
|
||||||
|
this.routeAuthenticator = routeAuthenticator;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.middleware.javalin.routeproviders.authentication;
|
||||||
|
|
||||||
|
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import io.javalin.http.Context;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** interface used by QJavalinRouteProviderInterface subclasses, to interact with
|
||||||
|
** QQQ Authentication modules, to provide authentication to custom javalin routes.
|
||||||
|
*******************************************************************************/
|
||||||
|
public interface RouteAuthenticatorInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
** where authentication for a route occurs, before the route is served.
|
||||||
|
**
|
||||||
|
** @return true if request is authenticated; else false
|
||||||
|
***************************************************************************/
|
||||||
|
boolean authenticateRequest(Context context) throws QException;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.middleware.javalin.routeproviders.authentication;
|
||||||
|
|
||||||
|
|
||||||
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QAuthenticationException;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException;
|
||||||
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||||
|
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleDispatcher;
|
||||||
|
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface;
|
||||||
|
import com.kingsrook.qqq.backend.javalin.QJavalinImplementation;
|
||||||
|
import io.javalin.http.Context;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** simple implementation of a route authenticator. Assumes that unauthenticated
|
||||||
|
** requests should redirect to a login page. Note though, maybe that should be
|
||||||
|
** more intelligent, like, only redirect requets for a .html file, but not
|
||||||
|
** requests for include files like images or .js/.css?
|
||||||
|
*******************************************************************************/
|
||||||
|
public class SimpleRouteAuthenticator implements RouteAuthenticatorInterface
|
||||||
|
{
|
||||||
|
private static final QLogger LOG = QLogger.getLogger(SimpleRouteAuthenticator.class);
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
public boolean authenticateRequest(Context context) throws QException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
QSession qSession = QJavalinImplementation.setupSession(context, null);
|
||||||
|
LOG.debug("Session has been activated", "uuid=" + qSession.getUuid());
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
catch(QAuthenticationException e)
|
||||||
|
{
|
||||||
|
QAuthenticationModuleDispatcher qAuthenticationModuleDispatcher = new QAuthenticationModuleDispatcher();
|
||||||
|
QAuthenticationModuleInterface authenticationModule = qAuthenticationModuleDispatcher.getQModule(QContext.getQInstance().getAuthentication());
|
||||||
|
|
||||||
|
String redirectURL = authenticationModule.getLoginRedirectUrl(context.fullUrl());
|
||||||
|
|
||||||
|
context.redirect(redirectURL);
|
||||||
|
LOG.debug("Redirecting request, due to required session missing");
|
||||||
|
return (false);
|
||||||
|
}
|
||||||
|
catch(QModuleDispatchException e)
|
||||||
|
{
|
||||||
|
throw (new QException("Error authenticating request", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -22,10 +22,8 @@
|
|||||||
package com.kingsrook.qqq.backend.javalin;
|
package com.kingsrook.qqq.backend.javalin;
|
||||||
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -98,6 +96,8 @@ import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager;
|
|||||||
import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
|
import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
|
||||||
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
|
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
|
||||||
import com.kingsrook.qqq.middleware.javalin.metadata.JavalinRouteProviderMetaData;
|
import com.kingsrook.qqq.middleware.javalin.metadata.JavalinRouteProviderMetaData;
|
||||||
|
import com.kingsrook.qqq.middleware.javalin.routeproviders.ProcessBasedRouterPayload;
|
||||||
|
import com.kingsrook.qqq.middleware.javalin.routeproviders.authentication.SimpleRouteAuthenticator;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
|
||||||
@ -126,7 +126,7 @@ public class TestUtils
|
|||||||
public static final String SCREEN_0 = "screen0";
|
public static final String SCREEN_0 = "screen0";
|
||||||
public static final String SCREEN_1 = "screen1";
|
public static final String SCREEN_1 = "screen1";
|
||||||
|
|
||||||
public static final String STATIC_SITE_PATH = Paths.get("").toAbsolutePath() + "/static-site";
|
public static final String STATIC_SITE_PATH = "static-site";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -134,12 +134,9 @@ public class TestUtils
|
|||||||
** Prime a test database (e.g., h2, in-memory)
|
** Prime a test database (e.g., h2, in-memory)
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public static void primeTestDatabase() throws Exception
|
public static void primeTestDatabase() throws Exception
|
||||||
{
|
{
|
||||||
ConnectionManager connectionManager = new ConnectionManager();
|
try(Connection connection = ConnectionManager.getConnection(TestUtils.defineDefaultH2Backend()))
|
||||||
|
|
||||||
try(Connection connection = connectionManager.getConnection(TestUtils.defineDefaultH2Backend()))
|
|
||||||
{
|
{
|
||||||
InputStream primeTestDatabaseSqlStream = TestUtils.class.getResourceAsStream("/prime-test-database.sql");
|
InputStream primeTestDatabaseSqlStream = TestUtils.class.getResourceAsStream("/prime-test-database.sql");
|
||||||
assertNotNull(primeTestDatabaseSqlStream);
|
assertNotNull(primeTestDatabaseSqlStream);
|
||||||
@ -161,8 +158,7 @@ public class TestUtils
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public static void runTestSql(String sql, QueryManager.ResultSetProcessor resultSetProcessor) throws Exception
|
public static void runTestSql(String sql, QueryManager.ResultSetProcessor resultSetProcessor) throws Exception
|
||||||
{
|
{
|
||||||
ConnectionManager connectionManager = new ConnectionManager();
|
try(Connection connection = ConnectionManager.getConnection(defineDefaultH2Backend()))
|
||||||
try(Connection connection = connectionManager.getConnection(defineDefaultH2Backend()))
|
|
||||||
{
|
{
|
||||||
QueryManager.executeStatement(connection, sql, resultSetProcessor);
|
QueryManager.executeStatement(connection, sql, resultSetProcessor);
|
||||||
}
|
}
|
||||||
@ -195,17 +191,24 @@ public class TestUtils
|
|||||||
defineWidgets(qInstance);
|
defineWidgets(qInstance);
|
||||||
|
|
||||||
List<JavalinRouteProviderMetaData> routeProviders = new ArrayList<>();
|
List<JavalinRouteProviderMetaData> routeProviders = new ArrayList<>();
|
||||||
if(new File(STATIC_SITE_PATH).exists())
|
routeProviders.add(new JavalinRouteProviderMetaData()
|
||||||
{
|
.withHostedPath("/statically-served")
|
||||||
routeProviders.add(new JavalinRouteProviderMetaData()
|
.withFileSystemPath(STATIC_SITE_PATH));
|
||||||
.withHostedPath("/statically-served")
|
|
||||||
.withFileSystemPath(STATIC_SITE_PATH));
|
routeProviders.add(new JavalinRouteProviderMetaData()
|
||||||
}
|
.withHostedPath("/protected-statically-served")
|
||||||
|
.withFileSystemPath(STATIC_SITE_PATH)
|
||||||
|
.withRouteAuthenticator(new QCodeReference(SimpleRouteAuthenticator.class)));
|
||||||
|
|
||||||
routeProviders.add(new JavalinRouteProviderMetaData()
|
routeProviders.add(new JavalinRouteProviderMetaData()
|
||||||
.withHostedPath("/served-by-process/<pagePath>")
|
.withHostedPath("/served-by-process/<pagePath>")
|
||||||
.withProcessName("routerProcess"));
|
.withProcessName("routerProcess"));
|
||||||
|
|
||||||
|
routeProviders.add(new JavalinRouteProviderMetaData()
|
||||||
|
.withHostedPath("/protected-served-by-process/<pagePath>")
|
||||||
|
.withProcessName("routerProcess")
|
||||||
|
.withRouteAuthenticator(new QCodeReference(SimpleRouteAuthenticator.class)));
|
||||||
|
|
||||||
qInstance.withSupplementalMetaData(new QJavalinMetaData().withRouteProviders(routeProviders));
|
qInstance.withSupplementalMetaData(new QJavalinMetaData().withRouteProviders(routeProviders));
|
||||||
|
|
||||||
qInstance.addBackend(defineMemoryBackend());
|
qInstance.addBackend(defineMemoryBackend());
|
||||||
@ -237,8 +240,10 @@ public class TestUtils
|
|||||||
.withName("step")
|
.withName("step")
|
||||||
.withCode(new QCodeReferenceLambda<BackendStep>((runBackendStepInput, runBackendStepOutput) ->
|
.withCode(new QCodeReferenceLambda<BackendStep>((runBackendStepInput, runBackendStepOutput) ->
|
||||||
{
|
{
|
||||||
String path = runBackendStepInput.getValueString("path");
|
ProcessBasedRouterPayload processPayload = runBackendStepInput.getProcessPayload(ProcessBasedRouterPayload.class);
|
||||||
runBackendStepOutput.addValue("response", "So you've asked for: " + path);
|
String path = processPayload.getPath();
|
||||||
|
processPayload.setResponseString("So you've asked for: " + path);
|
||||||
|
runBackendStepOutput.setProcessPayload(processPayload);
|
||||||
}))
|
}))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -793,7 +798,7 @@ public class TestUtils
|
|||||||
{
|
{
|
||||||
return (new RenderWidgetOutput(new RawHTML("title",
|
return (new RenderWidgetOutput(new RawHTML("title",
|
||||||
QContext.getQSession().getValue(QSession.VALUE_KEY_USER_TIMEZONE_OFFSET_MINUTES)
|
QContext.getQSession().getValue(QSession.VALUE_KEY_USER_TIMEZONE_OFFSET_MINUTES)
|
||||||
+ "|" + QContext.getQSession().getValue(QSession.VALUE_KEY_USER_TIMEZONE)
|
+ "|" + QContext.getQSession().getValue(QSession.VALUE_KEY_USER_TIMEZONE)
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,23 +22,18 @@
|
|||||||
package com.kingsrook.qqq.middleware.javalin;
|
package com.kingsrook.qqq.middleware.javalin;
|
||||||
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.instances.AbstractQQQApplication;
|
import com.kingsrook.qqq.backend.core.instances.AbstractQQQApplication;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
import com.kingsrook.qqq.backend.core.utils.SleepUtils;
|
|
||||||
import com.kingsrook.qqq.backend.javalin.TestUtils;
|
import com.kingsrook.qqq.backend.javalin.TestUtils;
|
||||||
import com.kingsrook.qqq.middleware.javalin.specs.v1.MiddlewareVersionV1;
|
import com.kingsrook.qqq.middleware.javalin.specs.v1.MiddlewareVersionV1;
|
||||||
|
import io.javalin.http.HttpStatus;
|
||||||
import kong.unirest.HttpResponse;
|
import kong.unirest.HttpResponse;
|
||||||
import kong.unirest.Unirest;
|
import kong.unirest.Unirest;
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
@ -55,17 +50,6 @@ class QApplicationJavalinServerTest
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
@BeforeEach
|
|
||||||
void beforeEach() throws IOException
|
|
||||||
{
|
|
||||||
FileUtils.writeStringToFile(new File(TestUtils.STATIC_SITE_PATH + "/foo.html"), "Foo? Bar!", Charset.defaultCharset());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -74,8 +58,6 @@ class QApplicationJavalinServerTest
|
|||||||
{
|
{
|
||||||
javalinServer.stop();
|
javalinServer.stop();
|
||||||
TestApplication.callCount = 0;
|
TestApplication.callCount = 0;
|
||||||
|
|
||||||
FileUtils.deleteDirectory(new File(TestUtils.STATIC_SITE_PATH));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -212,6 +194,35 @@ class QApplicationJavalinServerTest
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testAuthenticatedStaticRouter() throws Exception
|
||||||
|
{
|
||||||
|
javalinServer = new QApplicationJavalinServer(getQqqApplication())
|
||||||
|
.withServeFrontendMaterialDashboard(false)
|
||||||
|
.withPort(PORT);
|
||||||
|
javalinServer.start();
|
||||||
|
|
||||||
|
Unirest.config().setDefaultResponseEncoding("UTF-8")
|
||||||
|
.followRedirects(false);
|
||||||
|
|
||||||
|
HttpResponse<String> response = Unirest.get("http://localhost:" + PORT + "/protected-statically-served/foo.html")
|
||||||
|
.header("Authorization", "Bearer Deny")
|
||||||
|
.asString();
|
||||||
|
|
||||||
|
assertEquals(HttpStatus.FOUND.getCode(), response.getStatus());
|
||||||
|
assertThat(response.getHeaders().getFirst("Location")).contains("createMockSession");
|
||||||
|
|
||||||
|
response = Unirest.get("http://localhost:" + PORT + "/protected-statically-served/foo.html")
|
||||||
|
.asString();
|
||||||
|
assertEquals(HttpStatus.OK.getCode(), response.getStatus());
|
||||||
|
assertEquals("Foo? Bar!", response.getBody());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -230,6 +241,35 @@ class QApplicationJavalinServerTest
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testAuthenticatedProcessRouter() throws Exception
|
||||||
|
{
|
||||||
|
javalinServer = new QApplicationJavalinServer(getQqqApplication())
|
||||||
|
.withServeFrontendMaterialDashboard(false)
|
||||||
|
.withPort(PORT);
|
||||||
|
javalinServer.start();
|
||||||
|
|
||||||
|
Unirest.config().setDefaultResponseEncoding("UTF-8")
|
||||||
|
.followRedirects(false);
|
||||||
|
|
||||||
|
HttpResponse<String> response = Unirest.get("http://localhost:" + PORT + "/protected-served-by-process/foo.html")
|
||||||
|
.header("Authorization", "Bearer Deny")
|
||||||
|
.asString();
|
||||||
|
|
||||||
|
assertEquals(HttpStatus.FOUND.getCode(), response.getStatus());
|
||||||
|
assertThat(response.getHeaders().getFirst("Location")).contains("createMockSession");
|
||||||
|
|
||||||
|
response = Unirest.get("http://localhost:" + PORT + "/protected-statically-served/foo.html")
|
||||||
|
.asString();
|
||||||
|
assertEquals(200, response.getStatus());
|
||||||
|
assertEquals("So you've asked for: /served-by-process/foo.html", response.getBody());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/***************************************************************************
|
/***************************************************************************
|
||||||
**
|
**
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
Foo? Bar!
|
Reference in New Issue
Block a user