Logic for mfa-authn-config.xml

Andrew Morgan morgan at orst.edu
Fri Jan 12 12:34:15 EST 2018


On Fri, 12 Jan 2018, Paul B. Henson wrote:

>> From: Andrew Morgan Sent: Wednesday, January 10, 2018 2:39 PM
>>
>> The signal to the IDP that MFA is required is the authnContextClassRef. 
>> This can be sent in the SAML request by the SP or hard-coded in 
>> relying-party.xml on the IDP.
>
> Unless I'm mistaken, this operates under the assumption that an 
> application either doesn't use MFA or must use MFA, which will not 
> satisfy the category of opportunistic use? There is a set of 
> applications which will not fail without MFA for users that do not have 
> it available, but should not allow users that do have MFA available to 
> access them without it.

I was too lazy to give you my own MFA logic originally...  :)

If you want to use MFA for users that have opted-in, you can do an 
attribute looking in the MFA logic to detect this and turn on MFA for that 
specific user.  Otherwise, you rely on the SP to signal its MFA 
requirement or your override in relying-party.xml to "signal" the MFA 
requirement.

Here is my MFA logic in conf/authn/mfa-authn-config.xml:

     <!-- script to see if second factor is required. -->
     <bean id="checkSecondFactor" parent="shibboleth.ContextFunctions.Scripted" factory-method="inlineScript"
         p:customObject-ref="shibboleth.AttributeResolverService">
         <constructor-arg>
             <value>
             <![CDATA[
                 nextFlow = "authn/Duo";

                 logger = Java.type("org.slf4j.LoggerFactory").getLogger("checkSecondFactor");
                 logger.debug('Starting checkSecondFactor');

                 // Setup some environment for later
                 resCtx = input.getSubcontext("net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext", true);
                 usernameLookupStrategyClass = Java.type("net.shibboleth.idp.session.context.navigate.CanonicalUsernameLookupStrategy");
                 usernameLookupStrategy = new usernameLookupStrategyClass();
                 resCtx.setPrincipal(usernameLookupStrategy.apply(input));
                 stringType =  Java.type("net.shibboleth.idp.attribute.StringAttributeValue");

                 authCtx = input.getSubcontext("net.shibboleth.idp.authn.context.AuthenticationContext");
                 mfaCtx = authCtx.getSubcontext("net.shibboleth.idp.authn.context.MultiFactorAuthenticationContext");
                 if (mfaCtx.isAcceptable()) {
                     logger.debug('First factor is acceptable to SP, checking if we should do Duo anyways');

                     // Fetch attribute to check if Duo is required
                     resCtx.getRequestedIdPAttributeNames().add("needs_duo");
                     resCtx.resolveAttributes(custom);
                     needs_duo = resCtx.getResolvedIdPAttributes().get("needs_duo");
                     if (needs_duo == null || (needs_duo != null && ! needs_duo.getValues().contains(new stringType("1")))) {
                         nextFlow = null;
                     }
                 }
                 else {
                     logger.debug('Duo is required by the SP');

                     // if has_duo=0, don't trigger the Duo flow
                     resCtx.getRequestedIdPAttributeNames().add("has_duo");
                     resCtx.resolveAttributes(custom);
                     has_duo = resCtx.getResolvedIdPAttributes().get("has_duo");
                     if (has_duo == null || (has_duo != null && ! has_duo.getValues().contains(new stringType("1")))) {
                         logger.debug('Duo is required but user cannot use Duo');
                         nextFlow = null;
                     }
                 }

                 input.removeSubcontext(resCtx);   // cleanup

                 nextFlow;   // pass control to second factor or end with the first
             ]]>
             </value>
         </constructor-arg>
     </bean>


This MFA logic starts by asking if the previous authentication method 
(Password, in our case) satisfies the SP's request by calling 
mfaCtx.isAcceptable().  If true, we check the needs_duo attribute to see 
if the user has opted-in and we should perform MFA anyways.  If false, 
then the SP requires MFA.  In that case, we check if the user is capable 
of performing MFA by checking if they have opted-in to Duo (the has_duo 
attribute).  We don't want to display the Duo iframe if the user hasn't 
opted-in.  If they can't Duo, then the IDP will return a SAML error to the 
SP.

Here are the "needs_duo" and "has_duo" attributes from 
attribute-resolver.xml:

     <AttributeDefinition xsi:type="Mapped" id="has_duo" sourceAttributeID="ismemberof">
         <Dependency ref="ONIDLDAP" />
         <AttributeEncoder xsi:type="SAML2String" name="has_duo" />
         <ValueMap>
             <ReturnValue>1</ReturnValue>
             <SourceValue ignoreCase="true">cn=duo-opt-in,ou=duo,ou=app,ou=is,ou=org,ou=osu,ou=grouper,ou=groups,o=orst.edu</SourceValue>
         </ValueMap>
     </AttributeDefinition>

     <AttributeDefinition id="needs_duo" xsi:type="ScriptedAttribute">
         <Dependency ref="ONIDLDAP" />
         <Script><![CDATA[
             logger = Java.type("org.slf4j.LoggerFactory").getLogger("net.shibboleth.idp.attribute.resolver.needs_duo");
             duoflag = "0";
             for (i=0; i < ismemberof.getValues().size(); i++) {
                 tmp = ismemberof.getValues().get(i);
                 if (tmp.toLowerCase().equals("cn=duo-opt-in,ou=duo,ou=app,ou=is,ou=org,ou=osu,ou=grouper,ou=groups,o=orst.edu")) {
                     logger.debug("User is opted-in to Duo");
                     duoflag = "1";
                 }
             }
             needs_duo.addValue(duoflag);
             logger.debug("needs_duo final value: " + needs_duo.getValues().get(0));
         ]]></Script>
     </AttributeDefinition>

     <!-- Duo pass-thru -->
     <!-- comment out the needs_duo attribute above and uncomment this attribute to skip Duo authentication -->
<!--
     <AttributeDefinition id="needs_duo" xsi:type="ScriptedAttribute">
         <Script><![CDATA[
             duoflag = "0";
             needs_duo.addValue(duoflag);
         ]]></Script>
     </AttributeDefinition>
-->


has_duo and needs_duo look very similar right now, but originally we were 
testing some additional logic in the needs_duo attribute, such as matching 
on the SP entityID or an additional per-SP-per-user group.  In it's 
current state, these 2 separate attributes could potentially be collapsed 
into one attribute.


I think this solution meets your needs.  MFA if the application requests 
it (or you require it via relying-party.xml) and MFA if the user has 
opted-in.

Thanks,
 	Andy


More information about the users mailing list