/* ==================================================================
* QuerySecurityAspectTests.java - Mar 5, 2014 7:46:12 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.query.aop.test;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertSame;
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.Signature;
import org.easymock.Capture;
import org.easymock.EasyMock;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import net.solarnetwork.central.datum.domain.AggregateGeneralNodeDatumFilter;
import net.solarnetwork.central.datum.domain.DatumFilterCommand;
import net.solarnetwork.central.datum.domain.GeneralNodeDatumFilter;
import net.solarnetwork.central.datum.domain.GeneralNodeDatumFilterMatch;
import net.solarnetwork.central.datum.domain.ReportingGeneralNodeDatumMatch;
import net.solarnetwork.central.domain.Aggregation;
import net.solarnetwork.central.domain.Filter;
import net.solarnetwork.central.domain.FilterResults;
import net.solarnetwork.central.domain.SolarLocation;
import net.solarnetwork.central.domain.SolarNode;
import net.solarnetwork.central.query.aop.QuerySecurityAspect;
import net.solarnetwork.central.query.biz.QueryBiz;
import net.solarnetwork.central.security.AuthenticatedNode;
import net.solarnetwork.central.security.AuthenticatedToken;
import net.solarnetwork.central.security.AuthorizationException;
import net.solarnetwork.central.security.AuthorizationException.Reason;
import net.solarnetwork.central.security.BasicSecurityPolicy;
import net.solarnetwork.central.security.SecurityPolicy;
import net.solarnetwork.central.security.SecurityToken;
import net.solarnetwork.central.support.BasicFilterResults;
import net.solarnetwork.central.support.PriceLocationFilter;
import net.solarnetwork.central.user.dao.UserNodeDao;
import net.solarnetwork.central.user.domain.User;
import net.solarnetwork.central.user.domain.UserAuthTokenType;
import net.solarnetwork.central.user.domain.UserNode;
/**
* Unit tests for the {@link QuerySecurityAspect} class.
*
* @author matt
* @version 1.1
*/
public class QuerySecurityAspectTests {
private UserNodeDao userNodeDao;
private QuerySecurityAspect service;
@Before
public void setup() {
userNodeDao = EasyMock.createMock(UserNodeDao.class);
service = new QuerySecurityAspect(userNodeDao);
service.setNodeIdNotRequiredSet(new HashSet<String>(Arrays.asList("price", "weather")));
}
@After
public void teardown() {
EasyMock.verify(userNodeDao);
SecurityContextHolder.getContext().setAuthentication(null);
}
private void setUser(Authentication auth) {
SecurityContextHolder.getContext().setAuthentication(auth);
}
private AuthenticatedNode setAuthenticatedNode(final Long nodeId) {
AuthenticatedNode node = new AuthenticatedNode(nodeId, null, false);
TestingAuthenticationToken auth = new TestingAuthenticationToken(node, "foobar", "ROLE_NODE");
setUser(auth);
return node;
}
private SecurityToken setAuthenticatedUserToken(final Long userId, final SecurityPolicy policy) {
AuthenticatedToken token = new AuthenticatedToken(
new org.springframework.security.core.userdetails.User("user", "pass", true, true, true,
true, AuthorityUtils.NO_AUTHORITIES),
UserAuthTokenType.User.toString(), userId, policy);
TestingAuthenticationToken auth = new TestingAuthenticationToken(token, "123", "ROLE_USER");
setUser(auth);
return token;
}
private SecurityToken setAuthenticatedReadNodeDataToken(final Long userId,
final SecurityPolicy policy) {
AuthenticatedToken token = new AuthenticatedToken(
new org.springframework.security.core.userdetails.User("user", "pass", true, true, true,
true, AuthorityUtils.NO_AUTHORITIES),
UserAuthTokenType.ReadNodeData.toString(), userId, policy);
TestingAuthenticationToken auth = new TestingAuthenticationToken(token, "123", "ROLE_USER");
setUser(auth);
return token;
}
@Test
public void datumFilterPublicNodeAsAuthenticatedNode() {
AuthenticatedNode node = setAuthenticatedNode(-1L);
UserNode userNode = new UserNode(new User(), new SolarNode(node.getNodeId(), null));
EasyMock.expect(userNodeDao.get(node.getNodeId())).andReturn(userNode);
EasyMock.replay(userNodeDao);
DatumFilterCommand criteria = new DatumFilterCommand();
criteria.setType("Consumption");
criteria.setNodeId(node.getNodeId());
Filter result = service.userNodeAccessCheck(criteria);
Assert.assertSame(criteria, result);
}
@Test
public void datumFilterPublicNodeAsAnonymous() {
UserNode userNode = new UserNode(new User(), new SolarNode(-1L, null));
EasyMock.expect(userNodeDao.get(userNode.getNode().getId())).andReturn(userNode);
EasyMock.replay(userNodeDao);
DatumFilterCommand criteria = new DatumFilterCommand();
criteria.setType("Consumption");
criteria.setNodeId(userNode.getNode().getId());
Filter result = service.userNodeAccessCheck(criteria);
Assert.assertSame(criteria, result);
}
@Test
public void datumFilterPublicNodeAsSomeOtherNode() {
setAuthenticatedNode(-2L);
UserNode userNode = new UserNode(new User(), new SolarNode(-1L, null));
EasyMock.expect(userNodeDao.get(userNode.getNode().getId())).andReturn(userNode);
EasyMock.replay(userNodeDao);
DatumFilterCommand criteria = new DatumFilterCommand();
criteria.setType("Consumption");
criteria.setNodeId(userNode.getNode().getId());
Filter result = service.userNodeAccessCheck(criteria);
Assert.assertSame(criteria, result);
}
@Test
public void datumFilterPrivateNodeAsAuthenticatedNode() {
AuthenticatedNode node = setAuthenticatedNode(-1L);
UserNode userNode = new UserNode(new User(), new SolarNode(node.getNodeId(), null));
userNode.setRequiresAuthorization(true);
EasyMock.expect(userNodeDao.get(node.getNodeId())).andReturn(userNode);
EasyMock.replay(userNodeDao);
DatumFilterCommand criteria = new DatumFilterCommand();
criteria.setType("Consumption");
criteria.setNodeId(node.getNodeId());
Filter result = service.userNodeAccessCheck(criteria);
Assert.assertSame(criteria, result);
}
@Test
public void datumFilterPrivateNodeAsAnonymous() {
UserNode userNode = new UserNode(new User(), new SolarNode(-1L, null));
userNode.setRequiresAuthorization(true);
EasyMock.expect(userNodeDao.get(userNode.getNode().getId())).andReturn(userNode);
EasyMock.replay(userNodeDao);
DatumFilterCommand criteria = new DatumFilterCommand();
criteria.setType("Consumption");
criteria.setNodeId(userNode.getNode().getId());
try {
service.userNodeAccessCheck(criteria);
Assert.fail("Should have thrown AuthorizationException");
} catch ( AuthorizationException e ) {
Assert.assertEquals(Reason.ACCESS_DENIED, e.getReason());
}
}
@Test
public void datumFilterPrivateNodeAsSomeOtherNode() {
setAuthenticatedNode(-2L);
UserNode userNode = new UserNode(new User(), new SolarNode(-1L, null));
userNode.setRequiresAuthorization(true);
EasyMock.expect(userNodeDao.get(userNode.getNode().getId())).andReturn(userNode);
EasyMock.replay(userNodeDao);
DatumFilterCommand criteria = new DatumFilterCommand();
criteria.setType("Consumption");
criteria.setNodeId(userNode.getNode().getId());
try {
service.userNodeAccessCheck(criteria);
Assert.fail("Should have thrown AuthorizationException");
} catch ( AuthorizationException e ) {
Assert.assertEquals(Reason.ACCESS_DENIED, e.getReason());
}
}
@Test
public void datumFilterPrivateNodeAsUserToken() {
final Long nodeId = -1L;
final Long userId = -100L;
final SecurityPolicy policy = new BasicSecurityPolicy.Builder()
.withNodeIds(Collections.singleton(nodeId)).build();
setAuthenticatedUserToken(userId, policy);
UserNode userNode = new UserNode(new User(userId, null), new SolarNode(nodeId, null));
userNode.setRequiresAuthorization(true);
EasyMock.expect(userNodeDao.get(nodeId)).andReturn(userNode);
EasyMock.replay(userNodeDao);
DatumFilterCommand criteria = new DatumFilterCommand();
criteria.setType("Consumption");
criteria.setNodeId(nodeId);
GeneralNodeDatumFilter result = service.userNodeAccessCheck(criteria);
Assert.assertEquals(nodeId, result.getNodeId());
}
@Test
public void datumFilterPrivateNodeAsSomeOtherUserToken() {
final Long nodeId = -1L;
final Long userId = -100L;
final SecurityPolicy policy = new BasicSecurityPolicy.Builder()
.withNodeIds(Collections.singleton(nodeId)).build();
setAuthenticatedUserToken(-200L, policy);
UserNode userNode = new UserNode(new User(userId, null), new SolarNode(nodeId, null));
userNode.setRequiresAuthorization(true);
EasyMock.expect(userNodeDao.get(nodeId)).andReturn(userNode);
EasyMock.replay(userNodeDao);
DatumFilterCommand criteria = new DatumFilterCommand();
criteria.setType("Consumption");
criteria.setNodeId(nodeId);
try {
service.userNodeAccessCheck(criteria);
Assert.fail("Should have thrown AuthorizationException");
} catch ( AuthorizationException e ) {
Assert.assertEquals(Reason.ACCESS_DENIED, e.getReason());
}
}
@Test
public void datumFilterPrivateNodeAsReadNodeDataToken() {
final Long nodeId = -1L;
final Long userId = -100L;
final SecurityPolicy policy = new BasicSecurityPolicy.Builder()
.withNodeIds(Collections.singleton(nodeId)).build();
setAuthenticatedReadNodeDataToken(userId, policy);
UserNode userNode = new UserNode(new User(userId, null), new SolarNode(nodeId, null));
userNode.setRequiresAuthorization(true);
EasyMock.expect(userNodeDao.get(nodeId)).andReturn(userNode);
EasyMock.replay(userNodeDao);
DatumFilterCommand criteria = new DatumFilterCommand();
criteria.setType("Consumption");
criteria.setNodeId(nodeId);
GeneralNodeDatumFilter result = service.userNodeAccessCheck(criteria);
Assert.assertEquals(nodeId, result.getNodeId());
}
@Test
public void datumFilterPrivateNodeAsReadNodeDataTokenSomeOtherUser() {
final Long nodeId = -1L;
final Long userId = -100L;
final SecurityPolicy policy = new BasicSecurityPolicy.Builder()
.withNodeIds(Collections.singleton(nodeId)).build();
// note the actor is not the owner of the node
setAuthenticatedReadNodeDataToken(-200L, policy);
UserNode userNode = new UserNode(new User(userId, null), new SolarNode(nodeId, null));
userNode.setRequiresAuthorization(true);
EasyMock.expect(userNodeDao.get(nodeId)).andReturn(userNode);
EasyMock.replay(userNodeDao);
DatumFilterCommand criteria = new DatumFilterCommand();
criteria.setType("Consumption");
criteria.setNodeId(nodeId);
GeneralNodeDatumFilter result = service.userNodeAccessCheck(criteria);
Assert.assertEquals(nodeId, result.getNodeId());
}
@Test
public void datumFilterPrivateNodeAsReadNodeDataTokenSomeOtherUserNonMatchingNode() {
final Long nodeId = -1L;
final Long userId = -100L;
final SecurityPolicy policy = new BasicSecurityPolicy.Builder()
.withNodeIds(Collections.singleton(-2L)).build();
// note the actor is not the owner of the node, and the token is not granted access to the node ID
setAuthenticatedReadNodeDataToken(-200L, policy);
UserNode userNode = new UserNode(new User(userId, null), new SolarNode(nodeId, null));
userNode.setRequiresAuthorization(true);
EasyMock.expect(userNodeDao.get(nodeId)).andReturn(userNode);
EasyMock.replay(userNodeDao);
DatumFilterCommand criteria = new DatumFilterCommand();
criteria.setType("Consumption");
criteria.setNodeId(nodeId);
try {
service.userNodeAccessCheck(criteria);
Assert.fail("Should have thrown SecurityException for anonymous user");
} catch ( AuthorizationException e ) {
Assert.assertEquals(Reason.ACCESS_DENIED, e.getReason());
}
}
@Test
public void datumFilterPrivateNodeAsReadNodeDataTokenWithFilledInSourceIdsPolicy() {
final Long nodeId = -1L;
final Long userId = -100L;
final String[] policySourceIds = new String[] { "One", "Two" };
final SecurityPolicy policy = new BasicSecurityPolicy.Builder()
.withSourceIds(new LinkedHashSet<String>(Arrays.asList(policySourceIds)))
.withNodeIds(Collections.singleton(nodeId)).build();
setAuthenticatedReadNodeDataToken(userId, policy);
UserNode userNode = new UserNode(new User(userId, null), new SolarNode(nodeId, null));
userNode.setRequiresAuthorization(true);
EasyMock.expect(userNodeDao.get(nodeId)).andReturn(userNode);
EasyMock.replay(userNodeDao);
DatumFilterCommand criteria = new DatumFilterCommand();
criteria.setNodeId(nodeId);
GeneralNodeDatumFilter result = service.userNodeAccessCheck(criteria);
Assert.assertEquals(nodeId, result.getNodeId());
Assert.assertArrayEquals("Filled in source IDs", policySourceIds, result.getSourceIds());
}
@Test
public void availableSourceIdsFilteredFromPattern() throws Throwable {
final Long nodeId = -1L;
final Long userId = -100L;
final String[] policySourceIds = new String[] { "/A/**/watts" };
final SecurityPolicy policy = new BasicSecurityPolicy.Builder()
.withSourceIds(new LinkedHashSet<String>(Arrays.asList(policySourceIds)))
.withNodeIds(Collections.singleton(nodeId)).build();
final ProceedingJoinPoint pjp = EasyMock.createMock(org.aspectj.lang.ProceedingJoinPoint.class);
final Set<String> availableSourceIds = new LinkedHashSet<String>(
Arrays.asList("/A/B/watts", "/A/C/watts", "/B/B/watts", "Foo bar"));
setAuthenticatedReadNodeDataToken(userId, policy);
UserNode userNode = new UserNode(new User(userId, null), new SolarNode(nodeId, null));
userNode.setRequiresAuthorization(true);
EasyMock.expect(userNodeDao.get(nodeId)).andReturn(userNode);
EasyMock.expect(pjp.proceed()).andReturn(availableSourceIds);
EasyMock.replay(pjp);
EasyMock.replay(userNodeDao);
DatumFilterCommand criteria = new DatumFilterCommand();
criteria.setNodeId(nodeId);
@SuppressWarnings("unchecked")
Set<String> result = (Set<String>) service.reportableSourcesAccessCheck(pjp, nodeId);
Assert.assertEquals("Filtered source IDs",
new LinkedHashSet<String>(Arrays.asList("/A/B/watts", "/A/C/watts")), result);
}
@Test
public void weatherFilterAsAnonymous() {
EasyMock.replay(userNodeDao);
SolarLocation loc = new SolarLocation();
loc.setTimeZoneId("Pacific/Auckland");
DatumFilterCommand criteria = new DatumFilterCommand(loc);
criteria.setType("Weather");
Filter result = service.userNodeAccessCheck(criteria);
Assert.assertSame(criteria, result);
}
@Test
public void priceFilterAsAnonymous() {
EasyMock.replay(userNodeDao);
PriceLocationFilter criteria = new PriceLocationFilter();
criteria.setCurrency("NZD");
Filter result = service.userNodeAccessCheck(criteria);
Assert.assertSame(criteria, result);
}
@SuppressWarnings("unchecked")
@Test
public void findFilteredGeneralNodeDatumRedirectMinAggregateEnforement() throws Throwable {
final Long nodeId = -1L;
final Long userId = -100L;
final Aggregation policyMinAgg = Aggregation.Day;
final SecurityPolicy policy = new BasicSecurityPolicy.Builder()
.withNodeIds(Collections.singleton(nodeId)).withMinAggregation(policyMinAgg).build();
final ProceedingJoinPoint pjp = EasyMock.createMock(org.aspectj.lang.ProceedingJoinPoint.class);
setAuthenticatedReadNodeDataToken(userId, policy);
UserNode userNode = new UserNode(new User(userId, null), new SolarNode(nodeId, null));
userNode.setRequiresAuthorization(true);
final DatumFilterCommand criteria = new DatumFilterCommand();
criteria.setNodeId(nodeId);
final QueryBiz queryBiz = EasyMock.createMock(QueryBiz.class);
final Signature methodSig = EasyMock.createMock(Signature.class);
expect(userNodeDao.get(nodeId)).andReturn(userNode);
// setup join point conditions to mimic call to findFilteredGeneralNodeDatum()
expect(pjp.getTarget()).andReturn(queryBiz).anyTimes();
expect(pjp.getSignature()).andReturn(methodSig).anyTimes();
expect(methodSig.getName()).andReturn("findFilteredGeneralNodeDatum").anyTimes();
expect(pjp.getArgs()).andReturn(new Object[] { criteria, null, null, null }).anyTimes();
// findFilteredGeneralNodeDatum should be redirected to findFilteredAggregateGeneralNodeDatum()
final Capture<AggregateGeneralNodeDatumFilter> filterCapture = new Capture<AggregateGeneralNodeDatumFilter>();
final FilterResults<ReportingGeneralNodeDatumMatch> filterResults = new BasicFilterResults<ReportingGeneralNodeDatumMatch>(
Collections.<ReportingGeneralNodeDatumMatch> emptyList(), Long.valueOf(0L),
Integer.valueOf(0), Integer.valueOf(0));
expect(queryBiz.findFilteredAggregateGeneralNodeDatum(EasyMock.capture(filterCapture),
EasyMock.isNull(List.class), EasyMock.isNull(Integer.class),
EasyMock.isNull(Integer.class))).andReturn(filterResults);
replay(userNodeDao, pjp, methodSig, queryBiz);
Object result = service.userNodeFilterAccessCheck(pjp, criteria);
assertSame("Filtered results", filterResults, result);
AggregateGeneralNodeDatumFilter redirectedFilter = filterCapture.getValue();
Assert.assertEquals("Redirected filter node ID", nodeId, redirectedFilter.getNodeId());
Assert.assertEquals("Redirected filter aggregation", policyMinAgg,
redirectedFilter.getAggregation());
verify(userNodeDao, pjp, methodSig, queryBiz);
}
@Test
public void injectAvailableSourceIdsWhenNoneProvidedButPolicyRestricts() throws Throwable {
final Long nodeId = -1L;
final Long userId = -100L;
final String[] policySourceIds = new String[] { "/A/**/watts" };
final SecurityPolicy policy = new BasicSecurityPolicy.Builder()
.withSourceIds(new LinkedHashSet<String>(Arrays.asList(policySourceIds)))
.withNodeIds(Collections.singleton(nodeId)).build();
final ProceedingJoinPoint pjp = EasyMock.createMock(org.aspectj.lang.ProceedingJoinPoint.class);
final QueryBiz queryBiz = EasyMock.createMock(QueryBiz.class);
final Signature methodSig = EasyMock.createMock(Signature.class);
final Set<String> availableSourceIds = new LinkedHashSet<String>(
Arrays.asList("/A/B/watts", "/A/C/watts", "/B/B/watts", "Foo bar"));
setAuthenticatedReadNodeDataToken(userId, policy);
UserNode userNode = new UserNode(new User(userId, null), new SolarNode(nodeId, null));
userNode.setRequiresAuthorization(true);
expect(userNodeDao.get(nodeId)).andReturn(userNode);
final DatumFilterCommand criteria = new DatumFilterCommand();
criteria.setNodeId(nodeId);
criteria.setMostRecent(true);
// setup join point conditions to handle call to findFilteredGeneralNodeDatum()
expect(pjp.getTarget()).andReturn(queryBiz).anyTimes();
expect(pjp.getSignature()).andReturn(methodSig).anyTimes();
expect(methodSig.getName()).andReturn("findFilteredGeneralNodeDatum").anyTimes();
expect(pjp.getArgs()).andReturn(new Object[] { criteria, null, null, null }).anyTimes();
// aspect should call QueryBiz.getAvailableSources(nodeId, start, end)
expect(queryBiz.getAvailableSources(nodeId, null, null)).andReturn(availableSourceIds);
// join point should proceed with custom arguments list
final Capture<Object[]> proceedArgsCapture = new Capture<Object[]>();
final FilterResults<GeneralNodeDatumFilterMatch> filterResults = new BasicFilterResults<GeneralNodeDatumFilterMatch>(
Collections.<GeneralNodeDatumFilterMatch> emptyList(), Long.valueOf(0L),
Integer.valueOf(0), Integer.valueOf(0));
expect(pjp.proceed(EasyMock.capture(proceedArgsCapture))).andReturn(filterResults);
replay(userNodeDao, pjp, methodSig, queryBiz);
Object result = service.userNodeFilterAccessCheck(pjp, criteria);
assertSame("Filtered results", filterResults, result);
Object[] findFilteredArgs = proceedArgsCapture.getValue();
Assert.assertNotNull(findFilteredArgs);
Assert.assertEquals("findFilteredGeneralNodeDatum argument length", 4, findFilteredArgs.length);
Assert.assertNotSame("findFilteredGeneralNodeDatum filter argument changed", criteria,
findFilteredArgs[0]);
Assert.assertTrue("findFilteredGeneralNodeDatum filter",
findFilteredArgs[0] instanceof GeneralNodeDatumFilter);
GeneralNodeDatumFilter injectedFilter = (GeneralNodeDatumFilter) findFilteredArgs[0];
Assert.assertArrayEquals("Filtered source IDs", new String[] { "/A/B/watts", "/A/C/watts" },
injectedFilter.getSourceIds());
verify(userNodeDao, pjp, methodSig, queryBiz);
}
}