Yan, Below are a couple methods I use to see the flow.
Ray protected void flowToFile(final Flow flow, final String fileName) { 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; } On Thu, 2021-12-16 at 16:54 -0800, Yan Zhou wrote: Notice: This message was sent from outside the University of Victoria email system. Please be cautious with links and sensitive information. Hi there, CAS 6.4.x. we have global MFA turned on for all requests, but we want our SSO traffic to skip MFA. I run into problem with CAS looking for simple-mfa during our SSO login flow. I followed the CAS' source on token authentication, but has not found a solution. The following are some info. Thanks in advance! cas.properties: cas.authn.mfa.triggers.global.global-provider-id=mfa-simple cas.authn.mfa.simple.name=mfa-simple cas.authn.mfa.simple.order=1 service json: "multifactorPolicy" : { "@class" : "org.apereo.cas.services.DefaultRegisteredServiceMultifactorPolicy", "bypassPrincipalAttributeName": "questSkipMFA" } we have a separate SSO authenticationHandler that will set principal attribute, so that MFA module will know to skip MFA. this is my SSO webflow, once SSO passes, we issue TGT, and authN completes. public class SsoLoginWebflowConfigurer extends AbstractCasWebflowConfigurer { } @Override protected void doInitialize() { val flow = getLoginFlow(); if (flow != null) { val state = getState(flow, CasWebflowConstants.STATE_ID_INIT_LOGIN_FORM, ActionState.class); createTransitionForState(state, TRANSITION_ID_SSO_AUTHENTICATION_CHECK, STATE_ID_SSO_AUTHENTICATION_CHECK); val actionState = createActionState(flow, STATE_ID_SSO_AUTHENTICATION_CHECK, createEvaluateAction("oktaSamlNonInteractiveCredentialsAction")); createTransitionForState(actionState, CasWebflowConstants.TRANSITION_ID_ERROR, "lsmSAMLFailed"); val lsmSamlFailed = createViewState(flow, "lsmSAMLFailed", "error/casLsmTokenErrorView"); createStateDefaultTransition(lsmSamlFailed, "viewLoginForm"); createTransitionForState(actionState, CasWebflowConstants.TRANSITION_ID_SUCCESS, CasWebflowConstants.STATE_ID_CREATE_TICKET_GRANTING_TICKET); ......... here is the error I get. I looks like CAS is looking for mfa-simple state (probably because I have globally turned on MFA). How can I append the mfa-simple flow into this flow definition? And when I do so, I assume it will note the attribute and skip the actual mfa flow? 2021-12-17 00:42:17,828 DEBUG [org.apereo.cas.authentication.mfa.trigger.GlobalMultifactorAuthenticationTrigger] - <Attempting to globally activate [mfa-simple]> 2021-12-17 00:42:17,832 DEBUG [org.apereo.cas.authentication.mfa.trigger.GlobalMultifactorAuthenticationTrigger] - <Resolved single multifactor provider [AbstractMultifactorAuthenticationProvider(bypassEvaluator=org.apereo.cas.authentication.bypass.DefaultChainingMultifactorAuthenticationBypassProvider@673afa7f, failureModeEvaluator=org.apereo.cas.authentication.DefaultMultifactorAuthenticationFailureModeEvaluator@48b482d7, failureMode=CLOSED, id=mfa-simple, order=0)]> 2021-12-17 00:42:17,832 TRACE [org.apereo.cas.authentication.MultifactorAuthenticationUtils] - <Attempting to find a matching transition for event id [mfa-simple]> 2021-12-17 00:42:17,833 TRACE [org.apereo.cas.authentication.MultifactorAuthenticationUtils] - <Reviewing current state [[ActionState@64f89202 id = 'oktaSamlSSONonInteractiveCredentials', flow = 'login', entryActionList = list[[empty]], exceptionHandlerSet = list[[empty]], actionList = list[[EvaluateAction@71a47cf2 expression = oktaSamlNonInteractiveCredentialsAction, resultExpression = [null]]], transitions = list[[Transition@36169b00 on = success, to = realSubmitSamlSSO], [Transition@fe2f399 on = error, to = lsmSAMLFailed]], exitActionList = list[[empty]]]], event [oktaSAML] and transition [[Transition@37398daa on = oktaSAML, to = oktaSamlSSONonInteractiveCredentials]]> 2021-12-17 00:42:17,834 ERROR [org.apereo.cas.authentication.MultifactorAuthenticationUtils] - <State [oktaSamlSSONonInteractiveCredentials:oktaSAML:oktaSAML] does not have a matching transition for mfa-simple> 2021-12-17 00:42:17,836 DEBUG [org.apereo.cas.web.flow.resolver.impl.DefaultCasDelegatingWebflowEventResolver] - <State [oktaSamlSSONonInteractiveCredentials:oktaSAML:oktaSAML] does not have a matching transition for mfa-simple> == end == Yan -- Ray Bon Programmer Analyst Development Services, University Systems 2507218831 | CLE 019 | r...@uvic.ca<mailto:r...@uvic.ca> I acknowledge and respect the lək̓ʷəŋən peoples on whose traditional territory the university stands, and the Songhees, Esquimalt and WSÁNEĆ peoples whose historical relationships with the land continue to this day. -- - Website: https://apereo.github.io/cas - Gitter Chatroom: https://gitter.im/apereo/cas - List Guidelines: https://goo.gl/1VRrw7 - Contributions: https://goo.gl/mh7qDG --- You received this message because you are subscribed to the Google Groups "CAS Community" group. To unsubscribe from this group and stop receiving emails from it, send an email to cas-user+unsubscr...@apereo.org. To view this discussion on the web visit https://groups.google.com/a/apereo.org/d/msgid/cas-user/5babbc81a072f10783fca2af8797ca7e69209639.camel%40uvic.ca.