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