-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Streamline Flow execution for multiple Duo providers #3498
Conversation
I think this looks excellent. One comment here is that the call that puts the provider into the webflow scope is fairly generic; while duo is the only provider at this point that does in fact make use of it. It seems like either we need to move the webflow calls into the duo module and not expose generic static methods that would have one believe the API can be used to fetch a provider regardless of the module used for mfa, or we should take a pass at other providers and ensure we stuff a provider into the webflow scope for all of them. I do like the second option myself. |
Are we able to set active mfa provider here: SelectiveAuthenticationProviderWebflowEventEventResolver? Then would we not need "duoInitializeLoginAction"? |
Yes that should work. That's the last resolver in the chain that always executes, and in fact already puts the resolved provider ids into the flow, which should make those static methods unnecessary too! You can get the provider ids that are resolved, and fetch the provider instances from the context in the right spot. One thing that does require testing is multiple instances of duo. I believe if you have more than one defined, then the resolved provider ids that are put into the conversation-scope there include all duo ids. If that is true, then we are golden here; refactor and remove static method calls and simply use the resolved providers. |
Got a handle on this late in the day working with multiple Duo instances. I will try and finish up tomorrow morning and get an update committed. |
@@ -60,11 +62,14 @@ | |||
LOGGER.debug("No multifactor authentication providers are configured"); | |||
return Pair.of(Boolean.FALSE, Optional.empty()); | |||
} | |||
final Optional<MultifactorAuthenticationProvider> requestedProvider = locateRequestedProvider(providerMap.values(), requestedContext); | |||
final Collection<MultifactorAuthenticationProvider> flattenedProviders = MultifactorAuthenticationUtils.flattenProviders(providerMap.values()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needed to unwrap providers if Variegated Provider is found.
@@ -137,9 +143,9 @@ | |||
LOGGER.debug("No authentication context could be determined based on authentication attribute [{}]", this.authenticationContextAttribute); | |||
return null; | |||
} | |||
contexts.forEach(context -> providers.removeIf(provider -> !provider.getId().equals(context))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would fail when the context contained more than one provider.
private final String authenticationContextAttribute; | ||
|
||
private final DuoAuthenticationHandler authenticationHandler; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use the id of the specific provider as credential metadata so it can be matched correctly in ContextValidator.
Latest commits add supporting multiple instances of Duo. The final resolved provider is decided in multiple places. If SelectiveAuthenticationProviderWebflowEventEventResolver returns multiple events then the decision is passed on to the MultfactorFactorProviderSelector that is configured. For this reason I kept the addition of adding the resoloved provider into the webflow conversation scope. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If SelectiveAuthenticationProviderWebflowEventEventResolver returns multiple events then the decision is passed on to the MultfactorFactorProviderSelector that is configured.
Could you explain this a bit more? I am not sure I follow here.
if (!requestedProvider.isPresent()) { | ||
LOGGER.debug("Requested authentication provider cannot be recognized."); | ||
return Pair.of(Boolean.FALSE, Optional.empty()); | ||
} | ||
LOGGER.debug("RequestedContext is [{}] and Available Contexts are [{}]", requestedContext, contexts); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LOGGER.debug("Requested authentication context is [{}] and available contexts are [{}]", requestedContext, contexts);
throw new IllegalArgumentException("No multifactor providers are found in the current request context"); | ||
} | ||
final MultifactorAuthenticationProvider pr = providers.iterator().next(); | ||
final MultifactorAuthenticationProvider pr = WebUtils.getActiveMultifactorAuthenticationProvider(requestContext); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good starting point, but at some point, we should get this cleaned up. A handler component should not try to reach beyond its limits and deal with webflow scopes. We can clean up this bit later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe I was able to clear this up by not using VariegatedMFA Provider and registering multiple authentication handlers into the application context with their specific provider passed to each in the constructor. Submitting it this way for now to limit the scope of changes for a single PR.
* | ||
* @return - the provider id | ||
*/ | ||
public String getProviderId() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless you mean to make the method private of sorts, it would be better if you actually returned the provider instance, and renamed the method to reflect that bit too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Used currently in DuoAuthenticationMetadaPopulator. could be made protected and still work for the current solution.
} | ||
|
||
@RefreshScope | ||
@Bean | ||
public AuthenticationHandler duoAuthenticationHandler() { | ||
public DuoAuthenticationHandler duoAuthenticationHandler() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand why you had to do this, but this needs to be re-worked. We should try to stick to the most generic type possible, and introduce things that can be polymorphically recognized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, there are probably better ways to handle, and made easier if some other decisions are made like dropping VariegatedMFAProvider.
I think the scenario is mfa-duo-1 is resolved as the global MFA Provider, but the service is MFA policy resolves to mfa-duo-2. Then both are returned from Selective resolver and the decision is then made by the configured MultifactorProviderSelector for the MFA flow. There are currently two instances: RankedMFAProviderSelector and GroovyMFAProviderSelector. |
Right. So how about we simply move the |
If Selective resolves to only one provider, then the method in CAS resolver
where the Selector is executed is not called. So we would need to put the
webutils put call in multiple places. I am working on a different approach
that would solve this.
…On Tue, Sep 4, 2018, 12:15 PM Misagh Moayyed ***@***.***> wrote:
Then both are returned from Selective resolver and the decision is then
made by the configured MultifactorProviderSelector for the MFA flow.
Right. So how about we simply move the
putActiveMultifactorAuthenticationProvider right after the selector picks
a provider? That way, the behavior applies to all MFA providers, and isn't
just a feature of duo.
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#3498 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AS0kvVLlCWt31Pto_38t2B_0tTWFpbK1ks5uXtFOgaJpZM4WSUPR>
.
|
That makes sense. Thanks! |
The latest commit removes the use of the Variegated provider from Duo in favor of registering a Bean for each provider created in to the ApplicationContext. This also shifts the resolving of handling and providers to a place where other MFA providers can use the same configuration to offer multiple instances. Key additions and changes are:
|
Remove activeMultifactorProvider form WebUtils
Nice. A few comments here:
Do we need to remove the Variegated provider API altogether then, if we have no use for it?
I am guessing that these two need to be implemented by all credentials and providers for MFA, and not just duo, right?
How does this point in the webflow deal with this? Does it impact the REST API when MFA is attempted? Also I have not looked too closely yet but we should be mindful of the scope here and how it might fit into a patch release. Are there any breaking changes, configuration or API-wise? |
@@ -273,4 +274,9 @@ public Action serviceWarningAction() { | |||
return new ServiceWarningAction(centralAuthenticationService, authenticationSystemSupport, | |||
ticketRegistrySupport, warnCookieGenerator.getIfAvailable(), principalElectionStrategy); | |||
} | |||
|
|||
@Bean | |||
public Action mfaInitializeAction() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also not sure if this belongs here. Shouldn't the MFA-related actions, etc be housed in a module that is only required for MFA, and not by default?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we do the same with cas-sever-core-webflow-api, that has MFA implementations? Create cas-server-core-webflow-mfa and cas-server-core-weblfow-mfa-api and refactor all MFA classes to these two?
* @author Travis Schmidt | ||
* @since 5.3.4 | ||
*/ | ||
public class MfaInitializeAction extends AbstractAction { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above, really. This probably needs to move into some sort of common MFA module.
@Override | ||
protected Event doExecute(final RequestContext context) throws Exception { | ||
final String activeFlow = context.getActiveFlow().getId(); | ||
final MultifactorAuthenticationProvider provider = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does need to move into MFA Utils (I think there is a similar call in there already)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will look into this along with the entire MFA refactor, MFAUtils not currently in the build for this module.
* | ||
* @return - the provider id | ||
*/ | ||
String getProviderId(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be renamed to be more explicit because Credential in master presents a property for source
and it would be confusing to have both source and providerId in the same class. Something slightly more verbose like MultifactorProviderId would do, I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good
@@ -23,20 +24,33 @@ | |||
@NoArgsConstructor | |||
@AllArgsConstructor | |||
@EqualsAndHashCode(of = {"username"}) | |||
public class DuoCredential implements Credential { | |||
public class DuoCredential implements Credential, MfaCredential { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As noted early, this needs to be implemented by every form of MFA credential and not just Duo.
|| credential instanceof DuoDirectCredential; | ||
return (DuoCredential.class.isAssignableFrom(credential.getClass()) | ||
|| credential instanceof DuoDirectCredential) | ||
&& supports((MfaCredential) credential); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is supports((MfaCredential) credential);
a recursive call that would cause an overflow issue?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not that I found in my testing. It calls the overloaded default method in MfaAuthenticationHandler interface.
public class DuoAuthenticationMetaDataPopulator extends BaseAuthenticationMetaDataPopulator { | ||
private final String authenticationContextAttribute; | ||
|
||
private final DuoAuthenticationHandler authenticationHandler; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this be refactored now to use a more generic type? Since you're only adding a name here? And is this not a duplicate of AuthenticationContextAttributeMetaDataPopulator
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it has been refactored into a duplicate of AuthenticationContextAttributeMetaDataPopulator as I worked through the changes. I will remove it.
throw new IllegalArgumentException("At least one Duo instance must be defined"); | ||
} | ||
return provider; | ||
public DefaultDuoMultifactorAuthenticationProvider duoMultifactorAuthenticationProviders(final DuoSecurityMultifactorProperties duo) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Switch the type to DuoMultifactorAuthenticationProvider
? Make the method private?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
duoP.setBypassEvaluator(MultifactorAuthenticationUtils.newMultifactorAuthenticationProviderBypass(duo.getBypass())); | ||
duoP.setOrder(duo.getRank()); | ||
duoP.setId(duo.getId()); | ||
applicationContext.getBeanFactory().registerSingleton(duoP.getId()+"-provider", duoP); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use ApplicationContextProvider.registerBeanIntoApplicationContext
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't find that method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might have been looking at master. never mind this :)
} | ||
|
||
@RefreshScope | ||
@Bean | ||
public AuthenticationHandler duoAuthenticationHandler() { | ||
public Collection<DuoAuthenticationHandler> duoAuthenticationHandler() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Collection<AuthenticationHandler>
. Always stick to the more generic type, where possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
Duo was the only authn method currently using it. If this approach is preferred then I believe it can be removed if we see no future use for it.
It would need to be implemented by all MFA providers that are able to be configure multiple instances of itself. So far that is only Duo. If it makes no sense to provide multiple instances of provider or the provider just can't support it then there is no need to implement these interfaces
So non browser Duo use should work as it is currently coded. The REST API, I will need to look into further. For breaking changes, if we only do this for Duo in the current releases then there are no breaking changes, that I foresee. Duo configuration already supports defining multiple instances and that is the only breaking change I see if we added to other MFA providers(i.e. authy[0] is not supported). Scope wise, only applying this PR to Duo still fits within expected behavior of current releases, and only adds the functionality that rank order among Duo instances is now respected and guaranteed. Applying this pattern to other MFA providers should be handled in future releases, but this would give someone a template to use if they needed to implement this behavior locally. |
All sounds good.
|
Latest commit address comments in the last review. I was not able to find any affect on the REST API, as far as I can tell. |
Looks like all things pass here. Thanks very much! Excellent work. Did you want to handle the master changeset as well? Leave that up to me? Both? :) |
I am starting the master refactor now, waiting for IDE gradle refresh, Pushing one more commit to fix Codacy issues |
Includes multi-duo refactor from PR #3498
1 similar comment
Probably related to #3507 |
* Refactor to separate MFA webflows from initial flows Includes multi-duo refactor from PR #3498 * Typo fix Refactor to prevent NoSuchElementException * Ported MFA refactor changes from PR #3518 for 5.3.x * Refactor dynamic providers to config and made sure refresh scope works * revert webapp.gradle * Refactored the MultifactorAuthenticationProvider creation * Changed mfa webflow configurer to key off of start state * Added AbstractMultifactorAuthentication Refactored AuthententicationContextAttributeMetaDataPopulator * Added Mark to identify credentials added by provider Set providerId into flowscope to look up provider * Fix Tests * Fix Tests * api-mfa refactor namoing refactor * fix case of single provider * fix webflow mfa test * fix grouper mfa test * fix openid test * Refactored MultifactorAuthenticationCredential * Refactored MultifactorAuthenticationCredential DirectCredential * updated mfa documentation * Removed Variegated provider Ported forward changes from 5.3.4 * Fix DuoAuthenticationHandler supports * Fix codacy issue * Fix webflow mfa test * Fix for validation tests * try to fix ci build * Fix surrogate mfa tests * Fix checkstyle * fix spot bugs * Add mfa to tests * Fix for multivalue auth atributes
This PR refactors the Duo MFA webflow to determine the provider in an initial action that is added to the conversation scope. Subsequent actions then only need to pull the provider from the scope instead of locating it in the application context.