Raph,
This part of the log:
No transition found on occurrence of event 'unavailable' in state 'mfa-simple' of flow 'pswdreset'
Are you creating the flow pswdreset or the the event unavailable?
If not, then this would be a bug in cas code.
If you have created a flow or your groovy script is returning the unavailable event, then that would be the place to investigate.
Is it possible that trusted-mfa or captcha features are affecting the flow?
Try to simplify your config.
We have added custom flows into our cas. To help understand what is going on, I created a method that would generate a human readable description of the flow and write to a file (included below in case you are creating custom flows).
Ray
package ca.uvic.idm.cas.web.flow;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.web.flow.configurer.AbstractCasWebflowConfigurer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.webflow.definition.registry.FlowDefinitionRegistry;
import org.springframework.webflow.engine.ActionState;
import org.springframework.webflow.engine.Flow;
import org.springframework.webflow.engine.builder.support.FlowBuilderServices;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.regex.Pattern;
@Slf4j
public abstract class AbstractUvicCasWebflowConfigurer extends AbstractCasWebflowConfigurer {
protected String outputFileName = "/tmp/flow.txt";
public AbstractUvicCasWebflowConfigurer(FlowBuilderServices flowBuilderServices,
FlowDefinitionRegistry loginFlowDefinitionRegistry,
ConfigurableApplicationContext applicationContext,
CasConfigurationProperties casProperties) {
super(flowBuilderServices, loginFlowDefinitionRegistry, applicationContext, casProperties);
}
/**
* Inserts an inbound state into a transition for a target state and sets inbound
* state's matching transition to the previous target (preserving overall flow).
* Inbound state inserts itself into target state by replacing target transition.
* @param flow
* @param inboundStateId bean identifier for insertable action state
* @param inboundActionId action to be performed
* @param targetStateid bean identifier for state immediately before inbound
* @param targetTransitionId transition point for insertion
*/
protected ActionState insertIntoFlow(
final Flow flow,
final String inboundStateId,
final String inboundActionId,
final String targetStateid,
final String targetTransitionId) {
val inboundActionState = createActionState(flow, inboundStateId, inboundActionId);
val targetState = getState(flow, targetStateid, ActionState.class);
val destinationStateId = targetState.getTransition(targetTransitionId).getTargetStateId();
val inboundTransitionSet = inboundActionState.getTransitionSet();
inboundTransitionSet.add(createTransition(targetTransitionId, destinationStateId));
createTransitionForState(targetState, targetTransitionId, inboundStateId, true);
flowToFile(flow);
return inboundActionState;
}
protected void flowToFile(final Flow flow) {
flowToFile(flow, outputFileName);
}
protected void flowToFile(final Flow flow, final String fileName) {
if (LOGGER.isTraceEnabled()) {
String s = flow.toString().trim();
String formatted = formatFlow(s);
try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileName))) {
// writer.write(s);
// writer.write("\n\n\n\n");
writer.write(formatted);
} catch (IOException e) {
LOGGER.error(e.getMessage());
}
}
}
/**
* Formats a spring webflow flow to help determine how to modify a flow.
* Adds new lines and indents to make it easier to read.
* @param input flow.toString()
* @return nicely formatted flow
*/
public String formatFlow(final String input) {
//LOGGER.debug("input: ." + input + ".");
// used to add an extra indent for an object's field members
java.util.Stack<java.util.AbstractMap.SimpleEntry> stack = new java.util.Stack<>();
int currPosition = 0;
String indent = "";
String indentor = "\t";
String newLine = "\n";
// object identifier
java.util.regex.Pattern objPattern = Pattern.compile("^(\\w+@\\w+)\\b.*");
String in = input.trim();
StringBuilder out = new StringBuilder();
while (in.length() > currPosition) {
java.util.regex.Matcher m = objPattern.matcher(in.substring(currPosition));
String firstTwo = "";
// capture first two characters to match against ']' or '],'
if (1 < in.length() - currPosition) {
firstTwo = in.substring(currPosition, currPosition + 2);
} else {
// at end of input
firstTwo = in.substring(currPosition, currPosition + 1);
}
if (in.startsWith("[", currPosition)) {
out.append(indent).append(in.charAt(currPosition)).append(newLine);
indent += indentor;
currPosition++;
if (!stack.empty()) {
java.util.AbstractMap.SimpleEntry<String, Integer> se = stack.pop();
se.setValue(se.getValue() + 1);
stack.push(se);
}
} else if (firstTwo.startsWith("]")) {
if (!stack.empty()) {
java.util.AbstractMap.SimpleEntry<String, Integer> se = stack.pop();
if (1 > se.getValue()) {
// outdent after printing member variables
indent = indent.replaceFirst(indentor, "");
if (!stack.empty()) {
// this ] closes from outer object
java.util.AbstractMap.SimpleEntry<String, Integer> seOuter = stack.pop();
seOuter.setValue(seOuter.getValue() - 1);
stack.push(seOuter);
}
} else {
se.setValue(se.getValue() - 1);
stack.push(se);
}
}
indent = indent.replaceFirst(indentor, "");
out.append(indent).append("]");
if ("],".equals(firstTwo)) {
out.append(",");
currPosition++;
}
out.append(newLine);
currPosition++;
} else if (m.matches()) {
String obj = m.group(1);
out.append(indent).append(obj).append(newLine);
indent = indent + indentor;
// prepare for members
stack.push(new java.util.AbstractMap.SimpleEntry<String, Integer>(obj, 0));
currPosition += obj.length();
} else {
int nextOpenBracket = in.indexOf("[", currPosition);
int nextCloseBracket = in.indexOf("]", currPosition);
int nextComma = in.indexOf(",", currPosition);
int nextMark = 0;
boolean increaseIndent = false;
// if [ or , not found, push beyond last position which would be ]
if (0 > nextOpenBracket) {
nextOpenBracket = in.length();
}
if (0 > nextComma) {
nextComma = in.length();
}
// add 1 when [ and , since they should remain on same line and ] should be on next line
if (nextCloseBracket > nextOpenBracket) {
if (nextOpenBracket > nextComma) {
nextMark = nextComma + 1;
} else {
nextMark = nextOpenBracket + 1;
// bypass empty and null
if ((in.substring(nextMark).startsWith("[empty]]"))
|| (in.substring(nextMark).startsWith("null]"))) {
if (in.substring(nextMark).startsWith("[empty]],")) {
nextMark += 9;
} else if (in.substring(nextMark).startsWith("[empty]]")) {
nextMark += 8;
} else if (in.substring(nextMark).startsWith("null],")) {
nextMark += 6;
} else if (in.substring(nextMark).startsWith("null]")) {
nextMark += 5;
}
} else {
// indent members
increaseIndent = true;
if (!stack.empty()) {
java.util.AbstractMap.SimpleEntry<String, Integer> se = stack.pop();
se.setValue(se.getValue() + 1);
stack.push(se);
}
}
}
} else if (nextCloseBracket > nextComma) {
nextMark = nextComma + 1;
} else {
nextMark = nextCloseBracket;
}
String s = in.substring(currPosition, nextMark).trim();
if (0 < s.length()) {
out.append(indent).append(s).append(newLine);
currPosition = nextMark;
}
if (increaseIndent) {
// for next line
indent = indent + indentor;
}
}
}
String formatted = out.toString().trim();
//LOGGER.debug("formatted: ." + formatted + ".");
return formatted;
}
}