/* ==================================================================
* DatumMetadataSecurityAspect.java - Oct 3, 2014 4:21:36 PM
*
* Copyright 2007-2014 SolarNetwork.net Dev Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
* ==================================================================
*/
package net.solarnetwork.central.datum.aop;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.security.core.Authentication;
import org.springframework.util.AntPathMatcher;
import net.solarnetwork.central.datum.biz.DatumMetadataBiz;
import net.solarnetwork.central.datum.domain.GeneralNodeDatumMetadataFilter;
import net.solarnetwork.central.datum.domain.NodeSourcePK;
import net.solarnetwork.central.security.AuthorizationException;
import net.solarnetwork.central.security.SecurityPolicy;
import net.solarnetwork.central.security.SecurityPolicyEnforcer;
import net.solarnetwork.central.security.SecurityUtils;
import net.solarnetwork.central.user.dao.UserNodeDao;
import net.solarnetwork.central.user.support.AuthorizationSupport;
/**
* Security AOP support for {@link DatumMetadataBiz}.
*
* @author matt
* @version 1.2
*/
@Aspect
public class DatumMetadataSecurityAspect extends AuthorizationSupport {
/**
* The default value for the {@link #setLocaitonMetadataAdminRoles(Set)}
* property, contains the single role {@code ROLE_LOC_META_ADMIN}.
*/
public static final Set<String> DEFAULT_LOCATION_METADATA_ADMIN_ROLES = Collections
.singleton("ROLE_LOC_META_ADMIN");
private Set<String> locaitonMetadataAdminRoles = DEFAULT_LOCATION_METADATA_ADMIN_ROLES;
/**
* Constructor.
*
* @param userNodeDao
* the UserNodeDao to use
*/
public DatumMetadataSecurityAspect(UserNodeDao userNodeDao) {
super(userNodeDao);
AntPathMatcher antMatch = new AntPathMatcher();
antMatch.setCachePatterns(false);
antMatch.setCaseSensitive(true);
setPathMatcher(antMatch);
}
@Pointcut("bean(aop*) && execution(* net.solarnetwork.central.datum.biz.DatumMetadata*.addGeneralNode*(..)) && args(nodeId,..)")
public void addMetadata(Long nodeId) {
}
@Pointcut("bean(aop*) && execution(* net.solarnetwork.central.datum.biz.DatumMetadata*.storeGeneralNode*(..)) && args(nodeId,..)")
public void storeMetadata(Long nodeId) {
}
@Pointcut("bean(aop*) && execution(* net.solarnetwork.central.datum.biz.DatumMetadata*.removeGeneralNode*(..)) && args(nodeId,..)")
public void removeMetadata(Long nodeId) {
}
@Pointcut("bean(aop*) && execution(* net.solarnetwork.central.datum.biz.DatumMetadata*.findGeneralNode*(..)) && args(filter,..)")
public void findMetadata(GeneralNodeDatumMetadataFilter filter) {
}
@Pointcut("bean(aop*) && execution(* net.solarnetwork.central.datum.biz.DatumMetadata*.getGeneralNodeDatumMetadataFilteredSources(..)) && args(nodeIds,..)")
public void getMetadataFilteredSources(Long[] nodeIds) {
}
@Pointcut("bean(aop*) && execution(* net.solarnetwork.central.datum.biz.DatumMetadata*.addGeneralLocation*(..)) && args(locationId,..)")
public void addLocationMetadata(Long locationId) {
}
@Pointcut("bean(aop*) && execution(* net.solarnetwork.central.datum.biz.DatumMetadata*.storeGeneralLocation*(..)) && args(locationId,..)")
public void storeLocationMetadata(Long locationId) {
}
@Pointcut("bean(aop*) && execution(* net.solarnetwork.central.datum.biz.DatumMetadata*.removeGeneralLocation*(..)) && args(locationId,..)")
public void removeLocationMetadata(Long locationId) {
}
/**
* Check access to modifying datum metadata.
*
* @param nodeId
* the ID of the node to verify
*/
@Before("addMetadata(nodeId) || storeMetadata(nodeId) || removeMetadata(nodeId)")
public void updateMetadataCheck(Long nodeId) {
requireNodeWriteAccess(nodeId);
}
/**
* Check access to reading datum metadata.
*
* @param nodeId
* the ID of the node to verify
*/
@Before("findMetadata(filter)")
public void readMetadataCheck(GeneralNodeDatumMetadataFilter filter) {
requireNodeReadAccess(filter == null ? null : filter.getNodeId());
}
/**
* Enforce node ID and source ID policy restrictions when requesting the
* available sources for node datum metadata.
*
* First the node ID is verified. Then, for all returned source ID values,
* if the active policy has no source ID restrictions return all values,
* otherwise remove any value not included in the policy.
*
* @param pjp
* The join point.
* @param nodeIds
* The node IDs.
* @return The set of NodeSourcePK results.
* @throws Throwable
* @since 1.2
*/
@Around("getMetadataFilteredSources(nodeIds)")
public Object filteredMetadataSourcesAccessCheck(ProceedingJoinPoint pjp, Long[] nodeIds)
throws Throwable {
// verify node IDs
if ( nodeIds != null ) {
for ( Long nodeId : nodeIds ) {
requireNodeReadAccess(nodeId);
}
}
// verify source IDs in result
@SuppressWarnings("unchecked")
Set<NodeSourcePK> result = (Set<NodeSourcePK>) pjp.proceed();
if ( result == null || result.isEmpty() ) {
return result;
}
SecurityPolicy policy = getActiveSecurityPolicy();
if ( policy == null ) {
return result;
}
Set<String> allowedSourceIds = policy.getSourceIds();
if ( allowedSourceIds == null || allowedSourceIds.isEmpty() ) {
return result;
}
Authentication authentication = SecurityUtils.getCurrentAuthentication();
Object principal = (authentication != null ? authentication.getPrincipal() : null);
SecurityPolicyEnforcer enforcer = new SecurityPolicyEnforcer(policy, principal, null,
getPathMatcher());
try {
List<String> inputSourceIds = new ArrayList<String>(result.size());
for ( NodeSourcePK pk : result ) {
inputSourceIds.add(pk.getSourceId());
}
String[] resultSourceIds = enforcer
.verifySourceIds(inputSourceIds.toArray(new String[inputSourceIds.size()]));
Set<String> allowedSourceIdSet = new HashSet<String>(Arrays.asList(resultSourceIds));
Set<NodeSourcePK> restricted = new LinkedHashSet<NodeSourcePK>(resultSourceIds.length);
for ( NodeSourcePK oneResult : result ) {
if ( allowedSourceIdSet.contains(oneResult.getSourceId()) ) {
restricted.add(oneResult);
}
}
result = restricted;
} catch ( AuthorizationException e ) {
// ignore, and just map to empty set
result = Collections.emptySet();
}
return result;
}
@Before("addLocationMetadata(locationId) || storeLocationMetadata(locationId) || removeLocationMetadata(locationId)")
public void updateLocationMetadataCheck(Long locationId) {
SecurityUtils.requireAnyRole(locaitonMetadataAdminRoles);
}
/**
* Set the set of roles required to administer location metadata. If more
* than one role is provided, any one role must match for the authorization
* to succeed.
*
* @param locaitonMetadataAdminRoles
* the set of roles
* @since 1.2
*/
public void setLocaitonMetadataAdminRoles(Set<String> locaitonMetadataAdminRoles) {
if ( locaitonMetadataAdminRoles == null || locaitonMetadataAdminRoles.size() < 1 ) {
throw new IllegalArgumentException(
"The roleLocationMetadataAdmin argument must not be null or empty.");
}
Set<String> capitalized;
if ( locaitonMetadataAdminRoles.size() == 1 ) {
capitalized = Collections
.singleton(locaitonMetadataAdminRoles.iterator().next().toUpperCase());
} else {
capitalized = new HashSet<String>(locaitonMetadataAdminRoles.size());
for ( String role : locaitonMetadataAdminRoles ) {
capitalized.add(role.toUpperCase());
}
}
this.locaitonMetadataAdminRoles = capitalized;
}
}