/*
* ******************************************************************************
* 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").
* 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.manager;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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.provider.ExternalIdentityProviderDefinition;
import org.cloudfoundry.identity.uaa.provider.IdentityProvider;
import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning;
import org.cloudfoundry.identity.uaa.user.DialableByPhone;
import org.cloudfoundry.identity.uaa.user.ExternallyIdentifiable;
import org.cloudfoundry.identity.uaa.user.Mailable;
import org.cloudfoundry.identity.uaa.user.Named;
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.user.UaaUserPrototype;
import org.cloudfoundry.identity.uaa.user.UserInfo;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
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;
public class ExternalLoginAuthenticationManager<ExternalAuthenticationDetails> implements AuthenticationManager, ApplicationEventPublisherAware, BeanNameAware {
public static final String USER_ATTRIBUTE_PREFIX = "user.attribute.";
protected final Log logger = LogFactory.getLog(getClass());
private ApplicationEventPublisher eventPublisher;
private UaaUserDatabase userDatabase;
private String name;
private String origin = "unknown";
private IdentityProviderProvisioning providerProvisioning;
public ExternalLoginAuthenticationManager(IdentityProviderProvisioning providerProvisioning) {
this.providerProvisioning = providerProvisioning;
}
public IdentityProviderProvisioning getProviderProvisioning() {
return providerProvisioning;
}
public void setProviderProvisioning(IdentityProviderProvisioning providerProvisioning) {
this.providerProvisioning = providerProvisioning;
}
public String getOrigin() {
return origin;
}
public void setOrigin(String origin) {
this.origin = origin;
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
/**
* @param userDatabase the userDatabase to set
*/
public void setUserDatabase(UaaUserDatabase userDatabase) {
this.userDatabase = userDatabase;
}
public UaaUserDatabase getUserDatabase() {
return this.userDatabase;
}
@Override
public Authentication authenticate(Authentication request) throws AuthenticationException {
logger.debug("Starting external authentication for:"+request);
ExternalAuthenticationDetails authenticationData = getExternalAuthenticationDetails(request);
UaaUser userFromRequest = getUser(request, authenticationData);
if (userFromRequest == null) {
return null;
}
UaaUser userFromDb;
try {
logger.debug(String.format("Searching for user by (username:%s , origin:%s)", userFromRequest.getUsername(), getOrigin()));
userFromDb = userDatabase.retrieveUserByName(userFromRequest.getUsername(), getOrigin());
} catch (UsernameNotFoundException e) {
logger.debug(String.format("Searching for user by (email:%s , origin:%s)", userFromRequest.getEmail(), getOrigin()));
userFromDb = userDatabase.retrieveUserByEmail(userFromRequest.getEmail(), getOrigin());
}
// Register new users automatically
if (userFromDb == null) {
if (!isAddNewShadowUser()) {
throw new AccountNotPreCreatedException("The user account must be pre-created. Please contact your system administrator.");
}
publish(new NewUserAuthenticatedEvent(userFromRequest));
try {
userFromDb = userDatabase.retrieveUserByName(userFromRequest.getUsername(), getOrigin());
} catch (UsernameNotFoundException ex) {
throw new BadCredentialsException("Unable to register user in internal UAA store.");
}
}
//user is authenticated and exists in UAA
UaaUser user = userAuthenticated(request, userFromRequest, userFromDb);
UaaAuthenticationDetails uaaAuthenticationDetails;
if (request.getDetails() instanceof UaaAuthenticationDetails) {
uaaAuthenticationDetails = (UaaAuthenticationDetails) request.getDetails();
} else {
uaaAuthenticationDetails = UaaAuthenticationDetails.UNKNOWN;
}
UaaAuthentication success = new UaaAuthentication(new UaaPrincipal(user), user.getAuthorities(), uaaAuthenticationDetails);
populateAuthenticationAttributes(success, request, authenticationData);
publish(new UserAuthenticationSuccessEvent(user, success));
return success;
}
protected void populateAuthenticationAttributes(UaaAuthentication authentication, Authentication request, ExternalAuthenticationDetails authenticationData) {
if (request.getPrincipal() instanceof UserDetails) {
UserDetails userDetails = (UserDetails) request.getPrincipal();
authentication.setUserAttributes(getUserAttributes(userDetails));
authentication.setExternalGroups(new HashSet<>(getExternalUserAuthorities(userDetails)));
}
if (authentication.getAuthenticationMethods()==null) {
authentication.setAuthenticationMethods(new HashSet<>());
}
authentication.getAuthenticationMethods().add("ext");
if (authentication.getUserAttributes()!=null && authentication.getUserAttributes().size()>0 && getProviderProvisioning()!=null) {
IdentityProvider<ExternalIdentityProviderDefinition> provider = getProviderProvisioning().retrieveByOrigin(getOrigin(), IdentityZoneHolder.get().getId());
if (provider.getConfig()!=null && provider.getConfig().isStoreCustomAttributes()) {
logger.debug("Storing custom attributes for user_id:"+authentication.getPrincipal().getId());
UserInfo userInfo = new UserInfo()
.setUserAttributes(authentication.getUserAttributes())
.setRoles(new LinkedList(ofNullable(authentication.getExternalGroups()).orElse(EMPTY_SET)));
getUserDatabase().storeUserInfo(authentication.getPrincipal().getId(), userInfo);
}
}
}
protected ExternalAuthenticationDetails getExternalAuthenticationDetails(Authentication authentication) {
return null;
}
protected boolean isAddNewShadowUser() {
return true;
}
protected MultiValueMap<String, String> getUserAttributes(UserDetails request) {
return new LinkedMultiValueMap<>();
}
protected List<String> getExternalUserAuthorities(UserDetails request) {
return new LinkedList<>();
}
protected void publish(ApplicationEvent event) {
if (eventPublisher != null) {
eventPublisher.publishEvent(event);
}
}
protected UaaUser userAuthenticated(Authentication request, UaaUser userFromRequest, UaaUser userFromDb) {
return userFromDb;
}
protected UaaUser getUser(Authentication request, ExternalAuthenticationDetails authDetails) {
UserDetails userDetails;
if (request.getPrincipal() instanceof UserDetails) {
userDetails = (UserDetails) request.getPrincipal();
} else if (request instanceof UsernamePasswordAuthenticationToken) {
String username = request.getPrincipal().toString();
String password = request.getCredentials() != null ? request.getCredentials().toString() : "";
userDetails = new User(username, password, true, true, true, true, UaaAuthority.USER_AUTHORITIES);
} else if (request.getPrincipal() == null) {
logger.debug(this.getClass().getName() + "[" + name + "] cannot process null principal");
return null;
} else {
logger.debug(this.getClass().getName() + "[" + name + "] cannot process request of type: " + request.getClass().getName());
return null;
}
String name = userDetails.getUsername();
String email = null;
if (userDetails instanceof Mailable) {
email = ((Mailable) userDetails).getEmailAddress();
if (name == null) {
name = email;
}
}
if (email == null) {
email = generateEmailIfNull(name);
}
String givenName = null;
String familyName = null;
if (userDetails instanceof Named) {
Named names = (Named) userDetails;
givenName = names.getGivenName();
familyName = names.getFamilyName();
}
String phoneNumber = (userDetails instanceof DialableByPhone) ? ((DialableByPhone) userDetails).getPhoneNumber() : null;
String externalId = (userDetails instanceof ExternallyIdentifiable) ? ((ExternallyIdentifiable) userDetails).getExternalId() : name;
UaaUserPrototype userPrototype = new UaaUserPrototype()
.withUsername(name)
.withPassword("")
.withEmail(email)
.withAuthorities(UaaAuthority.USER_AUTHORITIES)
.withGivenName(givenName)
.withFamilyName(familyName)
.withCreated(new Date())
.withModified(new Date())
.withOrigin(origin)
.withExternalId(externalId)
.withZoneId(IdentityZoneHolder.get().getId())
.withPhoneNumber(phoneNumber);
return new UaaUser(userPrototype);
}
protected String generateEmailIfNull(String name) {
String email;
if (name != null) {
if (name.contains("@")) {
if (name.split("@").length == 2 && !name.startsWith("@") && !name.endsWith("@")) {
email = name;
} else {
email = name.replaceAll("@", "") + "@user.from." + getOrigin() + ".cf";
}
} else {
email = name + "@user.from." + getOrigin() + ".cf";
}
} else {
throw new BadCredentialsException("Cannot determine username from credentials supplied");
}
return email;
}
protected boolean haveUserAttributesChanged(UaaUser existingUser, UaaUser user) {
if (!StringUtils.equals(existingUser.getGivenName(), user.getGivenName()) || !StringUtils.equals(existingUser.getFamilyName(), user.getFamilyName()) ||
!StringUtils.equals(existingUser.getPhoneNumber(), user.getPhoneNumber()) || !StringUtils.equals(existingUser.getEmail(), user.getEmail())) {
return true;
}
return false;
}
@Override
public void setBeanName(String name) {
this.name = name;
}
}