/**
* Copyright (C) 2015 Valkyrie RCP
*
* Licensed 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.valkyriercp.security.support;
import com.google.common.collect.Maps;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
import org.valkyriercp.binding.form.FormModel;
import org.valkyriercp.core.AuthorityConfigurable;
import org.valkyriercp.core.Authorizable;
import org.valkyriercp.core.Secured;
import org.valkyriercp.security.SecurityController;
import java.lang.ref.WeakReference;
import java.util.*;
/**
* Class geinspireerd door
* {@link AbstractSecurityController}.
*
* Verschilt in opzet doordat deze elk controlledObject bevraagt op zijn
* Authorities om daarna deze af te checken aan de
* {@link AccessDecisionManager}. Dit in tegenstelling tot
* AbstractSecurityController die een set van regels afchecked voor alle
* controlledObjects.
*
* @author jh
*
*/
public class AuthorityConfigurableSecurityController implements SecurityController
{
private final Log logger = LogFactory.getLog(getClass());
/** The list of objects that we are controlling. */
private List<WeakReference<Authorizable>> controlledObjects = new ArrayList<WeakReference<Authorizable>>();
/** The AccessDecisionManager used to make the "authorize" decision. */
private AccessDecisionManager accessDecisionManager;
/** Last known authentication token. */
private Authentication lastAuthentication = null;
/** Specific configAttributeDefinition overriding others. */
private List<ConfigAttribute> configAttributeDefinition;
/** Id bound configAttributeDefinitions. */
private Map<String, List<ConfigAttribute>> idConfigAttributeDefinitionMap = Maps.newHashMap();
/**
* Last authentication token = currently used.
*/
protected void setLastAuthentication(Authentication authentication)
{
lastAuthentication = authentication;
}
/**
* Each entry of the idConfigAttributeDefinitionMap contains a SecurityControllerId
* together with its actionClusters. When using this SecurityController as
* FallBack, this map can be used to configure commands that are
* declared/used in code.
*
* @param idAuthorityMap
*/
public void setIdAuthorityMap(Map<String, String> idAuthorityMap)
{
idConfigAttributeDefinitionMap = new HashMap<String, List<ConfigAttribute>>(idAuthorityMap
.size());
for (Map.Entry<String, String> entry : idAuthorityMap.entrySet())
{
idConfigAttributeDefinitionMap.put(entry.getKey(), SecurityConfig.createListFromCommaDelimitedString(entry.getValue()));
}
}
/**
* Last authentication token = currently used.
*/
protected Authentication getLastAuthentication()
{
return lastAuthentication;
}
/**
* Add an object to our controlled set.
*
* @param object
* to control
*/
public void addControlledObject(Authorizable object)
{
addAndPrepareControlledObject(object);
}
/**
* Add a new object to the list of controlled objects. Install our last
* known authorization decision so newly created objects will reflect the
* current security state.
*
* @param controlledObject
* to add
*/
private void addAndPrepareControlledObject(Authorizable controlledObject)
{
controlledObjects.add(new WeakReference<Authorizable>(controlledObject));
// Properly configure the new object
boolean authorize = shouldAuthorize(getLastAuthentication(), controlledObject);
updateControlledObject(controlledObject, authorize);
}
/**
* Update a controlled object based on the given authorization state.
*
* @param controlledObject
* Object being controlled
* @param authorized
* state that has been installed on controlledObject
*/
protected void updateControlledObject(Authorizable controlledObject, boolean authorized)
{
if (logger.isDebugEnabled())
{
logger.debug("setAuthorized( " + authorized + ") on: " + controlledObject);
}
controlledObject.setAuthorized(authorized);
}
/**
* Determine if our controlled objects should be authorized based on the
* provided authentication token.
*
* @param authentication
* token
* @return true if should authorize
*/
protected boolean shouldAuthorize(Authentication authentication, Authorizable controlledObject)
{
Assert.state(getAccessDecisionManager() != null, "The AccessDecisionManager can not be null!");
boolean authorize = false;
try
{
if (authentication != null)
{
List<ConfigAttribute> cad = getConfigAttributeDefinition(controlledObject);
if (cad != null)
{
getAccessDecisionManager().decide(authentication, null, cad);
}
authorize = true;
} else {
// authentication must be disabled, going through
authorize = true;
}
}
catch (AccessDeniedException e)
{
authorize = false;
// This means the secured objects should not be authorized
}
return authorize;
}
/**
* Get the ConfigAttributeDefinitions with following strategy:
*
* <ol>
* <li>Check the property configAttributeDefinition. If set, this one
* overrides all others.</li>
* <li>Check the configAttributeDefinitionMap. Use the securityControllerId
* of the object to find the appropriate configs. When this
* SecurityController is set up as fallback, the securityControllerId of
* Authorizable objects can be used to link them to a particular
* ActionCluster.</li>
* <li>Check if the controlledObject is actually an
* ActionClusterConfigurable object. If it is, it probably has it's own set
* of ActionClusters defined, so return a configAttributeDefinition object
* conform to this property.
* </ol>
*
*/
private List<ConfigAttribute> getConfigAttributeDefinition(Authorizable controlledObject)
{
// first check on global override
if (configAttributeDefinition != null)
return configAttributeDefinition;
if (controlledObject instanceof Secured)
{
Secured securedObject = (Secured) controlledObject;
if(securedObject.getSecurityControllerId() != null) {
String securityControllerId = securedObject.getSecurityControllerId();
List<ConfigAttribute> cad = idConfigAttributeDefinitionMap.get(securityControllerId);
if (cad != null)
return cad;
}
if(securedObject.getAuthorities() != null) {
return SecurityConfig.createList(((AuthorityConfigurable) controlledObject).getAuthorities());
}
} else if(controlledObject instanceof FormModel) {
FormModel formModel = (FormModel) controlledObject;
if(formModel.getId() != null) {
String securityControllerId = formModel.getId() + ".edit";
List<ConfigAttribute> cad = idConfigAttributeDefinitionMap.get(securityControllerId);
if (cad != null)
return cad;
}
}
return null;
}
public void setConfigAttributeDefinition(List<ConfigAttribute> configAttributeDefinition)
{
this.configAttributeDefinition = configAttributeDefinition;
}
/**
* Set the access decision manager to use
*
* @param accessDecisionManager
*/
public void setAccessDecisionManager(AccessDecisionManager accessDecisionManager)
{
this.accessDecisionManager = accessDecisionManager;
}
/**
* Get the access decision manager in use
*/
public AccessDecisionManager getAccessDecisionManager()
{
return accessDecisionManager;
}
/**
* Remove an object from our controlled set.
*
* @param object
* to remove
* @return object removed or null if not found
*/
public Object removeControlledObject(Authorizable object)
{
Object removed = null;
for (Iterator iter = controlledObjects.iterator(); iter.hasNext();)
{
WeakReference ref = (WeakReference) iter.next();
Authorizable controlledObject = (Authorizable) ref.get();
if (controlledObject == null)
{
// Has been GCed, remove from our list
iter.remove();
}
else if (controlledObject.equals(object))
{
removed = controlledObject;
iter.remove();
}
}
return removed;
}
/**
* Set the objects that are to be controlled. Only beans that implement the
* {@link org.springframework.security.access.event.AuthorizedEvent} interface are processed.
*
* @param secured
* List of objects to control
*/
public void setControlledObjects(List secured)
{
controlledObjects = new ArrayList<WeakReference<Authorizable>>(secured.size());
// Convert to weak references and validate the object types
for (Iterator iter = secured.iterator(); iter.hasNext();)
{
Object o = iter.next();
// Ensure that we got something we can control
if (!(o instanceof Authorizable))
{
throw new IllegalArgumentException("Controlled object must implement Authorizable, got "
+ o.getClass());
}
addAndPrepareControlledObject((Authorizable) o);
}
}
public void setAuthenticationToken(Authentication authentication)
{
setLastAuthentication(authentication); // Keep it for later
runAuthorization();
}
/** securityAwareConfigurer
* Update the authorization of all controlled objects.
*/
protected void runAuthorization()
{
// Install the decision
for (Iterator iter = controlledObjects.iterator(); iter.hasNext();)
{
WeakReference ref = (WeakReference) iter.next();
Authorizable controlledObject = (Authorizable) ref.get();
if (controlledObject == null)
{
// Has been GCed, remove from our list
iter.remove();
}
else
{
updateControlledObject(controlledObject, shouldAuthorize(getLastAuthentication(),
controlledObject));
}
}
}
}