Skip to content

Commit

Permalink
Support event ordering for multi factor authentication
Browse files Browse the repository at this point in the history
[#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
  • Loading branch information
fhanik committed Jan 17, 2018
1 parent bce6700 commit 4db1771
Show file tree
Hide file tree
Showing 24 changed files with 514 additions and 319 deletions.
Expand Up @@ -58,7 +58,7 @@ public enum AuditEventType {
ServiceProviderModifiedEvent(34),
UserAccountUnlockedEvent(35),
TokenRevocationEvent(36),
PasswordAuthenticationSuccess(37),
IdentityProviderAuthenticationSuccess(37),
PasswordAuthenticationFailure(38),
MfaAuthenticationSuccess(39);

Expand Down
@@ -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").
Expand All @@ -19,7 +19,7 @@
/**
* @author Luke Taylor
*/
abstract class AbstractUaaAuthenticationEvent extends AbstractUaaEvent {
public abstract class AbstractUaaAuthenticationEvent extends AbstractUaaEvent {

AbstractUaaAuthenticationEvent(Authentication authentication) {
super(authentication);
Expand Down
@@ -0,0 +1,40 @@
/*
* Cloud Foundry
* Copyright (c) [2009-2018] Pivotal Software, Inc. All Rights Reserved.
* <p/>
* 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.
* <p/>
* 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;
}
}

This file was deleted.

@@ -0,0 +1,88 @@
/*
* Cloud Foundry
* Copyright (c) [2009-2018] Pivotal Software, Inc. All Rights Reserved.
* <p/>
* 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.
* <p/>
* 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<AbstractUaaAuthenticationEvent>, 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);
}
}
}

This file was deleted.

Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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());
Expand Down Expand Up @@ -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;
}
}
Expand Down
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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;
}

Expand Down
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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;
}
}
Expand Down

0 comments on commit 4db1771

Please sign in to comment.