From 4db1771bfd08fe349ce082131c4113d8ebcef2c5 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Tue, 16 Jan 2018 16:17:17 -0800 Subject: [PATCH] Support event ordering for multi factor authentication [#153527760] https://www.pivotaltracker.com/story/show/153527760 Breaking Change: UserAuthenticationSuccessEvent only happens after all auth steps have been completed - All providers publish a IdentityProviderAuthenticationSuccessEvent upon successful authentication - UserAuthenticationSuccessEvent happens after MFA if MFA is enabled, or at the same time as IdentityProviderAuthenticationSuccessEvent if MFA is disabled --- .../identity/uaa/audit/AuditEventType.java | 2 +- .../event/AbstractUaaAuthenticationEvent.java | 4 +- ...ityProviderAuthenticationSuccessEvent.java | 40 +++++ .../PasswordAuthenticationSuccessEvent.java | 27 --- .../AuthenticationSuccessListener.java | 88 ++++++++++ .../UserAuthenticationSuccessListener.java | 40 ----- .../manager/AuthzAuthenticationManager.java | 18 +- .../ExternalLoginAuthenticationManager.java | 23 +-- .../manager/LoginAuthenticationManager.java | 18 +- .../identity/uaa/mfa/MfaChecker.java | 29 ++++ .../identity/uaa/mfa/MfaRequiredFilter.java | 14 +- .../saml/LoginSamlAuthenticationProvider.java | 35 ++-- server/src/main/resources/spring/login-ui.xml | 5 + .../AuthenticationSuccessListenerTests.java | 160 ++++++++++++++++++ ...serAuthenticationSuccessListenerTests.java | 120 ------------- .../AuthzAuthenticationManagerTests.java | 28 ++- ...xternalLoginAuthenticationManagerTest.java | 15 +- .../LoginAuthenticationManagerTests.java | 19 ++- .../identity/uaa/mfa/MfaCheckerTests.java | 52 ++++++ .../uaa/mfa/MfaRequiredFilterTests.java | 19 +-- .../LoginSamlAuthenticationProviderTests.java | 25 +-- .../main/webapp/WEB-INF/spring-servlet.xml | 3 +- .../mock/audit/AuditCheckMockMvcTests.java | 38 +++-- .../identity/uaa/mock/util/MockMvcUtils.java | 11 ++ 24 files changed, 514 insertions(+), 319 deletions(-) create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/IdentityProviderAuthenticationSuccessEvent.java delete mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/PasswordAuthenticationSuccessEvent.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/authentication/listener/AuthenticationSuccessListener.java delete mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/authentication/listener/UserAuthenticationSuccessListener.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaChecker.java create mode 100644 server/src/test/java/org/cloudfoundry/identity/uaa/authentication/listener/AuthenticationSuccessListenerTests.java delete mode 100644 server/src/test/java/org/cloudfoundry/identity/uaa/authentication/listener/UserAuthenticationSuccessListenerTests.java create mode 100644 server/src/test/java/org/cloudfoundry/identity/uaa/mfa/MfaCheckerTests.java diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/audit/AuditEventType.java b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/AuditEventType.java index e38c5ce256f..f2d6db171a2 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/audit/AuditEventType.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/AuditEventType.java @@ -58,7 +58,7 @@ public enum AuditEventType { ServiceProviderModifiedEvent(34), UserAccountUnlockedEvent(35), TokenRevocationEvent(36), - PasswordAuthenticationSuccess(37), + IdentityProviderAuthenticationSuccess(37), PasswordAuthenticationFailure(38), MfaAuthenticationSuccess(39); diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/AbstractUaaAuthenticationEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/AbstractUaaAuthenticationEvent.java index c2b810c033f..e98cdb21ea2 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/AbstractUaaAuthenticationEvent.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/AbstractUaaAuthenticationEvent.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Cloud Foundry + * Cloud Foundry * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). @@ -19,7 +19,7 @@ /** * @author Luke Taylor */ -abstract class AbstractUaaAuthenticationEvent extends AbstractUaaEvent { +public abstract class AbstractUaaAuthenticationEvent extends AbstractUaaEvent { AbstractUaaAuthenticationEvent(Authentication authentication) { super(authentication); diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/IdentityProviderAuthenticationSuccessEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/IdentityProviderAuthenticationSuccessEvent.java new file mode 100644 index 00000000000..4ec2497a66e --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/IdentityProviderAuthenticationSuccessEvent.java @@ -0,0 +1,40 @@ +/* + * Cloud Foundry + * Copyright (c) [2009-2018] Pivotal Software, Inc. All Rights Reserved. + *

+ * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + *

+ * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file + */ + +package org.cloudfoundry.identity.uaa.authentication.event; + +import org.cloudfoundry.identity.uaa.audit.AuditEvent; +import org.cloudfoundry.identity.uaa.audit.AuditEventType; +import org.cloudfoundry.identity.uaa.user.UaaUser; +import org.springframework.security.core.Authentication; +import org.springframework.util.Assert; + +public class IdentityProviderAuthenticationSuccessEvent extends AbstractUaaAuthenticationEvent { + private final UaaUser user; + + public IdentityProviderAuthenticationSuccessEvent(UaaUser user, Authentication authentication) { + super(authentication); + this.user = user; + } + + @Override + public AuditEvent getAuditEvent() { + Assert.notNull(user, "UaaUser cannot be null"); + return createAuditRecord(user.getId(), AuditEventType.IdentityProviderAuthenticationSuccess, + getOrigin(getAuthenticationDetails()), user.getUsername()); + } + + public UaaUser getUser() { + return user; + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/PasswordAuthenticationSuccessEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/PasswordAuthenticationSuccessEvent.java deleted file mode 100644 index 2b9d0cd88db..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/PasswordAuthenticationSuccessEvent.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.cloudfoundry.identity.uaa.authentication.event; - -import org.cloudfoundry.identity.uaa.audit.AuditEvent; -import org.cloudfoundry.identity.uaa.audit.AuditEventType; -import org.cloudfoundry.identity.uaa.user.UaaUser; -import org.springframework.security.core.Authentication; -import org.springframework.util.Assert; - -public class PasswordAuthenticationSuccessEvent extends AbstractUaaAuthenticationEvent { - private final UaaUser user; - - public PasswordAuthenticationSuccessEvent(UaaUser user, Authentication authentication) { - super(authentication); - this.user = user; - } - - @Override - public AuditEvent getAuditEvent() { - Assert.notNull(user, "UaaUser cannot be null"); - return createAuditRecord(user.getId(), AuditEventType.PasswordAuthenticationSuccess, - getOrigin(getAuthenticationDetails()), user.getUsername()); - } - - public UaaUser getUser() { - return user; - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/listener/AuthenticationSuccessListener.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/listener/AuthenticationSuccessListener.java new file mode 100644 index 00000000000..d317c5083e3 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/listener/AuthenticationSuccessListener.java @@ -0,0 +1,88 @@ +/* + * Cloud Foundry + * Copyright (c) [2009-2018] Pivotal Software, Inc. All Rights Reserved. + *

+ * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + *

+ * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file + */ + +package org.cloudfoundry.identity.uaa.authentication.listener; + +import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; +import org.cloudfoundry.identity.uaa.authentication.event.AbstractUaaAuthenticationEvent; +import org.cloudfoundry.identity.uaa.authentication.event.IdentityProviderAuthenticationSuccessEvent; +import org.cloudfoundry.identity.uaa.authentication.event.MfaAuthenticationSuccessEvent; +import org.cloudfoundry.identity.uaa.authentication.event.UserAuthenticationSuccessEvent; +import org.cloudfoundry.identity.uaa.mfa.MfaChecker; +import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; +import org.cloudfoundry.identity.uaa.user.UaaUser; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; + +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.context.ApplicationListener; +import org.springframework.security.core.Authentication; + +public class AuthenticationSuccessListener implements ApplicationListener, ApplicationEventPublisherAware { + + private final ScimUserProvisioning scimUserProvisioning; + private final MfaChecker checker; + private ApplicationEventPublisher publisher; + + public AuthenticationSuccessListener(ScimUserProvisioning scimUserProvisioning, + MfaChecker checker) { + this.scimUserProvisioning = scimUserProvisioning; + this.checker = checker; + } + + @Override + public void onApplicationEvent(AbstractUaaAuthenticationEvent event) { + if (event instanceof UserAuthenticationSuccessEvent) { + onApplicationEvent((UserAuthenticationSuccessEvent) event); + } else if (event instanceof IdentityProviderAuthenticationSuccessEvent) { + IdentityProviderAuthenticationSuccessEvent passwordAuthEvent = (IdentityProviderAuthenticationSuccessEvent) event; + UserAuthenticationSuccessEvent userEvent = new UserAuthenticationSuccessEvent( + passwordAuthEvent.getUser(), + (Authentication) passwordAuthEvent.getSource() + ); + if (!checker.isMfaEnabled(userEvent.getIdentityZone(), userEvent.getUser().getOrigin())) { + publisher.publishEvent(userEvent); + } + } else if (event instanceof MfaAuthenticationSuccessEvent) { + MfaAuthenticationSuccessEvent mfaEvent = (MfaAuthenticationSuccessEvent) event; + UserAuthenticationSuccessEvent userEvent = new UserAuthenticationSuccessEvent( + mfaEvent.getUser(), + (Authentication) mfaEvent.getSource() + ); + publisher.publishEvent(userEvent); + } + } + + protected void onApplicationEvent(UserAuthenticationSuccessEvent event) { + UaaUser user = event.getUser(); + if (user.isLegacyVerificationBehavior() && !user.isVerified()) { + scimUserProvisioning.verifyUser(user.getId(), -1, IdentityZoneHolder.get().getId()); + } + UaaAuthentication authentication = (UaaAuthentication) event.getAuthentication(); + authentication.setLastLoginSuccessTime(user.getLastLogonTime()); + scimUserProvisioning.updateLastLogonTime(user.getId(), IdentityZoneHolder.get().getId()); + } + + + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + this.publisher = applicationEventPublisher; + } + + public void publish(ApplicationEvent event) { + if (publisher != null) { + publisher.publishEvent(event); + } + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/listener/UserAuthenticationSuccessListener.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/listener/UserAuthenticationSuccessListener.java deleted file mode 100644 index 7a77fb9a50f..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/listener/UserAuthenticationSuccessListener.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.cloudfoundry.identity.uaa.authentication.listener; - -import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; -import org.cloudfoundry.identity.uaa.authentication.event.UserAuthenticationSuccessEvent; -import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; -import org.cloudfoundry.identity.uaa.user.UaaUser; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.springframework.context.ApplicationListener; - -/******************************************************************************* - * Cloud Foundry - * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. - *

- * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - *

- * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - *******************************************************************************/ -public class UserAuthenticationSuccessListener implements ApplicationListener { - - private final ScimUserProvisioning scimUserProvisioning; - - public UserAuthenticationSuccessListener(ScimUserProvisioning scimUserProvisioning) { - this.scimUserProvisioning = scimUserProvisioning; - } - - @Override - public void onApplicationEvent(UserAuthenticationSuccessEvent event) { - UaaUser user = event.getUser(); - if(user.isLegacyVerificationBehavior() && !user.isVerified()) { - scimUserProvisioning.verifyUser(user.getId(), -1, IdentityZoneHolder.get().getId()); - } - UaaAuthentication authentication = (UaaAuthentication) event.getAuthentication(); - authentication.setLastLoginSuccessTime(user.getLastLogonTime()); - scimUserProvisioning.updateLastLogonTime(user.getId(), IdentityZoneHolder.get().getId()); - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java index 4fc33b409f0..ae76fc5eb0e 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java @@ -12,16 +12,20 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.authentication.manager; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.Locale; + import org.cloudfoundry.identity.uaa.authentication.AccountNotVerifiedException; import org.cloudfoundry.identity.uaa.authentication.AuthenticationPolicyRejectionException; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.cloudfoundry.identity.uaa.authentication.event.IdentityProviderAuthenticationSuccessEvent; import org.cloudfoundry.identity.uaa.authentication.event.PasswordAuthenticationFailureEvent; -import org.cloudfoundry.identity.uaa.authentication.event.PasswordAuthenticationSuccessEvent; import org.cloudfoundry.identity.uaa.authentication.event.UnverifiedUserAuthenticationEvent; import org.cloudfoundry.identity.uaa.authentication.event.UserAuthenticationFailureEvent; -import org.cloudfoundry.identity.uaa.authentication.event.UserAuthenticationSuccessEvent; import org.cloudfoundry.identity.uaa.authentication.event.UserNotFoundEvent; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.logging.SanitizedLogFactory; @@ -32,6 +36,7 @@ import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.util.ObjectUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; + import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; @@ -44,11 +49,6 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; -import java.util.Calendar; -import java.util.Collections; -import java.util.Date; -import java.util.Locale; - public class AuthzAuthenticationManager implements AuthenticationManager, ApplicationEventPublisherAware { private final SanitizedLogFactory.SanitizedLog logger = SanitizedLogFactory.getLog(getClass()); @@ -129,9 +129,7 @@ public Authentication authenticate(Authentication req) throws AuthenticationExce success.setRequiresPasswordChange(true); } - publish(new PasswordAuthenticationSuccessEvent(user, success)); - publish(new UserAuthenticationSuccessEvent(user, success)); - + publish(new IdentityProviderAuthenticationSuccessEvent(user, success)); return success; } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManager.java index 65169177250..686869aa2d2 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManager.java @@ -15,14 +15,17 @@ package org.cloudfoundry.identity.uaa.authentication.manager; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; + import org.cloudfoundry.identity.uaa.authentication.AccountNotPreCreatedException; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.authentication.event.UserAuthenticationSuccessEvent; +import org.cloudfoundry.identity.uaa.authentication.event.IdentityProviderAuthenticationSuccessEvent; import org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; @@ -38,6 +41,10 @@ import org.cloudfoundry.identity.uaa.user.UaaUserPrototype; import org.cloudfoundry.identity.uaa.user.UserInfo; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanNameAware; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; @@ -55,12 +62,6 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import java.util.Collection; -import java.util.Date; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; - import static java.util.Collections.EMPTY_SET; import static java.util.Optional.ofNullable; @@ -169,7 +170,7 @@ public Authentication authenticate(Authentication request) throws Authentication } UaaAuthentication success = new UaaAuthentication(new UaaPrincipal(user), user.getAuthorities(), uaaAuthenticationDetails); populateAuthenticationAttributes(success, request, authenticationData); - publish(new UserAuthenticationSuccessEvent(user, success)); + publish(new IdentityProviderAuthenticationSuccessEvent(user, success)); return success; } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/LoginAuthenticationManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/LoginAuthenticationManager.java index a963b21fbaf..3ac6354ec1d 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/LoginAuthenticationManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/LoginAuthenticationManager.java @@ -12,18 +12,22 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.authentication.manager; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import java.util.Date; +import java.util.Map; + import org.cloudfoundry.identity.uaa.authentication.AuthzAuthenticationRequest; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.authentication.event.UserAuthenticationSuccessEvent; +import org.cloudfoundry.identity.uaa.authentication.event.IdentityProviderAuthenticationSuccessEvent; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.user.UaaAuthority; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; @@ -37,9 +41,6 @@ import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import org.springframework.security.oauth2.provider.OAuth2Authentication; -import java.util.Date; -import java.util.Map; - public class LoginAuthenticationManager implements AuthenticationManager, ApplicationEventPublisherAware { public static final String NotANumber = OriginKeys.NotANumber; @@ -106,9 +107,8 @@ public Authentication authenticate(Authentication request) throws Authentication throw new BadCredentialsException("Bad Credentials"); } } - Authentication success = new UaaAuthentication(new UaaPrincipal(user), user.getAuthorities(), - authdetails); - publish(new UserAuthenticationSuccessEvent(user, success)); + Authentication success = new UaaAuthentication(new UaaPrincipal(user), user.getAuthorities(), authdetails); + publish(new IdentityProviderAuthenticationSuccessEvent(user, success)); return success; } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaChecker.java b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaChecker.java new file mode 100644 index 00000000000..2d807b66a3d --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaChecker.java @@ -0,0 +1,29 @@ +/* + * Cloud Foundry + * Copyright (c) [2009-2018] Pivotal Software, Inc. All Rights Reserved. + *

+ * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + *

+ * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file + */ +package org.cloudfoundry.identity.uaa.mfa; + +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; + +public class MfaChecker { + + private final IdentityProviderProvisioning providerProvisioning; + + public MfaChecker(IdentityProviderProvisioning providerProvisioning) { + this.providerProvisioning = providerProvisioning; + } + + public boolean isMfaEnabled(IdentityZone zone, String originKey) { + return zone.getConfig().getMfaConfig().isEnabled(); + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaRequiredFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaRequiredFilter.java index 1135f0c87ab..63722f89178 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaRequiredFilter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaRequiredFilter.java @@ -44,15 +44,18 @@ public class MfaRequiredFilter extends GenericFilterBean { private final AntPathRequestMatcher completedMatcher; private final String redirect; private final RequestCache cache; + private final MfaChecker checker; public MfaRequiredFilter(String urlFilter, String redirect, RequestCache cache, - String mfaCompleteUrl) { + String mfaCompleteUrl, + MfaChecker checker) { inProgressMatcher = new AntPathRequestMatcher(urlFilter); this.redirect = redirect; this.cache = cache; this.completedMatcher = new AntPathRequestMatcher(mfaCompleteUrl); + this.checker = checker; } @Override @@ -130,10 +133,11 @@ protected MfaNextStep getNextStep(HttpServletRequest request) { if (!(a instanceof UaaAuthentication)) { return MfaNextStep.INVALID_AUTH; } - if (!mfaRequired()) { + UaaAuthentication uaaAuth = (UaaAuthentication) a; + if (!mfaRequired(uaaAuth.getPrincipal().getOrigin())) { return MfaNextStep.MFA_NOT_REQUIRED; } - UaaAuthentication uaaAuth = (UaaAuthentication) a; + if (completedMatcher.matches(request) && uaaAuth.getAuthenticationMethods().contains("mfa")) { return MfaNextStep.MFA_COMPLETED; } @@ -158,7 +162,7 @@ protected void sendRedirect(String redirectUrl, HttpServletRequest request, Http response.sendRedirect(url.toString()); } - protected boolean mfaRequired() { - return IdentityZoneHolder.get().getConfig().getMfaConfig().isEnabled(); + protected boolean mfaRequired(String origin) { + return checker.isMfaEnabled(IdentityZoneHolder.get(), origin); } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProvider.java index 4e8fccc079f..91e86bdb75a 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProvider.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProvider.java @@ -13,12 +13,21 @@ package org.cloudfoundry.identity.uaa.provider.saml; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import javax.xml.namespace.QName; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.authentication.event.UserAuthenticationSuccessEvent; +import org.cloudfoundry.identity.uaa.authentication.event.IdentityProviderAuthenticationSuccessEvent; import org.cloudfoundry.identity.uaa.authentication.manager.ExternalGroupAuthorizationEvent; import org.cloudfoundry.identity.uaa.authentication.manager.InvitedUserAuthenticatedEvent; import org.cloudfoundry.identity.uaa.authentication.manager.NewUserAuthenticatedEvent; @@ -37,6 +46,10 @@ import org.cloudfoundry.identity.uaa.web.UaaSavedRequestAwareAuthenticationSuccessHandler; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.joda.time.DateTime; import org.opensaml.saml2.core.Attribute; import org.opensaml.saml2.core.AuthnStatement; @@ -72,18 +85,6 @@ import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; -import javax.xml.namespace.QName; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.LinkedList; -import java.util.List; -import java.util.Map.Entry; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.EMAIL_ATTRIBUTE_NAME; import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.FAMILY_NAME_ATTRIBUTE_NAME; import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.GIVEN_NAME_ATTRIBUTE_NAME; @@ -173,7 +174,7 @@ public Authentication authenticate(Authentication authentication) throws Authent UaaUser user = createIfMissing(samlPrincipal, addNew, authorities, userAttributes); UaaPrincipal principal = new UaaPrincipal(user); UaaAuthentication resultUaaAuthentication = new LoginSamlAuthenticationToken(principal, result).getUaaAuthentication(user.getAuthorities(), filteredExternalGroups, userAttributes); - publish(new UserAuthenticationSuccessEvent(user, resultUaaAuthentication)); + publish(new IdentityProviderAuthenticationSuccessEvent(user, resultUaaAuthentication)); if (samlConfig.isStoreCustomAttributes()) { userDatabase.storeUserInfo(user.getId(), new UserInfo() diff --git a/server/src/main/resources/spring/login-ui.xml b/server/src/main/resources/spring/login-ui.xml index c1fb16cc9ba..babeba23dbb 100644 --- a/server/src/main/resources/spring/login-ui.xml +++ b/server/src/main/resources/spring/login-ui.xml @@ -214,11 +214,16 @@ + + + + + + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + *

+ * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file + */ + +package org.cloudfoundry.identity.uaa.authentication.listener; + +import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; +import org.cloudfoundry.identity.uaa.authentication.event.IdentityProviderAuthenticationSuccessEvent; +import org.cloudfoundry.identity.uaa.authentication.event.MfaAuthenticationSuccessEvent; +import org.cloudfoundry.identity.uaa.authentication.event.UserAuthenticationSuccessEvent; +import org.cloudfoundry.identity.uaa.mfa.MfaChecker; +import org.cloudfoundry.identity.uaa.scim.ScimUser; +import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; +import org.cloudfoundry.identity.uaa.user.UaaUser; +import org.cloudfoundry.identity.uaa.user.UaaUserPrototype; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.context.ApplicationEventPublisher; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +public class AuthenticationSuccessListenerTests { + + AuthenticationSuccessListener listener; + ScimUserProvisioning scimUserProvisioning; + UaaAuthentication mockAuth = mock(UaaAuthentication.class); + MfaChecker checker; + ApplicationEventPublisher publisher; + private String id; + private UaaUserPrototype userPrototype; + private UaaUser user; + + @Before + public void setUp() { + publisher = mock(ApplicationEventPublisher.class); + checker = mock(MfaChecker.class); + scimUserProvisioning = mock(ScimUserProvisioning.class); + listener = new AuthenticationSuccessListener(scimUserProvisioning, checker); + listener.setApplicationEventPublisher(publisher); + id = "user-id"; + userPrototype = new UaaUserPrototype() + .withId(id) + .withUsername("testUser") + .withEmail("test@email.com"); + user = new UaaUser(userPrototype); + } + + private ScimUser getScimUser(UaaUser user) { + ScimUser scimUser = new ScimUser(user.getId(), user.getUsername(), user.getGivenName(), user.getFamilyName()); + scimUser.setVerified(user.isVerified()); + return scimUser; + } + + @Test + public void unverifiedUserBecomesVerifiedIfTheyHaveLegacyFlag() { + userPrototype + .withVerified(false) + .withLegacyVerificationBehavior(true); + UserAuthenticationSuccessEvent event = getEvent(); + String zoneId = IdentityZoneHolder.get().getId(); + when(scimUserProvisioning.retrieve(id, zoneId)).thenReturn(getScimUser(event.getUser())); + listener.onApplicationEvent(event); + verify(scimUserProvisioning).verifyUser(eq(id), eq(-1), eq(zoneId)); + } + + public UserAuthenticationSuccessEvent getEvent() { + user = new UaaUser(userPrototype); + return new UserAuthenticationSuccessEvent( + user, + mockAuth + ); + } + + @Test + public void unverifiedUserDoesNotBecomeVerifiedIfTheyHaveNoLegacyFlag() { + userPrototype.withVerified(false); + UserAuthenticationSuccessEvent event = getEvent(); + String zoneId = IdentityZoneHolder.get().getId(); + when(scimUserProvisioning.retrieve(id, zoneId)).thenReturn(getScimUser(event.getUser())); + listener.onApplicationEvent(event); + verify(scimUserProvisioning, never()).verifyUser(anyString(), anyInt(), eq(zoneId)); + } + + @Test + public void userLastUpdatedGetsCalledOnEvent() { + + UserAuthenticationSuccessEvent event = getEvent(); + when(scimUserProvisioning.retrieve(id, IdentityZoneHolder.get().getId())).thenReturn(getScimUser(event.getUser())); + listener.onApplicationEvent(event); + verify(scimUserProvisioning, times(1)).updateLastLogonTime(id, IdentityZoneHolder.get().getId()); + } + + @Test + public void previousLoginIsSetOnTheAuthentication() { + userPrototype + .withLastLogonSuccess(123456789L); + UserAuthenticationSuccessEvent event = getEvent(); + String zoneId = IdentityZoneHolder.get().getId(); + when(scimUserProvisioning.retrieve(this.id, zoneId)).thenReturn(getScimUser(event.getUser())); + UaaAuthentication authentication = (UaaAuthentication) event.getAuthentication(); + listener.onApplicationEvent(event); + verify(authentication).setLastLoginSuccessTime(123456789L); + } + + @Test + public void provider_authentication_success_triggers_user_authentication_success() throws Exception { + when(checker.isMfaEnabled(any(), any())).thenReturn(false); + IdentityProviderAuthenticationSuccessEvent event = new IdentityProviderAuthenticationSuccessEvent( + user, + mockAuth + ); + listener.onApplicationEvent(event); + verify(publisher, times(1)).publishEvent(isA(UserAuthenticationSuccessEvent.class)); + } + + @Test + public void provider_authentication_success_does_not_trigger_user_authentication_success() throws Exception { + when(checker.isMfaEnabled(any(), any())).thenReturn(true); + IdentityProviderAuthenticationSuccessEvent event = new IdentityProviderAuthenticationSuccessEvent( + user, + mockAuth + ); + listener.onApplicationEvent(event); + verifyZeroInteractions(publisher); + } + + @Test + public void mfa_authentication_success_triggers_user_authentication_success() throws Exception { + when(checker.isMfaEnabled(any(), any())).thenReturn(true); + MfaAuthenticationSuccessEvent event = new MfaAuthenticationSuccessEvent( + user, + mockAuth, + "mfa-type" + ); + listener.onApplicationEvent(event); + verify(publisher, times(1)).publishEvent(isA(UserAuthenticationSuccessEvent.class)); + } + +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/listener/UserAuthenticationSuccessListenerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/listener/UserAuthenticationSuccessListenerTests.java deleted file mode 100644 index 81bfa683eb0..00000000000 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/listener/UserAuthenticationSuccessListenerTests.java +++ /dev/null @@ -1,120 +0,0 @@ -package org.cloudfoundry.identity.uaa.authentication.listener; - -import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; -import org.cloudfoundry.identity.uaa.authentication.event.UserAuthenticationSuccessEvent; -import org.cloudfoundry.identity.uaa.scim.ScimUser; -import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; -import org.cloudfoundry.identity.uaa.user.UaaUser; -import org.cloudfoundry.identity.uaa.user.UaaUserPrototype; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.junit.Before; -import org.junit.Test; - -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -/******************************************************************************* - * Cloud Foundry - * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. - *

- * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - *

- * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - *******************************************************************************/ -public class UserAuthenticationSuccessListenerTests { - - UserAuthenticationSuccessListener listener; - ScimUserProvisioning scimUserProvisioning; - UaaAuthentication mockAuth = mock(UaaAuthentication.class); - @Before - public void SetUp() - { - scimUserProvisioning = mock(ScimUserProvisioning.class); - listener = new UserAuthenticationSuccessListener(scimUserProvisioning); - } - - private UserAuthenticationSuccessEvent getEvent(UaaUserPrototype userPrototype) { - return new UserAuthenticationSuccessEvent(new UaaUser(userPrototype), mockAuth); - } - - private ScimUser getScimUser(UaaUser user) { - ScimUser scimUser = new ScimUser(user.getId(), user.getUsername(), user.getGivenName(), user.getFamilyName()); - scimUser.setVerified(user.isVerified()); - return scimUser; - } - - @Test - public void unverifiedUserBecomesVerifiedIfTheyHaveLegacyFlag() { - String id = "user-id"; - UserAuthenticationSuccessEvent event = getEvent(new UaaUserPrototype() - .withId(id) - .withUsername("testUser") - .withEmail("test@email.com") - .withVerified(false) - .withLegacyVerificationBehavior(true)); - String zoneId = IdentityZoneHolder.get().getId(); - when(scimUserProvisioning.retrieve(id, zoneId)).thenReturn(getScimUser(event.getUser())); - - listener.onApplicationEvent(event); - - verify(scimUserProvisioning).verifyUser(eq(id), eq(-1), eq(zoneId)); - } - - @Test - public void unverifiedUserDoesNotBecomeVerifiedIfTheyHaveNoLegacyFlag() { - String id = "user-id"; - UserAuthenticationSuccessEvent event = getEvent(new UaaUserPrototype() - .withId(id) - .withUsername("testUser") - .withEmail("test@email.com") - .withVerified(false)); - String zoneId = IdentityZoneHolder.get().getId(); - when(scimUserProvisioning.retrieve(id, zoneId)).thenReturn(getScimUser(event.getUser())); - - listener.onApplicationEvent(event); - - verify(scimUserProvisioning, never()).verifyUser(anyString(), anyInt(), eq(zoneId)); - } - - @Test - public void userLastUpdatedGetsCalledOnEvent() { - String userId = "userId"; - UserAuthenticationSuccessEvent event = getEvent(new UaaUserPrototype() - .withId(userId) - .withEmail("test@test.org") - .withUsername("testUser") - .withVerified(false)); - when(scimUserProvisioning.retrieve(userId, IdentityZoneHolder.get().getId())).thenReturn(getScimUser(event.getUser())); - - listener.onApplicationEvent(event); - verify(scimUserProvisioning, times(1)).updateLastLogonTime(userId, IdentityZoneHolder.get().getId()); - } - - @Test - public void previousLoginIsSetOnTheAuthentication() { - String userId = "userId"; - UaaUserPrototype uaaUserPrototype = new UaaUserPrototype() - .withId(userId) - .withEmail("test@test.org") - .withUsername("testUser") - .withVerified(false) - .withLastLogonSuccess(123456789L); - - UserAuthenticationSuccessEvent event = new UserAuthenticationSuccessEvent(new UaaUser(uaaUserPrototype), mockAuth); - when(scimUserProvisioning.retrieve(userId, IdentityZoneHolder.get().getId())).thenReturn(getScimUser(event.getUser())); - UaaAuthentication authentication = (UaaAuthentication) event.getAuthentication(); - listener.onApplicationEvent(event); - verify(authentication).setLastLoginSuccessTime(123456789L); - } - -} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManagerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManagerTests.java index 5547ee511a2..2cab4306c54 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManagerTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManagerTests.java @@ -12,17 +12,23 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.authentication.manager; +import javax.servlet.http.HttpServletRequest; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import org.cloudfoundry.identity.uaa.authentication.AccountNotVerifiedException; import org.cloudfoundry.identity.uaa.authentication.AuthenticationPolicyRejectionException; import org.cloudfoundry.identity.uaa.authentication.AuthzAuthenticationRequest; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.cloudfoundry.identity.uaa.authentication.event.IdentityProviderAuthenticationSuccessEvent; import org.cloudfoundry.identity.uaa.authentication.event.PasswordAuthenticationFailureEvent; -import org.cloudfoundry.identity.uaa.authentication.event.PasswordAuthenticationSuccessEvent; import org.cloudfoundry.identity.uaa.authentication.event.UnverifiedUserAuthenticationEvent; import org.cloudfoundry.identity.uaa.authentication.event.UserAuthenticationFailureEvent; -import org.cloudfoundry.identity.uaa.authentication.event.UserAuthenticationSuccessEvent; import org.cloudfoundry.identity.uaa.authentication.event.UserNotFoundEvent; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; @@ -34,6 +40,7 @@ import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.user.UaaUserPrototype; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; + import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -49,13 +56,6 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; -import javax.servlet.http.HttpServletRequest; -import java.util.Calendar; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.instanceOf; import static org.junit.Assert.assertEquals; @@ -136,11 +136,8 @@ public void successfulAuthentication() throws Exception { assertThat(((UaaAuthentication)result).getAuthenticationMethods(), containsInAnyOrder("pwd")); List events = eventCaptor.getAllValues(); - assertThat(events.get(0), instanceOf(PasswordAuthenticationSuccessEvent.class)); - assertEquals("auser", ((PasswordAuthenticationSuccessEvent)events.get(0)).getUser().getUsername()); - assertThat(events.get(1), instanceOf(UserAuthenticationSuccessEvent.class)); - assertEquals("auser", ((UserAuthenticationSuccessEvent)events.get(1)).getUser().getUsername()); - + assertThat(events.get(0), instanceOf(IdentityProviderAuthenticationSuccessEvent.class)); + assertEquals("auser", ((IdentityProviderAuthenticationSuccessEvent)events.get(0)).getUser().getUsername()); } @Test @@ -199,8 +196,7 @@ public void successfulAuthenticationReturnsTokenAndPublishesEvent() throws Excep assertEquals("auser", result.getName()); assertEquals("auser", ((UaaPrincipal) result.getPrincipal()).getName()); - verify(publisher).publishEvent(isA(PasswordAuthenticationSuccessEvent.class)); - verify(publisher).publishEvent(isA(UserAuthenticationSuccessEvent.class)); + verify(publisher).publishEvent(isA(IdentityProviderAuthenticationSuccessEvent.class)); } @Test diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManagerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManagerTest.java index 1d919b93e74..90c445d0f8b 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManagerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManagerTest.java @@ -1,10 +1,15 @@ package org.cloudfoundry.identity.uaa.authentication.manager; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + import org.cloudfoundry.identity.uaa.authentication.AccountNotPreCreatedException; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.authentication.event.UserAuthenticationSuccessEvent; +import org.cloudfoundry.identity.uaa.authentication.event.IdentityProviderAuthenticationSuccessEvent; import org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; @@ -15,6 +20,7 @@ import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.user.UaaUserPrototype; import org.cloudfoundry.identity.uaa.user.UserInfo; + import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -30,11 +36,6 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; - import static org.cloudfoundry.identity.uaa.constants.OriginKeys.LDAP; import static org.hamcrest.CoreMatchers.containsString; import static org.junit.Assert.assertEquals; @@ -496,7 +497,7 @@ public void testAuthenticateUserExists() throws Exception { userArgumentCaptor = ArgumentCaptor.forClass(ApplicationEvent.class); verify(applicationEventPublisher,times(1)).publishEvent(userArgumentCaptor.capture()); assertEquals(1,userArgumentCaptor.getAllValues().size()); - UserAuthenticationSuccessEvent userevent = (UserAuthenticationSuccessEvent)userArgumentCaptor.getAllValues().get(0); + IdentityProviderAuthenticationSuccessEvent userevent = (IdentityProviderAuthenticationSuccessEvent)userArgumentCaptor.getAllValues().get(0); assertEquals(origin, userevent.getUser().getOrigin()); assertEquals(userName, userevent.getUser().getUsername()); } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/LoginAuthenticationManagerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/LoginAuthenticationManagerTests.java index fbdb25cc7e5..1a636edbeef 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/LoginAuthenticationManagerTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/LoginAuthenticationManagerTests.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Cloud Foundry + * Cloud Foundry * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). @@ -13,10 +13,6 @@ package org.cloudfoundry.identity.uaa.authentication.manager; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertNotNull; - import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -24,12 +20,13 @@ import org.cloudfoundry.identity.uaa.authentication.AuthzAuthenticationRequest; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationTestFactory; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.authentication.event.UserAuthenticationSuccessEvent; +import org.cloudfoundry.identity.uaa.authentication.event.IdentityProviderAuthenticationSuccessEvent; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.test.TestApplicationEventPublisher; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.user.UaaUserTestFactory; + import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -46,9 +43,13 @@ import org.springframework.security.oauth2.provider.AuthorizationRequest; import org.springframework.security.oauth2.provider.OAuth2Authentication; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + /** * @author Dave Syer - * + * */ public class LoginAuthenticationManagerTests { @@ -58,14 +59,14 @@ public class LoginAuthenticationManagerTests { private OAuth2Authentication oauth2Authentication; - private TestApplicationEventPublisher publisher; + private TestApplicationEventPublisher publisher; @Rule public ExpectedException expectedException = ExpectedException.none(); @Before public void init() { - publisher = TestApplicationEventPublisher.forEventClass(UserAuthenticationSuccessEvent.class); + publisher = TestApplicationEventPublisher.forEventClass(IdentityProviderAuthenticationSuccessEvent.class); manager.setApplicationEventPublisher(publisher); manager.setUserDatabase(userDatabase); oauth2Authentication = new OAuth2Authentication(new AuthorizationRequest("client", Arrays.asList("read", diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/MfaCheckerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/MfaCheckerTests.java new file mode 100644 index 00000000000..b345610fbff --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/MfaCheckerTests.java @@ -0,0 +1,52 @@ +/* + * Cloud Foundry + * Copyright (c) [2009-2018] Pivotal Software, Inc. All Rights Reserved. + *

+ * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + *

+ * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file + */ + +package org.cloudfoundry.identity.uaa.mfa; + +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; + +import org.junit.Before; +import org.junit.Test; + +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.UAA; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + +public class MfaCheckerTests { + + private IdentityZone zone; + private MfaChecker checker; + private IdentityProviderProvisioning providerProvisioning; + + @Before + public void setUp() throws Exception { + providerProvisioning = mock(IdentityProviderProvisioning.class); + zone = MultitenancyFixture.identityZone("id", "domain"); + checker = new MfaChecker(providerProvisioning); + } + + @Test + public void mfa_zone_enabled() { + zone.getConfig().getMfaConfig().setEnabled(true); + assertTrue(checker.isMfaEnabled(zone, UAA)); + } + + @Test + public void mfa_zone_disabled() { + zone.getConfig().getMfaConfig().setEnabled(false); + assertFalse(checker.isMfaEnabled(zone, UAA)); + } +} \ No newline at end of file diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/MfaRequiredFilterTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/MfaRequiredFilterTests.java index a1a7b33b481..caf5d995b3e 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/MfaRequiredFilterTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/MfaRequiredFilterTests.java @@ -23,6 +23,7 @@ import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.junit.After; @@ -46,11 +47,9 @@ import static org.cloudfoundry.identity.uaa.mfa.MfaRequiredFilter.MfaNextStep.MFA_REQUIRED; import static org.cloudfoundry.identity.uaa.mfa.MfaRequiredFilter.MfaNextStep.NOT_AUTHENTICATED; import static org.hamcrest.Matchers.containsString; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.same; @@ -72,14 +71,17 @@ public class MfaRequiredFilterTests { private HttpServletResponse response; private FilterChain chain; private MfaRequiredFilter filter; + private IdentityProviderProvisioning providerProvisioning; @Before public void setup() throws Exception { + providerProvisioning = mock(IdentityProviderProvisioning.class); requestCache = mock(RequestCache.class); filter = new MfaRequiredFilter("/login/mfa/**", "/login/mfa/register", requestCache, - "/login/mfa/completed"); + "/login/mfa/completed", + new MfaChecker(providerProvisioning)); spyFilter = spy(filter); request = new MockHttpServletRequest(); usernameAuthentication = new UsernamePasswordAuthenticationToken("fake-principal","fake-credentials"); @@ -100,17 +102,6 @@ public void teardown() throws Exception { SecurityContextHolder.clearContext(); } - @Test - public void mfa_not_required() throws Exception { - assertFalse(spyFilter.mfaRequired()); - } - - @Test - public void mfa_required() throws Exception { - IdentityZoneHolder.get().getConfig().getMfaConfig().setEnabled(true); - assertTrue(spyFilter.mfaRequired()); - } - @Test public void authentication_log_info_null() throws Exception { assertNull(spyFilter.getAuthenticationLogInfo()); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProviderTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProviderTests.java index f9011b3f248..cdee3830f6a 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProviderTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProviderTests.java @@ -15,8 +15,18 @@ package org.cloudfoundry.identity.uaa.provider.saml; +import javax.servlet.ServletContext; +import javax.xml.namespace.QName; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; -import org.cloudfoundry.identity.uaa.authentication.event.UserAuthenticationSuccessEvent; +import org.cloudfoundry.identity.uaa.authentication.event.IdentityProviderAuthenticationSuccessEvent; import org.cloudfoundry.identity.uaa.authentication.manager.AuthEvent; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; @@ -42,6 +52,7 @@ import org.cloudfoundry.identity.uaa.web.UaaSavedRequestAwareAuthenticationSuccessHandler; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; + import org.joda.time.DateTime; import org.junit.After; import org.junit.Before; @@ -84,16 +95,6 @@ import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.request.ServletWebRequest; -import javax.servlet.ServletContext; -import javax.xml.namespace.QName; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.GROUP_ATTRIBUTE_NAME; import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_ATTRIBUTE_PREFIX; import static org.hamcrest.Matchers.containsInAnyOrder; @@ -332,7 +333,7 @@ public void testAuthenticateSimple() { public void testAuthenticationEvents() { authprovider.authenticate(mockSamlAuthentication(OriginKeys.SAML)); assertEquals(3, publisher.events.size()); - assertTrue(publisher.events.get(2) instanceof UserAuthenticationSuccessEvent); + assertTrue(publisher.events.get(2) instanceof IdentityProviderAuthenticationSuccessEvent); } @Test diff --git a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml index ad869bd2acc..224cfb1d547 100755 --- a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml +++ b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml @@ -572,8 +572,9 @@ - + + diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java index 578e90ad8b5..d35df3584b8 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java @@ -12,8 +12,12 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.mock.audit; -import com.fasterxml.jackson.core.type.TypeReference; -import org.apache.commons.codec.binary.Base64; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + import org.cloudfoundry.identity.uaa.account.LostPasswordChangeRequest; import org.cloudfoundry.identity.uaa.account.event.PasswordChangeEvent; import org.cloudfoundry.identity.uaa.account.event.PasswordChangeFailureEvent; @@ -30,8 +34,8 @@ import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.event.ClientAuthenticationFailureEvent; import org.cloudfoundry.identity.uaa.authentication.event.ClientAuthenticationSuccessEvent; +import org.cloudfoundry.identity.uaa.authentication.event.IdentityProviderAuthenticationSuccessEvent; import org.cloudfoundry.identity.uaa.authentication.event.PasswordAuthenticationFailureEvent; -import org.cloudfoundry.identity.uaa.authentication.event.PasswordAuthenticationSuccessEvent; import org.cloudfoundry.identity.uaa.authentication.event.PrincipalAuthenticationFailureEvent; import org.cloudfoundry.identity.uaa.authentication.event.UnverifiedUserAuthenticationEvent; import org.cloudfoundry.identity.uaa.authentication.event.UserAuthenticationFailureEvent; @@ -56,6 +60,9 @@ import org.cloudfoundry.identity.uaa.zone.ClientServicesExtension; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.MultitenantJdbcClientDetailsService; + +import com.fasterxml.jackson.core.type.TypeReference; +import org.apache.commons.codec.binary.Base64; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -75,16 +82,11 @@ import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - import static org.cloudfoundry.identity.uaa.audit.AuditEventType.ClientCreateSuccess; import static org.cloudfoundry.identity.uaa.audit.AuditEventType.ClientUpdateSuccess; import static org.cloudfoundry.identity.uaa.audit.AuditEventType.GroupCreatedEvent; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.CookieCsrfPostProcessor.cookieCsrf; +import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.getEventOfType; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertEquals; @@ -256,10 +258,10 @@ public void userLoginTest() throws Exception { ArgumentCaptor captor = ArgumentCaptor.forClass(AbstractUaaEvent.class); verify(listener, times(2)).onApplicationEvent(captor.capture()); - PasswordAuthenticationSuccessEvent passwordevent = (PasswordAuthenticationSuccessEvent)captor.getAllValues().get(0); + IdentityProviderAuthenticationSuccessEvent passwordevent = getEventOfType(captor, IdentityProviderAuthenticationSuccessEvent.class); assertEquals(testUser.getUserName(), passwordevent.getUser().getUsername()); assertTrue(passwordevent.getAuditEvent().getOrigin().contains("sessionId=")); - UserAuthenticationSuccessEvent userevent = (UserAuthenticationSuccessEvent)captor.getAllValues().get(1); + UserAuthenticationSuccessEvent userevent = getEventOfType(captor, UserAuthenticationSuccessEvent.class); assertEquals(passwordevent.getUser().getId(), userevent.getUser().getId()); assertEquals(testUser.getUserName(), userevent.getUser().getUsername()); assertTrue(userevent.getAuditEvent().getOrigin().contains("sessionId=")); @@ -281,10 +283,10 @@ public void userLoginAuthenticateEndpointTest() throws Exception { ArgumentCaptor captor = ArgumentCaptor.forClass(AbstractUaaEvent.class); verify(listener, times(2)).onApplicationEvent(captor.capture()); - PasswordAuthenticationSuccessEvent passwordevent = (PasswordAuthenticationSuccessEvent)captor.getAllValues().get(0); + IdentityProviderAuthenticationSuccessEvent passwordevent = getEventOfType(captor, IdentityProviderAuthenticationSuccessEvent.class); assertEquals(testUser.getUserName(), passwordevent.getUser().getUsername()); assertTrue(passwordevent.getAuditEvent().getOrigin().contains("sessionId=")); - UserAuthenticationSuccessEvent userevent = (UserAuthenticationSuccessEvent)captor.getAllValues().get(1); + UserAuthenticationSuccessEvent userevent = getEventOfType(captor, UserAuthenticationSuccessEvent.class); assertEquals(passwordevent.getUser().getId(), userevent.getUser().getId()); assertEquals(testUser.getUserName(), userevent.getUser().getUsername()); assertTrue(userevent.getAuditEvent().getOrigin().contains("sessionId=")); @@ -502,10 +504,10 @@ public void userChangePasswordTest() throws Exception { .andExpect(header().string("Location", "/")); ArgumentCaptor captor = ArgumentCaptor.forClass(AbstractUaaEvent.class); verify(listener, times(2)).onApplicationEvent(captor.capture()); - PasswordAuthenticationSuccessEvent passwordevent = (PasswordAuthenticationSuccessEvent)captor.getAllValues().get(0); + IdentityProviderAuthenticationSuccessEvent passwordevent = getEventOfType(captor, IdentityProviderAuthenticationSuccessEvent.class); String userid = passwordevent.getUser().getId(); assertTrue(passwordevent.getAuditEvent().getOrigin().contains("sessionId=")); - UserAuthenticationSuccessEvent userevent = (UserAuthenticationSuccessEvent)captor.getAllValues().get(1); + UserAuthenticationSuccessEvent userevent = getEventOfType(captor, UserAuthenticationSuccessEvent.class); assertEquals(passwordevent.getUser().getId(), userevent.getUser().getId()); assertTrue(userevent.getAuditEvent().getOrigin().contains("sessionId=")); @@ -553,10 +555,10 @@ public void userChangeInvalidPasswordTest() throws Exception { ArgumentCaptor captor = ArgumentCaptor.forClass(AbstractUaaEvent.class); verify(listener, times(2)).onApplicationEvent(captor.capture()); - PasswordAuthenticationSuccessEvent passwordevent = (PasswordAuthenticationSuccessEvent)captor.getAllValues().get(0); + IdentityProviderAuthenticationSuccessEvent passwordevent = getEventOfType(captor, IdentityProviderAuthenticationSuccessEvent.class); String userid = passwordevent.getUser().getId(); assertTrue(passwordevent.getAuditEvent().getOrigin().contains("sessionId=")); - UserAuthenticationSuccessEvent userevent = (UserAuthenticationSuccessEvent)captor.getAllValues().get(1); + UserAuthenticationSuccessEvent userevent = getEventOfType(captor, UserAuthenticationSuccessEvent.class); assertEquals(passwordevent.getUser().getId(), userevent.getUser().getId()); assertTrue(userevent.getAuditEvent().getOrigin().contains("sessionId=")); @@ -793,7 +795,7 @@ public void testUserCreatedEventDuringLoginServerAuthorize() throws Exception { getMockMvc().perform(userPost) .andExpect(status().isOk()); - assertEquals(2, testListener.getEventCount()); + assertEquals(3, testListener.getEventCount()); UserModifiedEvent userModifiedEvent = (UserModifiedEvent) testListener.getEvents().get(0); assertEquals("login", userModifiedEvent.getAuthentication().getName()); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java index 97189a0050a..0d91ff59744 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java @@ -31,6 +31,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.cloudfoundry.identity.uaa.audit.event.AbstractUaaEvent; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; @@ -80,6 +81,7 @@ import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang.RandomStringUtils; import org.junit.Assert; +import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEvent; @@ -175,6 +177,15 @@ private MockMvcUtils() {} ""; + public static T getEventOfType(ArgumentCaptor captor, Class type) { + for (AbstractUaaEvent event : captor.getAllValues()) { + if (event.getClass().equals(type)) { + return (T)event; + } + } + return null; + } + public static String performMfaPostVerifyWithCode(int code, MockMvc mvc, MockHttpSession session) throws Exception { return performMfaPostVerifyWithCode(code, mvc, session, "localhost"); }