Skip to content

Commit

Permalink
feat(agama): add support for parameterizable enter/exit urls in flows (
Browse files Browse the repository at this point in the history
…#10716)

* chore: update "finished" templates #10661

Signed-off-by: jgomer2001 <[email protected]>

* feat: introduce start/end config mapping for flows #10661

Signed-off-by: jgomer2001 <[email protected]>

* feat: add support in engine for parameterizable enter/exit urls #10661

Signed-off-by: jgomer2001 <[email protected]>

* feat: add support in bridge and challenge script for enter urls #10661

Signed-off-by: jgomer2001 <[email protected]>

* docs: remove unused config property #10661

Signed-off-by: jgomer2001 <[email protected]>

* fix: add missing comma #10661

Signed-off-by: jgomer2001 <[email protected]>

---------

Signed-off-by: jgomer2001 <[email protected]>
  • Loading branch information
jgomer2001 authored Jan 23, 2025
1 parent 78457b6 commit 71fbcb7
Show file tree
Hide file tree
Showing 16 changed files with 92 additions and 59 deletions.
4 changes: 2 additions & 2 deletions agama/misc/finished.ftlh
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@

<p class="my-4">Redirecting you...

<form action="${webCtx.contextPath}/postlogin.htm" method="post">
<form action="${webCtx.contextPath}/${data.post_finish_url}" method="post">
<noscript>
<p>Your browser does not seem to support Javascript. Click on the button below to be redirected
<p><input type="submit" class="btn btn-success px-4" value="Continue">
</noscript>
</form>

<script>
function submit() {
document.forms[0].submit()
Expand Down
2 changes: 1 addition & 1 deletion agama/misc/json_finished.ftlh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{
"success": ${success?c},
<#if success>
"finish_post_url": "${webCtx.contextPath}/postlogin.htm"
"post_finish_url": "${webCtx.contextPath}/${data.post_finish_url}"
<#else>
"error": "${jt.escStr(error!"")}"
</#if>
Expand Down
2 changes: 0 additions & 2 deletions docs/janssen-server/developer/agama/engine-bridge-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ The properties of Agama engine configuration are described in the following:

- `finishedFlowPage`: A path relative to `/opt/jans/jetty/jans-auth/agama` containing the location of the page shown when a flow has finished (whether successfully or not) in the phase handled exclusively by the engine. This page features an auto-submitting form that users won't notice in practice. This page will rarely need modifications. Default value is `finished.ftlh`

- `bridgeScriptPage`: This is a facelets (JSF) page the bridge needs for proper operation. This page resides in the authentication server WAR file and will rarely need modifications. Default value is `agama.xhtml`

- `serializeRules`: A JSON object specifying the serialization rules, see below. It is not recommended to remove items from the out-of-the-box rules. Adding items is fine

### Serialization rules
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ private Pair<String, String> prepareFlow(String sessionId, String flowName) {

if (qn != null) {
NativeJansFlowBridge bridge = CdiUtil.bean(NativeJansFlowBridge.class);
Boolean running = bridge.prepareFlow(sessionId, qn, inputs, true);
Boolean running = bridge.prepareFlow(sessionId, qn, inputs, true, null);

if (running == null) {
msg = "Flow " + qn + " does not exist or cannot be launched from an application";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def init(self, customScript, configurationAttributes):
print "Agama. Property '%s' is missing value" % prop
return False

self.startUrl = "agama/agama.xhtml"
print "Agama. DB attribute '%s' will be used to map the identity of userId passed in Finish directives (if any)" % self.finish_userid_db_attr
print "Agama. Initialized successfully"

Expand Down Expand Up @@ -144,7 +145,7 @@ def prepareForStep(self, configurationAttributes, requestParameters, step):

try:
bridge = CdiUtil.bean(NativeJansFlowBridge)
running = bridge.prepareFlow(session.getId(), qn, ins, False)
running = bridge.prepareFlow(session.getId(), qn, ins, False, self.startUrl)

if running == None:
print "Agama. Flow '%s' does not exist or cannot be launched from a browser!" % qn
Expand All @@ -161,7 +162,7 @@ def prepareForStep(self, configurationAttributes, requestParameters, step):
return False
#except java.lang.Throwable, ex:
# ex.printStackTrace()
# return False
# return False
return True

def getExtraParametersForStep(self, configurationAttributes, step):
Expand All @@ -171,8 +172,7 @@ def getCountAuthenticationSteps(self, configurationAttributes):
return 1

def getPageForStep(self, configurationAttributes, step):
# page referenced here is only used when a flow is restarted
return "/" + CdiUtil.bean(NativeJansFlowBridge).scriptPageUrl()
return "/" + self.startUrl

def getNextStep(self, configurationAttributes, requestParameters, step):
return -1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import io.jans.agama.engine.service.WebContext;
import io.jans.agama.engine.servlet.ExecutionServlet;
import io.jans.agama.engine.script.LogUtils;
import io.jans.agama.model.EngineConfig;

import jakarta.inject.Inject;
import jakarta.enterprise.context.RequestScoped;
Expand All @@ -27,32 +26,26 @@ public class NativeJansFlowBridge {
@Inject
private FlowService fs;

@Inject
private EngineConfig conf;

@Inject
private WebContext webContext;

public String scriptPageUrl() {
return conf.getBridgeScriptPage();
}

public String getTriggerUrl() {
return webContext.getContextPath() + ExecutionServlet.URL_PREFIX +
"agama" + ExecutionServlet.URL_SUFFIX;
}

public Boolean prepareFlow(String sessionId, String qname, String jsonInput, boolean nativeClient) throws Exception {
public Boolean prepareFlow(String sessionId, String qname, String jsonInput, boolean nativeClient,
String startUrl) throws Exception {

logger.info("Preparing flow '{}'", qname);
Boolean alreadyRunning = null;
if (aps.flowEnabled(qname)) {

FlowStatus st = aps.getFlowStatus(sessionId);
alreadyRunning = st != null;

if (alreadyRunning && !st.getQname().equals(qname)) {
logger.warn("Flow {} is already running. Will be terminated", st.getQname());
if (alreadyRunning && !qname.equals(st.getQname())) {
logger.warn("Flow {} seems to be already running. Will be terminated", st.getQname());
fs.terminateFlow();
st = null;
}
Expand All @@ -69,6 +62,8 @@ public Boolean prepareFlow(String sessionId, String qname, String jsonInput, boo
st.setJsonInput(jsonInput);
st.setFinishBefore(expireAt);
st.setNativeClient(nativeClient);
st.setStartUrl(startUrl);

aps.createFlowRun(sessionId, st, expireAt);
LogUtils.log("@w Effective timeout for this flow will be % seconds", timeout);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class FlowStatus {
private String externalRedirectUrl;
private boolean allowCallbackResume;
private String jsonInput;
private String startUrl;

private FlowResult result;

Expand Down Expand Up @@ -116,4 +117,12 @@ public void setJsonInput(String jsonInput) {
this.jsonInput = jsonInput;
}

public String getStartUrl() {
return startUrl;
}

public void setStartUrl(String startUrl) {
this.startUrl = startUrl;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ public void finishFlow(String sessionId, FlowResult result) throws IOException {
status.setTemplatePath(null);
status.setTemplateDataModel(null);
status.setExternalRedirectUrl(null);
status.setStartUrl(null);

run.setEncodedContinuation(null);
run.setHash(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.HttpMethod;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

import io.jans.agama.engine.exception.FlowCrashException;
Expand All @@ -26,6 +27,8 @@ public class ExecutionServlet extends BaseServlet {
public static final String URL_PREFIX = "/fl/";
public static final String CALLBACK_PATH = URL_PREFIX + "callback";
public static final String ABORT_PARAM = "_abort";

private static final String POST_FINISH_URL_KEY = "post_finish_url";

private static final String NO_ACTIVE_FLOW = "This flow may have already finished/timed out. " +
"Login again to the website you were trying to access originally.";
Expand Down Expand Up @@ -55,7 +58,7 @@ public void doGet(HttpServletRequest request, HttpServletResponse response)
if (result == null) {
sendRedirect(response, request.getContextPath(), fstatus, true);
} else {
sendFinishPage(response, result);
sendFinishPage(response, result, fstatus.isNativeClient() ? null : fstatus.getStartUrl());
}
} catch (FlowCrashException e) {
logger.error(e.getMessage(), e);
Expand Down Expand Up @@ -156,7 +159,7 @@ private void continueFlow(HttpServletResponse response, FlowStatus fstatus, bool
sendRedirect(response, request.getContextPath(), fstatus,
request.getMethod().equals(HttpMethod.GET));
} else {
sendFinishPage(response, result);
sendFinishPage(response, result, fstatus.isNativeClient() ? null : fstatus.getStartUrl());
}

} catch (FlowTimeoutException te) {
Expand Down Expand Up @@ -204,13 +207,26 @@ private void sendRedirect(HttpServletResponse response, String contextPath, Flow
response.setHeader(HttpHeaders.LOCATION, newLocation);
response.setStatus(HttpServletResponse.SC_SEE_OTHER);
}

}

private void sendFinishPage(HttpServletResponse response, FlowResult result) throws IOException {
private void sendFinishPage(HttpServletResponse response, FlowResult result,
String startUrl) throws IOException {

String fpage = isJsonRequest() ? engineConf.getJsonFinishedFlowPage() :
engineConf.getFinishedFlowPage();

if (startUrl != null) {
Map<String, Object> map = Collections.singletonMap(POST_FINISH_URL_KEY,
engineConf.getStartEndUrlMapping().get(startUrl));

if (result.getData() == null) {
result.setData(map);
} else {
result.getData().putAll(map);
}
}

page.setTemplatePath(fpage);
page.setDataModel(result);
sendPageContents(response);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,27 +37,31 @@ public void doPost(HttpServletRequest request, HttpServletResponse response)
try {
FlowStatus st = flowService.getRunningFlowStatus();

if (st == null || st.getStartedAt() == FlowStatus.FINISHED) {
//If flow exists, ensure it is not about to be collected by cleaner job
if (st == null || st.getStartedAt() == FlowStatus.FINISHED)
throw new IOException("No flow to restart");
} else {

try {
flowService.ensureTimeNotExceeded(st);
flowService.terminateFlow();

logger.debug("Sending user's browser for a flow start");
//This redirection relies on the (unauthenticated) session id being still alive
//(so the flow name and its inputs can be remembered in the bridge script)
//see AgamaBridge.py#prepareForStep
String url = bridge.scriptPageUrl().replaceFirst("\\.xhtml", ".htm");
response.sendRedirect(request.getContextPath() + "/" + url);
try {
//If flow exists, ensure it is not about to be collected by cleaner job
flowService.ensureTimeNotExceeded(st);
flowService.terminateFlow();
logger.debug("Sending user's browser for a flow start");

} catch (FlowTimeoutException e) {
sendFlowTimeout(response, e.getMessage());
//This redirection relies on the (unauthenticated) sessionId being still alive
//(so the flow name and its inputs can be remembered in the script). See
//for instance AgamaBridge.py#prepareForStep or AgamaConsent.py#prepareForStep
String url = st.getStartUrl();
if (url == null) {
logger.warn("Check the Engine's config startEndUrlMapping");
throw new IOException("Unknown end url for " + url);
}


url = url.replaceFirst("\\.xhtml", ".htm");
response.sendRedirect(request.getContextPath() + "/" + url);

} catch (FlowTimeoutException e) {
sendFlowTimeout(response, e.getMessage());
}

} catch (IOException e) {
sendFlowCrashed(response, e.getMessage());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,12 @@ public class EngineConfig {
//transpiled code hash verification. Boolean preferred over boolean because it helps to keep the property "hidden"
private Boolean disableTCHV;

private String pageMismatchErrorPage = "mismatch.ftl";
private String interruptionErrorPage = "timeout.ftl";
private String crashErrorPage = "crash.ftl";
private String finishedFlowPage = "finished.ftl";

//relative to https://.../jans-auth
private String bridgeScriptPage = "agama.xhtml";
private String pageMismatchErrorPage;
private String interruptionErrorPage;
private String crashErrorPage;
private String finishedFlowPage;

private Map<String, String> startEndUrlMapping;
private Map<String, List<String>> serializeRules;

private Map<String, String> defaultResponseHeaders = Map.of(
Expand Down Expand Up @@ -126,22 +124,22 @@ public void setFinishedFlowPage(String finishedFlowPage) {
this.finishedFlowPage = finishedFlowPage;
}

public String getBridgeScriptPage() {
return bridgeScriptPage;
}

public void setBridgeScriptPage(String bridgeScriptPage) {
this.bridgeScriptPage = bridgeScriptPage;
}

public Map<String, String> getDefaultResponseHeaders() {
return defaultResponseHeaders;
}

public void setDefaultResponseHeaders(Map<String, String> defaultResponseHeaders) {
this.defaultResponseHeaders = defaultResponseHeaders;
}

public Map<String, String> getStartEndUrlMapping() {
return startEndUrlMapping;
}

public void setStartEndUrlMapping(Map<String, String> startEndUrlMapping) {
this.startEndUrlMapping = startEndUrlMapping;
}

public Map<String, List<String>> getSerializeRules() {
return serializeRules;
}
Expand Down
2 changes: 1 addition & 1 deletion jans-auth-server/server/conf/jans-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,7 @@
"interruptionErrorPage": "timeout.ftlh",
"crashErrorPage": "crash.ftlh",
"finishedFlowPage": "finished.ftlh",
"bridgeScriptPage": "agama.xhtml",
"startEndUrlMapping": { "agama/agama.xhtml": "postlogin.htm", "agama/consent.xhtml": "agama/consent_authorize.htm" },
"defaultResponseHeaders": {
"Cache-Control": "max-age=0, no-store"
},
Expand Down
6 changes: 6 additions & 0 deletions jans-auth-server/server/src/main/webapp/agama/consent.xhtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<f:view transient="true" xmlns:f="http://xmlns.jcp.org/jsf/core">
<f:metadata>
<f:viewAction action="#{consentGatherer.prepareForStep}" />
</f:metadata>
</f:view>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<f:view transient="true" xmlns:f="http://xmlns.jcp.org/jsf/core">
<f:metadata>
<f:viewAction action="#{consentGatherer.authorize}" />
</f:metadata>
</f:view>
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,7 @@
"interruptionErrorPage": "timeout.ftlh",
"crashErrorPage": "crash.ftlh",
"finishedFlowPage": "finished.ftlh",
"bridgeScriptPage": "agama.xhtml",
"startEndUrlMapping": { "agama/agama.xhtml": "postlogin.htm", "agama/consent.xhtml": "agama/consent_authorize.htm" },
"defaultResponseHeaders": {
"Cache-Control": "max-age=0, no-store"
},
Expand Down

0 comments on commit 71fbcb7

Please sign in to comment.