/*
* Copyright 2000-2013 Enonic AS
* http://www.enonic.com/license
*/
package com.enonic.cms.core.security.userstore.connector.remote;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import com.enonic.cms.api.plugin.ext.userstore.RemoteUserStore;
import com.enonic.cms.core.security.group.GroupEntity;
import com.enonic.cms.core.security.group.GroupKey;
import com.enonic.cms.core.security.group.GroupSpecification;
import com.enonic.cms.core.security.group.GroupType;
import com.enonic.cms.core.security.group.StoreNewGroupCommand;
import com.enonic.cms.core.security.user.DisplayNameResolver;
import com.enonic.cms.core.security.user.StoreNewUserCommand;
import com.enonic.cms.core.security.user.UserEntity;
import com.enonic.cms.core.security.user.UserKey;
import com.enonic.cms.core.security.user.UserSpecification;
import com.enonic.cms.core.security.user.UserType;
import com.enonic.cms.core.security.userstore.GroupStorer;
import com.enonic.cms.core.security.userstore.UserStoreEntity;
import com.enonic.cms.core.security.userstore.UserStoreKey;
import com.enonic.cms.core.security.userstore.UserStorer;
import com.enonic.cms.api.plugin.ext.userstore.UserStoreConfig;
import com.enonic.cms.core.security.userstore.connector.config.UserStoreConnectorConfig;
import com.enonic.cms.core.security.userstore.connector.synchronize.status.SynchronizeStatus;
import com.enonic.cms.core.time.TimeService;
import com.enonic.cms.api.plugin.ext.userstore.UserFields;
import com.enonic.cms.api.plugin.ext.userstore.RemoteGroup;
import com.enonic.cms.api.plugin.ext.userstore.RemoteUser;
import com.enonic.cms.store.dao.GroupDao;
import com.enonic.cms.store.dao.UserDao;
public abstract class AbstractBaseUserSynchronizer
{
protected UserStorer userStorer;
private GroupStorer groupStorer;
protected final UserStoreEntity userStore;
protected final boolean syncMemberships;
protected RemoteUserStore remoteUserStorePlugin;
protected UserDao userDao;
protected GroupDao groupDao;
protected TimeService timeService;
protected UserStoreConnectorConfig connectorConfig;
protected UserStoreConfig userStoreConfig;
protected SynchronizeStatus status = null;
protected final boolean syncUser;
protected final boolean createMissingGroupsLocallyForMemberships;
protected AbstractBaseUserSynchronizer( final SynchronizeStatus synchronizeStatus, final UserStoreEntity userStore,
final boolean syncUser, final boolean syncMemberships,
final boolean createMissingGroupsLocallyForMemberships )
{
this.status = synchronizeStatus;
this.userStore = userStore;
this.syncMemberships = syncMemberships;
this.userStoreConfig = userStore.getConfig();
this.syncUser = syncUser;
this.createMissingGroupsLocallyForMemberships = createMissingGroupsLocallyForMemberships;
}
protected UserStoreKey getUserStoreKey()
{
return userStore.getKey();
}
UserEntity createUser( final RemoteUser remoteUser, final MemberCache memberCache )
{
final UserFields userFields = remoteUser.getUserFields().getConfiguredFieldsOnly( userStoreConfig );
userFields.retain( userStoreConfig.getRemoteOnlyUserFieldTypes() );
final StoreNewUserCommand storeNewUserCommand = new StoreNewUserCommand();
storeNewUserCommand.setUserStoreKey( userStore.getKey() );
storeNewUserCommand.setUsername( remoteUser.getId() );
storeNewUserCommand.setSyncValue( remoteUser.getSync() );
storeNewUserCommand.setEmail( remoteUser.getEmail() );
storeNewUserCommand.setType( UserType.NORMAL );
storeNewUserCommand.setUserFields( userFields );
if ( syncMemberships )
{
final List<RemoteGroup> remoteMemberships = remoteUserStorePlugin.getMemberships( remoteUser );
for ( RemoteGroup remoteGroup : remoteMemberships )
{
GroupEntity groupToBeMemberOf = findLocalGroup( remoteGroup, memberCache );
if ( groupToBeMemberOf == null && createMissingGroupsLocallyForMemberships )
{
groupToBeMemberOf = createGroup( remoteGroup );
}
if ( groupToBeMemberOf != null )
{
storeNewUserCommand.addMembership( groupToBeMemberOf.getGroupKey() );
}
}
}
final UserKey newUserKey = userStorer.storeNewUser( storeNewUserCommand, new DisplayNameResolver( userStoreConfig ) );
return userDao.findByKey( newUserKey );
}
UserEntity findUserBySyncValue( String syncValue )
{
final UserSpecification spec = new UserSpecification();
spec.setUserStoreKey( userStore.getKey() );
spec.setSyncValue( syncValue );
spec.setDeletedState( UserSpecification.DeletedState.ANY );
return userDao.findSingleBySpecification( spec );
}
UserEntity findUserByName( String uid )
{
final UserSpecification spec = new UserSpecification();
spec.setUserStoreKey( userStore.getKey() );
spec.setName( uid );
spec.setDeletedState( UserSpecification.DeletedState.ANY );
return userDao.findSingleBySpecification( spec );
}
protected boolean updateAndResurrectUser( final UserEntity localUser, final RemoteUser remoteUser )
{
boolean resurrected = false;
boolean modified = false;
// force resurrection
if ( localUser.isDeleted() )
{
resurrected = true;
localUser.setDeleted( false );
if ( localUser.getUserGroup() != null )
{
localUser.getUserGroup().setDeleted( false );
}
modified = true;
}
if ( updateUserModifiableProperties( localUser, remoteUser ) )
{
modified = true;
}
if ( modified )
{
localUser.setTimestamp( timeService.getNowAsDateTime() );
}
return resurrected;
}
protected String getNameToVerify( final UserEntity localUser, final RemoteUser remoteUser )
{
final String remoteName = remoteUser != null ? remoteUser.getId() : null;
if ( StringUtils.isNotBlank( remoteName ) )
{
return remoteName;
}
final String localName = localUser != null ? localUser.getName() : null;
if ( StringUtils.isNotBlank( localName ) )
{
return localName;
}
return null;
}
protected String getEmailToVerify( final UserEntity localUser, final RemoteUser remoteUser )
{
final String remoteEmail = remoteUser != null ? remoteUser.getEmail() : null;
if ( StringUtils.isNotBlank( remoteEmail ) )
{
return remoteEmail;
}
final String localEmail = localUser != null ? localUser.getEmail() : null;
if ( StringUtils.isNotBlank( localEmail ) )
{
return localEmail;
}
return null;
}
protected boolean nameAlreadyUsedByOtherUser( final String name, final UserEntity localUser )
{
if ( name == null )
{
return false;
}
final UserSpecification userByEmailSpec = new UserSpecification();
userByEmailSpec.setName( name );
userByEmailSpec.setUserStoreKey( getUserStoreKey() );
userByEmailSpec.setDeletedStateNotDeleted();
return isOtherThanMeFound( userByEmailSpec, localUser );
}
protected UserEntity getOtherUserWithSameEmail( final String email, final UserEntity localUser )
{
if ( email == null )
{
return null;
}
final UserSpecification userByEmailSpec = new UserSpecification();
userByEmailSpec.setEmail( email );
userByEmailSpec.setUserStoreKey( getUserStoreKey() );
userByEmailSpec.setDeletedStateNotDeleted();
return findOtherThanMe( userByEmailSpec, localUser );
}
protected boolean emailAlreadyUsedByOtherUser( final String email, final UserEntity localUser )
{
return getOtherUserWithSameEmail( email, localUser ) != null;
}
private boolean isOtherThanMeFound( final UserSpecification specification, final UserEntity me )
{
return findOtherThanMe( specification, me ) != null;
}
private UserEntity findOtherThanMe( final UserSpecification specification, final UserEntity me )
{
final List<UserEntity> users = userDao.findBySpecification( specification );
if ( me != null )
{
final boolean oneEntityFoundAndItsMe = users.size() == 1 && me.equals( users.get( 0 ) );
if ( oneEntityFoundAndItsMe )
{
return null;
}
return getOtherThanMe( users, me );
}
return users.isEmpty() ? null : users.get( 0 );
}
private UserEntity getOtherThanMe( List<UserEntity> users, UserEntity me )
{
for ( UserEntity user : users )
{
if ( !user.equals( me ) )
{
return user;
}
}
return null;
}
protected void synchronizeOtherUserWithSameEmail( final String email, final UserEntity localUser )
{
UserEntity otherUserWithSameEmail = getOtherUserWithSameEmail( email, localUser );
if ( otherUserWithSameEmail != null )
{
final RemoteUser remoteUser = remoteUserStorePlugin.getUser( otherUserWithSameEmail.getName() );
if ( remoteUser == null )
{
deleteUser( otherUserWithSameEmail );
}
}
}
private boolean updateUserModifiableProperties( final UserEntity userToModify, final RemoteUser remoteUser )
{
final DisplayNameResolver displayNameResolver = new DisplayNameResolver( userStoreConfig );
final boolean displayNameManuallyEdited = displayNameManuallyEdited( displayNameResolver, userToModify );
boolean modified = false;
if ( !equals( userToModify.getEmail(), remoteUser.getEmail() ) )
{
userToModify.setEmail( remoteUser.getEmail() );
modified = true;
}
if ( !equals( userToModify.getName(), remoteUser.getId() ) )
{
userToModify.setName( remoteUser.getId() );
modified = true;
}
final UserFields remoteUserFields = remoteUser.getUserFields().getConfiguredFieldsOnly( userStoreConfig );
remoteUserFields.retain(
userStoreConfig.getRemoteOnlyUserFieldTypes() ); // TODO: isnt not this unecessary since these are coming from removeUser?
// must only clear and update fields that are remote, otherwise locally stored user fields are lost
final UserFields userFieldsToModify = userToModify.getUserFields().getConfiguredFieldsOnly( userStoreConfig );
userFieldsToModify.replaceAllRemoteFieldsOnly( remoteUserFields, userStoreConfig );
final boolean modifiedUserFields = userToModify.setUserFields( userFieldsToModify );
modified = modified || modifiedUserFields;
if ( !displayNameManuallyEdited )
{
userToModify.setDisplayName( displayNameResolver.resolveDisplayName( userToModify.getName(), userToModify.getDisplayName(),
userToModify.getUserFields() ) );
}
return modified;
}
private boolean displayNameManuallyEdited( final DisplayNameResolver displayNameResolver, UserEntity user )
{
final String displayNameGeneratedFromExistingUser =
displayNameResolver.resolveDisplayName( user.getName(), user.getDisplayName(), user.getUserFields() );
final String existingDisplayName = user.getDisplayName();
return !displayNameGeneratedFromExistingUser.equals( existingDisplayName );
}
protected void syncUserMemberships( final UserEntity localUser, final RemoteUser remoteUser, final MemberCache memberCache )
{
final List<RemoteGroup> remoteMemberships = remoteUserStorePlugin.getMemberships( remoteUser );
removeLocalUserMembershipsNotExistingRemote( localUser, remoteMemberships );
final GroupEntity userGroup = localUser.getUserGroup();
for ( final RemoteGroup remoteMembership : remoteMemberships )
{
syncGroupMembershipOfTypeGroup( userGroup, remoteMembership, memberCache );
}
}
private void syncGroupMembershipOfTypeGroup( final GroupEntity localGroup, final RemoteGroup remoteGroupMember,
final MemberCache memberCache )
{
final GroupEntity existingMember = findLocalGroup( remoteGroupMember, memberCache );
if ( existingMember == null )
{
// skip creation - only supported in full sync
}
else
{
if ( localGroup.hasMembership( existingMember ) )
{
// all is fine
if ( status != null )
{
status.userMembershipVerified();
}
}
else
{
localGroup.addMembership( existingMember );
if ( status != null )
{
status.userMembershipCreated();
}
}
}
}
private GroupEntity findLocalGroup( final RemoteGroup remoteGroup, final MemberCache memberCache )
{
final GroupSpecification spec = createGroupSpecification( remoteGroup );
GroupEntity existingMember = memberCache.getMemberOfTypeGroup( spec );
if ( existingMember == null )
{
existingMember = groupDao.findSingleBySpecification( spec );
if ( existingMember != null )
{
memberCache.addMemeberOfTypeGroup( existingMember );
}
}
return existingMember;
}
private GroupSpecification createGroupSpecification( final RemoteGroup remoteGroup )
{
final GroupSpecification spec = new GroupSpecification();
spec.setUserStoreKey( getUserStoreKey() );
spec.setSyncValue( remoteGroup.getSync() );
return spec;
}
protected void removeLocalUserMembershipsNotExistingRemote( final UserEntity localUser, final List<RemoteGroup> remoteMemberships )
{
// Gather remote users in a map for fast and easy access
final Map<String, RemoteGroup> remoteMembershipsMap = new HashMap<String, RemoteGroup>();
for ( final RemoteGroup remoteMembership : remoteMemberships )
{
remoteMembershipsMap.put( remoteMembership.getId() + "-" + remoteMembership.getSync(), remoteMembership );
}
final GroupEntity userGroup = localUser.getUserGroup();
// Gather local memberships that does not exist remote
final Set<GroupEntity> localMembershipsToRemove = new HashSet<GroupEntity>();
for ( final GroupEntity localMembership : userGroup.getMemberships( false ) )
{
// We're not removing memberships in built-in or global groups
if ( !localMembership.isBuiltIn() && !localMembership.isGlobal() )
{
final RemoteGroup remoteMembership =
remoteMembershipsMap.get( localMembership.getName() + "-" + localMembership.getSyncValue() );
if ( remoteMembership == null )
{
localMembershipsToRemove.add( localMembership );
}
}
}
// Remove local memberships that does not exist remote
for ( final GroupEntity localMembershipToRemove : localMembershipsToRemove )
{
userGroup.removeMembership( localMembershipToRemove );
if ( status != null )
{
status.userMembershipDeleted();
}
}
}
protected void deleteUser( final UserEntity localUser )
{
if ( !localUser.isDeleted() )
{
final UserSpecification userToDeleteSpec = new UserSpecification();
userToDeleteSpec.setKey( localUser.getKey() );
userStorer.deleteUser( userToDeleteSpec );
if ( status != null )
{
status.userDeleted();
}
}
}
private boolean equals( Object a, Object b )
{
if ( a == null && b == null )
{
return true;
}
else if ( a == null || b == null )
{
return false;
}
return a.equals( b );
}
private GroupEntity createGroup( final RemoteGroup remoteGroup )
{
final StoreNewGroupCommand storeNewGroupCommand = new StoreNewGroupCommand();
storeNewGroupCommand.setName( remoteGroup.getId() );
storeNewGroupCommand.setSyncValue( remoteGroup.getSync() );
storeNewGroupCommand.setRestriced( true );
storeNewGroupCommand.setType( GroupType.USERSTORE_GROUP );
storeNewGroupCommand.setUserStoreKey( userStore.getKey() );
GroupKey groupKey = groupStorer.storeNewGroup( storeNewGroupCommand );
return groupDao.findByKey( groupKey );
}
public void setRemoteUserStorePlugin( final RemoteUserStore value )
{
this.remoteUserStorePlugin = value;
}
public void setTimeService( final TimeService value )
{
this.timeService = value;
}
public void setUserDao( final UserDao value )
{
this.userDao = value;
}
public void setGroupDao( final GroupDao value )
{
this.groupDao = value;
}
public void setConnectorConfig( final UserStoreConnectorConfig value )
{
this.connectorConfig = value;
}
public void setUserStorer( UserStorer value )
{
this.userStorer = value;
}
public void setGroupStorer( GroupStorer groupStorer )
{
this.groupStorer = groupStorer;
}
}