/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jetspeed.security.spi.impl;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.jetspeed.security.JetspeedPrincipal;
import org.apache.jetspeed.security.JetspeedPrincipalAssociationReference;
import org.apache.jetspeed.security.JetspeedPrincipalManager;
import org.apache.jetspeed.security.JetspeedPrincipalManagerProvider;
import org.apache.jetspeed.security.JetspeedPrincipalType;
import org.apache.jetspeed.security.SecurityException;
import org.apache.jetspeed.security.SecurityAttribute;
import org.apache.jetspeed.security.SecurityAttributes;
import org.apache.jetspeed.security.mapping.SecurityEntityManager;
import org.apache.jetspeed.security.mapping.impl.BaseEntitySearchResultHandler;
import org.apache.jetspeed.security.mapping.model.Attribute;
import org.apache.jetspeed.security.mapping.model.AttributeDef;
import org.apache.jetspeed.security.mapping.model.Entity;
import org.apache.jetspeed.security.mapping.model.SecurityEntityRelationType;
import org.apache.jetspeed.security.spi.JetspeedSecuritySynchronizer;
/**
* @author <a href="mailto:ddam@apache.org">Dennis Dam</a>
* @author <a href="mailto:ate@douma.nu>Ate Douma</a>
* @version $Id: DefaultJetspeedSecuritySynchronizer.java 1101700 2011-05-11 00:52:30Z ate $
*/
public class DefaultJetspeedSecuritySynchronizer implements JetspeedSecuritySynchronizer
{
protected static final Logger logger = LoggerFactory.getLogger(JetspeedSecuritySynchronizer.class);
protected static final Logger feedbackLogger = LoggerFactory.getLogger(DefaultJetspeedSecuritySynchronizer.class);
protected JetspeedPrincipalManagerProvider principalManagerProvider;
protected SecurityEntityManager securityEntityManager;
protected boolean abortOnError = true;
protected int feedbackAfterSyncCount = 500;
/**
* @param principalManagerProvider
* @param securityEntityManager
*/
public DefaultJetspeedSecuritySynchronizer(JetspeedPrincipalManagerProvider principalManagerProvider, SecurityEntityManager securityEntityManager)
{
this.principalManagerProvider = principalManagerProvider;
this.securityEntityManager = securityEntityManager;
}
public void setAbortOnError(boolean abort)
{
this.abortOnError = abort;
}
public void setFeedbackAfterSyncCount(int count)
{
this.feedbackAfterSyncCount = count;
}
public synchronized void synchronizeAll() throws SecurityException
{
setSynchronizing(true);
try
{
feedbackLogger.info("Synchronizing all entities");
final Map<String,Set<String>> processing = new HashMap<String,Set<String>>();
final Map<String,Map<String,String>> processed = new HashMap<String,Map<String,String>>();
for (final String type : securityEntityManager.getSupportedEntityTypes())
{
BaseEntitySearchResultHandler handler = new BaseEntitySearchResultHandler()
{
@Override
protected boolean processSearchResult(Entity entity, int pageSize, int pageIndex, int index)
{
try
{
if (feedbackAfterSyncCount > 0 && index % feedbackAfterSyncCount == 0)
{
feedbackLogger.info("Synchronizing {}s - processed: {}", type, index);
}
synchronizeEntity(entity, processing, processed);
}
catch (SecurityException e)
{
setFeedback(e);
return false;
}
return true;
}
};
feedbackLogger.info("Synchronizing all {}s", type);
securityEntityManager.getAllEntities(type,handler);
if (handler.getFeedback() != null)
{
feedbackLogger.error("Synchronizing {}s aborted. Processed: {}", type, handler.getSize());
throw (SecurityException)handler.getFeedback();
}
feedbackLogger.info("Synchronizing {}s done. Processed: {}", type, handler.getSize());
}
feedbackLogger.info("Synchronizing all entities done.");
}
finally
{
setSynchronizing(false);
}
}
public synchronized void synchronizePrincipalsByType(final String type) throws SecurityException
{
setSynchronizing(true);
try
{
final Map<String,Set<String>> processing = new HashMap<String,Set<String>>();
final Map<String,Map<String,String>> processed = new HashMap<String,Map<String,String>>();
BaseEntitySearchResultHandler handler = new BaseEntitySearchResultHandler()
{
@Override
protected boolean processSearchResult(Entity entity, int pageSize, int pageIndex, int index)
{
try
{
if (feedbackAfterSyncCount > 0 && index % feedbackAfterSyncCount == 0)
{
feedbackLogger.info("Synchronizing {}s - processed: {}", type, index);
}
synchronizeEntity(entity, processing, processed);
}
catch (SecurityException e)
{
if (abortOnError)
{
feedbackLogger.error("Failed to synchronize {}: {}", type, entity.getInternalId());
setFeedback(e);
return false;
}
feedbackLogger.warn("Failed to synchronize {}: {}", type, entity.getInternalId());
logger.error("Failed to synchronize "+type+": "+entity, e);
return true;
}
return true;
}
};
feedbackLogger.info("Synchronizing {}s", type);
securityEntityManager.getAllEntities(type, handler);
if (handler.getFeedback() != null)
{
feedbackLogger.error("Synchronizing {}s aborted. Processed: {}", type, handler.getSize());
throw (SecurityException)handler.getFeedback();
}
feedbackLogger.info("Synchronizing {}s done. Processed: {}", type, handler.getSize());
}
finally
{
setSynchronizing(false);
}
}
public synchronized void synchronizeUserPrincipal(String name) throws SecurityException
{
setSynchronizing(true);
try
{
feedbackLogger.debug("Synchronizing UserPrincipal({})", name);
Entity userEntity = securityEntityManager.getEntity(JetspeedPrincipalType.USER, name);
if (userEntity != null)
{
synchronizeEntity(userEntity, new HashMap<String,Set<String>>(), new HashMap<String,Map<String,String>>());
}
else
{
throw new SecurityException(SecurityException.PRINCIPAL_DOES_NOT_EXIST.createScoped(JetspeedPrincipalType.USER, name));
}
}
finally
{
setSynchronizing(false);
}
}
protected JetspeedPrincipal synchronizeEntity(final Entity entity, final Map<String,Set<String>> processing, final Map<String,Map<String,String>> processed) throws SecurityException
{
JetspeedPrincipal principal = null;
if (processing != null && processing.get(entity.getType()) != null && processing.get(entity.getType()).contains(entity.getId()))
{
// TODO: throw proper security exception type
throw new IllegalStateException("Circular relationship detected for Entity type "+entity.getType()+" id: "+entity.getId());
}
if (processed.get(entity.getType()) != null && processed.get(entity.getType()).containsKey(entity.getId()))
{
String principalName = processed.get(entity.getType()).get(entity.getId());
return principalName != null ? getJetspeedPrincipal(entity.getType(),principalName) : null;
}
feedbackLogger.debug("Synchronizing entity {} id: {}",entity.getType(),entity.getId());
// synchronize and collect Entity from relations first
Set<JetspeedPrincipalAssociationReference> toAssociations = synchronizeEntityFromRelations(entity, processing, processed);
// create or update entity itself including all its from associations
principal = synchronizeEntity(entity, toAssociations);
Map<String,String> entitiesMap = processed.get(entity.getType());
if (entitiesMap == null)
{
entitiesMap = new HashMap<String,String>();
processed.put(entity.getType(), entitiesMap);
}
entitiesMap.put(entity.getId(), entity.getId());
return principal;
}
protected Set<JetspeedPrincipalAssociationReference> synchronizeEntityFromRelations(final Entity entity, final Map<String,Set<String>> processing, final Map<String,Map<String,String>> processed) throws SecurityException
{
final Set<JetspeedPrincipalAssociationReference> toAssociations = new HashSet<JetspeedPrincipalAssociationReference>();
// loop through all relation types for this entity type
for (final SecurityEntityRelationType relationTypeForThisEntity : securityEntityManager.getSupportedEntityRelationTypes(entity.getType()))
{
if (relationTypeForThisEntity.getFromEntityType().equals(entity.getType()))
{
final String toEntityType = relationTypeForThisEntity.getToEntityType();
final Map<String,String> processedToType = processed.containsKey(toEntityType) ? processed.get(toEntityType) : new HashMap<String,String>();
final Set<String> processingToType = processing.containsKey(toEntityType) ? processing.get(toEntityType) : null;
BaseEntitySearchResultHandler handler = new BaseEntitySearchResultHandler()
{
@Override
protected boolean processSearchResult(Entity relatedEntity, int pageSize, int pageIndex, int index)
{
try
{
JetspeedPrincipal principal = null;
if (processingToType != null && processingToType.contains(relatedEntity.getId()))
{
// TODO: throw proper security exception type
throw new IllegalStateException("Circular relationship detected for Entity type "+toEntityType+" id: "+relatedEntity.getId());
}
else if (processedToType != null && processedToType.containsKey(relatedEntity.getId()))
{
String principalName = processed.get(relatedEntity.getType()).get(relatedEntity.getId());
principal = principalName != null ? getJetspeedPrincipal(relatedEntity.getType(),principalName) : null;
}
else
{
Set<String> processingFromType = processing.get(entity.getType());
if (processingFromType == null)
{
processingFromType = new HashSet<String>();
processing.put(entity.getType(), processingFromType);
}
processingFromType.add(entity.getId());
principal = synchronizeEntity(relatedEntity, processing, processed);
}
if (principal != null)
{
toAssociations.add(new JetspeedPrincipalAssociationReference(JetspeedPrincipalAssociationReference.Type.TO, principal, relationTypeForThisEntity.getRelationType()));
}
}
catch (SecurityException e)
{
setFeedback(e);
return false;
}
return true;
}
};
securityEntityManager.getRelatedEntitiesFrom(entity, relationTypeForThisEntity, handler);
if (handler.getFeedback() != null)
{
throw (SecurityException)handler.getFeedback();
}
Set<String> processingFromType = processing.get(entity.getType());
if (processingFromType != null)
{
processingFromType.remove(entity.getId());
}
}
}
return toAssociations;
}
protected JetspeedPrincipal synchronizeEntity(Entity entity, Set<JetspeedPrincipalAssociationReference> toAssociations) throws SecurityException
{
JetspeedPrincipal principal = getJetspeedPrincipal(entity.getType(), entity.getId());
JetspeedPrincipalManager principalManager = principalManagerProvider.getManager(principalManagerProvider.getPrincipalType(entity.getType()));
boolean syncAll = false;
if (principal == null)
{
// principal does not exist yet, create a new one using the principal manager
principal = principalManager.newPrincipal(entity.getId(), true);
principalManager.addPrincipal(principal, toAssociations);
syncAll = true;
}
else if (!principal.isMapped())
{
feedbackLogger.warn("Found {} principal: {} is not mapped therefore not synchronized!", principal.getType().getName(),principal.getName());
return null;
}
else
{
// sync relations
for (final SecurityEntityRelationType relationType : securityEntityManager.getSupportedEntityRelationTypes(entity.getType()))
{
if (relationType.getFromEntityType().equals(entity.getType()))
{
List<? extends JetspeedPrincipal> associatedFrom = principalManager.getAssociatedFrom(principal.getName(), principal.getType(), relationType.getRelationType());
for (JetspeedPrincipal p : associatedFrom)
{
if (toAssociations.isEmpty() ||
!toAssociations.remove(new JetspeedPrincipalAssociationReference(JetspeedPrincipalAssociationReference.Type.TO, p, relationType.getRelationType())))
{
principalManager.removeAssociation(principal, p, relationType.getRelationType());
}
}
}
}
for (JetspeedPrincipalAssociationReference ref : toAssociations)
{
principalManager.addAssociation(principal, ref.ref, ref.associationName);
}
}
boolean updated = false;
SecurityAttributes principalAttrs = principal.getSecurityAttributes();
for (AttributeDef attrDef : entity.getAttributeDefinitions())
{
if (attrDef.isMapped() && !attrDef.isMultiValue())
{
Attribute attr = entity.getAttribute(attrDef.getName());
if (attr == null)
{
if (!syncAll)
{
// if principal has attr: remove it
SecurityAttribute principalAttr = principalAttrs.getAttribute(attrDef.getMappedName());
if (principalAttr != null)
{
feedbackLogger.debug("Removing attribute {} for principal {}", principalAttr.getName(), principal.getName());
principalAttrs.removeAttribute(principalAttr.getName());
updated = true;
}
}
}
else if (syncAll)
{
SecurityAttribute principalAttr = principalAttrs.getAttribute(attrDef.getMappedName(), true);
if (feedbackLogger.isDebugEnabled())
{
feedbackLogger.debug("Adding attribute {} for principal {}. Value: {}",
new String[] {principalAttr.getName(), principal.getName(), attr.getValue()});
}
principalAttr.setStringValue(attr.getValue());
updated = true;
}
else
{
SecurityAttribute principalAttr = principalAttrs.getAttribute(attrDef.getMappedName(), true);
if (!StringUtils.equals(principalAttr.getStringValue(), attr.getValue()))
{
if (feedbackLogger.isDebugEnabled())
{
feedbackLogger.debug("Attribute attribute {} for principal {}. Old value: {}, new value: {}",
new String[] {principalAttr.getName(), principal.getName(), (principalAttr.getStringValue()), attr.getValue()});
}
principalAttr.setStringValue(attr.getValue());
updated = true;
}
}
}
}
if (updated)
{
feedbackLogger.debug("Storing attribute changes for principal {}", principal.getName());
principalManager.updatePrincipal(principal);
}
feedbackLogger.debug("Synchronized entity {} id: {} mapped attributes", entity.getType(), entity.getId());
return principal;
}
protected JetspeedPrincipal getJetspeedPrincipal(String principalType, String principalName) throws SecurityException
{
JetspeedPrincipalManager principalManager = principalManagerProvider.getManager(principalManagerProvider.getPrincipalType(principalType));
if (principalManager != null)
{
return principalManager.getPrincipal(principalName);
}
throw new SecurityException(SecurityException.UNKNOWN_PRINCIPAL_TYPE.create(principalType));
}
protected void setSynchronizing(boolean sync)
{
SynchronizationStateAccess.setSynchronizing(sync ? Boolean.TRUE : Boolean.FALSE);
}
public void setPrincipalManagerProvider(JetspeedPrincipalManagerProvider principalManagerProvider)
{
this.principalManagerProvider = principalManagerProvider;
}
}