CAS 6.4, Skip MFA for NonInteractiveCredentialsAction

161 views
Skip to first unread message

Yan Zhou

unread,
Dec 16, 2021, 7:54:57 PM12/16/21
to CAS Community
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

unread,
Dec 16, 2021, 8:15:24 PM12/16/21
to cas-...@apereo.org
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.
-- 
Ray Bon
Programmer Analyst
Development Services, University Systems

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.
Reply all
Reply to author
Forward
0 new messages