changeset 1212:5107ada6cee5

Implement ACL based filters for queries. Reviewed-by: ebaron Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-August/007739.html PR1461
author Severin Gehwolf <sgehwolf@redhat.com>
date Thu, 25 Jul 2013 17:45:33 +0200
parents 8806071b075a
children d44f06cf8a58
files host-cpu/common/src/main/java/com/redhat/thermostat/host/cpu/common/internal/CpuStatDAOImplStatementDescriptorRegistration.java host-cpu/common/src/test/java/com/redhat/thermostat/host/cpu/common/internal/CpuStatDAOImplStatementDescriptorRegistrationTest.java host-memory/common/src/main/java/com/redhat/thermostat/host/memory/common/internal/MemoryStatDAOImplStatementDescriptorRegistration.java host-memory/common/src/test/java/com/redhat/thermostat/host/memory/common/internal/MemoryStatDAOImplStatementDescriptorRegistrationTest.java integration-tests/src/test/java/com/redhat/thermostat/itest/WebAppTest.java integration-tests/src/test/java/com/redhat/thermostat/itest/WebAppTestStatementDescriptorRegistration.java numa/common/src/main/java/com/redhat/thermostat/numa/common/internal/NumaDAOImplStatementDescriptorRegistration.java numa/common/src/test/java/com/redhat/thermostat/numa/common/internal/NumaDAOImplStatementDescriptorRegistrationTest.java storage/core/src/main/java/com/redhat/thermostat/storage/core/auth/DescriptorMetadata.java storage/core/src/main/java/com/redhat/thermostat/storage/core/auth/StatementDescriptorMetadataFactory.java storage/core/src/main/java/com/redhat/thermostat/storage/core/auth/StatementDescriptorRegistration.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/dao/DAOImplStatementDescriptorRegistration.java storage/core/src/test/java/com/redhat/thermostat/storage/core/auth/DescriptorMetadataTest.java storage/core/src/test/java/com/redhat/thermostat/storage/internal/dao/DAOImplStatementDescriptorRegistrationTest.java thread/collector/src/main/java/com/redhat/thermostat/thread/dao/impl/ThreadDaoImplStatementDescriptorRegistration.java thread/collector/src/test/java/com/redhat/thermostat/thread/dao/impl/ThreadDAOImplStatementDescriptorRegistrationTest.java vm-classstat/common/src/main/java/com/redhat/thermostat/vm/classstat/common/internal/VmClassStatDAOImplStatementDescriptorRegistration.java vm-classstat/common/src/test/java/com/redhat/thermostat/vm/classstat/common/internal/VmClassStatDAOImplStatementDescriptorRegistrationTest.java vm-cpu/common/src/main/java/com/redhat/thermostat/vm/cpu/common/internal/VmCpuStatDAOImplStatementDescriptorRegistration.java vm-cpu/common/src/test/java/com/redhat/thermostat/vm/cpu/common/internal/VmCpuStatDAOImplStatementDescriptorRegistrationTest.java vm-gc/common/src/main/java/com/redhat/thermostat/vm/gc/common/internal/VmGcStatDAOImplStatementDescriptorRegistration.java vm-gc/common/src/test/java/com/redhat/thermostat/vm/gc/common/internal/VmGcStatDAOImplStatementDescriptorRegistrationTest.java vm-heap-analysis/common/src/main/java/com/redhat/thermostat/vm/heap/analysis/common/internal/HeapDAOImplStatementDescriptorRegistration.java vm-heap-analysis/common/src/test/java/com/redhat/thermostat/vm/heap/analysis/common/internal/HeapDAOImplStatementDescriptorRegistrationTest.java vm-jmx/common/src/main/java/com/redhat/thermostat/vm/jmx/common/internal/JmxNotificationDAOImplStatementDescriptorRegistration.java vm-jmx/common/src/test/java/com/redhat/thermostat/vm/jmx/common/internal/JmxNotificationDAOImplStatementDescriptorRegistrationTest.java vm-memory/common/src/main/java/com/redhat/thermostat/vm/memory/common/internal/VmMemoryStatDAOImplStatementDescriptorRegistration.java vm-memory/common/src/test/java/com/redhat/thermostat/vm/memory/common/internal/VmMemoryStatDAOImplStatementDescriptorRegistrationTest.java web/server/pom.xml web/server/src/main/java/com/redhat/thermostat/web/server/KnownDescriptorRegistry.java web/server/src/main/java/com/redhat/thermostat/web/server/PreparedStatementHolder.java web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java web/server/src/main/java/com/redhat/thermostat/web/server/auth/AbstractFilter.java web/server/src/main/java/com/redhat/thermostat/web/server/auth/AgentIdFilter.java web/server/src/main/java/com/redhat/thermostat/web/server/auth/FilterResult.java web/server/src/main/java/com/redhat/thermostat/web/server/auth/HostnameFilter.java web/server/src/main/java/com/redhat/thermostat/web/server/auth/Roles.java web/server/src/main/java/com/redhat/thermostat/web/server/auth/StatementFilter.java web/server/src/main/java/com/redhat/thermostat/web/server/auth/UserPrincipal.java web/server/src/main/java/com/redhat/thermostat/web/server/auth/VmIdFilter.java web/server/src/main/java/com/redhat/thermostat/web/server/auth/VmUsernameFilter.java web/server/src/main/webapp/WEB-INF/web.xml web/server/src/test/java/com/redhat/thermostat/web/server/KnownDescriptorRegistryTest.java web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java web/server/src/test/java/com/redhat/thermostat/web/server/auth/AgentIdFilterTest.java web/server/src/test/java/com/redhat/thermostat/web/server/auth/HostnameFilterTest.java web/server/src/test/java/com/redhat/thermostat/web/server/auth/UserPrincipalTest.java web/server/src/test/java/com/redhat/thermostat/web/server/auth/VmIdFilterTest.java web/server/src/test/java/com/redhat/thermostat/web/server/auth/VmUsernameFilterTest.java
diffstat 49 files changed, 3452 insertions(+), 103 deletions(-) [+]
line wrap: on
line diff
--- a/host-cpu/common/src/main/java/com/redhat/thermostat/host/cpu/common/internal/CpuStatDAOImplStatementDescriptorRegistration.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/host-cpu/common/src/main/java/com/redhat/thermostat/host/cpu/common/internal/CpuStatDAOImplStatementDescriptorRegistration.java	Thu Jul 25 17:45:33 2013 +0200
@@ -41,6 +41,8 @@
 
 import com.redhat.thermostat.host.cpu.common.CpuStatDAO;
 import com.redhat.thermostat.storage.core.HostLatestPojoListGetter;
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
 import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration;
 
 /**
@@ -50,15 +52,29 @@
  */
 public class CpuStatDAOImplStatementDescriptorRegistration implements
         StatementDescriptorRegistration {
+    
+    static final String DESCRIPTOR = String.format(
+            HostLatestPojoListGetter.HOST_LATEST_QUERY_FORMAT,
+            CpuStatDAO.cpuStatCategory.getName());
 
     @Override
     public Set<String> getStatementDescriptors() {
-        Set<String> descs = new HashSet<>();
-        String descriptor = String.format(
-                HostLatestPojoListGetter.HOST_LATEST_QUERY_FORMAT,
-                CpuStatDAO.cpuStatCategory.getName());
-        descs.add(descriptor);
+        Set<String> descs = new HashSet<>(1);
+        descs.add(DESCRIPTOR);
         return descs;
     }
 
+    @Override
+    public DescriptorMetadata getDescriptorMetadata(String descriptor,
+            PreparedParameter[] params) {
+        if (descriptor.equals(CpuStatDAOImplStatementDescriptorRegistration.DESCRIPTOR)) {
+            String agentId = (String)params[0].getValue();
+            DescriptorMetadata metadata = new DescriptorMetadata(agentId);
+            return metadata;
+        } else {
+            throw new IllegalArgumentException("Unknown descriptor: ->"
+                    + descriptor + "<-");
+        }
+    }
+
 }
--- a/host-cpu/common/src/test/java/com/redhat/thermostat/host/cpu/common/internal/CpuStatDAOImplStatementDescriptorRegistrationTest.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/host-cpu/common/src/test/java/com/redhat/thermostat/host/cpu/common/internal/CpuStatDAOImplStatementDescriptorRegistrationTest.java	Thu Jul 25 17:45:33 2013 +0200
@@ -40,6 +40,9 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -49,6 +52,9 @@
 
 import org.junit.Test;
 
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
+import com.redhat.thermostat.storage.core.auth.StatementDescriptorMetadataFactory;
 import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration;
 import com.redhat.thermostat.storage.internal.dao.DAOImplStatementDescriptorRegistration;
 
@@ -88,4 +94,32 @@
         assertNotNull(cpuStatReg);
         assertEquals(1, cpuStatReg.getStatementDescriptors().size());
     }
+    
+    @Test
+    public void canGetMetadataForHostLatestQuery() {
+        PreparedParameter agentIdParam = mock(PreparedParameter.class);
+        String agentId = "agentId";
+        when(agentIdParam.getValue()).thenReturn(agentId);
+        PreparedParameter[] params = new PreparedParameter[] {
+                agentIdParam
+        };
+        
+        StatementDescriptorMetadataFactory factory = new CpuStatDAOImplStatementDescriptorRegistration();
+        DescriptorMetadata data = factory.getDescriptorMetadata(CpuStatDAOImplStatementDescriptorRegistration.DESCRIPTOR, params);
+        assertNotNull(data);
+        assertTrue(data.hasAgentId());
+        assertFalse(data.hasVmId());
+        assertEquals(agentId, data.getAgentId());
+    }
+    
+    @Test
+    public void unknownDescriptorThrowsException() {
+        StatementDescriptorMetadataFactory factory = new CpuStatDAOImplStatementDescriptorRegistration();
+        try {
+            factory.getDescriptorMetadata("QUERY foo-bar WHERE 'a' = 'b'", null);
+            fail("should have thrown exception");
+        } catch (IllegalArgumentException e) {
+            // pass
+        }
+    }
 }
--- a/host-memory/common/src/main/java/com/redhat/thermostat/host/memory/common/internal/MemoryStatDAOImplStatementDescriptorRegistration.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/host-memory/common/src/main/java/com/redhat/thermostat/host/memory/common/internal/MemoryStatDAOImplStatementDescriptorRegistration.java	Thu Jul 25 17:45:33 2013 +0200
@@ -41,6 +41,8 @@
 
 import com.redhat.thermostat.host.memory.common.MemoryStatDAO;
 import com.redhat.thermostat.storage.core.HostLatestPojoListGetter;
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
 import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration;
 
 /**
@@ -51,13 +53,27 @@
 public class MemoryStatDAOImplStatementDescriptorRegistration implements
         StatementDescriptorRegistration {
 
+    static final String descriptor = String.format(HostLatestPojoListGetter.HOST_LATEST_QUERY_FORMAT, 
+            MemoryStatDAO.memoryStatCategory.getName());
+    
     @Override
     public Set<String> getStatementDescriptors() {
         Set<String> descs = new HashSet<>(1);
-        String descriptor = String.format(HostLatestPojoListGetter.HOST_LATEST_QUERY_FORMAT, 
-                MemoryStatDAO.memoryStatCategory.getName());
         descs.add(descriptor);
         return descs;
     }
 
+    @Override
+    public DescriptorMetadata getDescriptorMetadata(String descriptor,
+            PreparedParameter[] params) {
+        if (descriptor.equals(MemoryStatDAOImplStatementDescriptorRegistration.descriptor)) {
+            String agentId = (String)params[0].getValue();
+            DescriptorMetadata metadata = new DescriptorMetadata(agentId);
+            return metadata;
+        } else {
+            throw new IllegalArgumentException("Unknown descriptor: ->"
+                    + descriptor + "<-");
+        }
+    }
+
 }
--- a/host-memory/common/src/test/java/com/redhat/thermostat/host/memory/common/internal/MemoryStatDAOImplStatementDescriptorRegistrationTest.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/host-memory/common/src/test/java/com/redhat/thermostat/host/memory/common/internal/MemoryStatDAOImplStatementDescriptorRegistrationTest.java	Thu Jul 25 17:45:33 2013 +0200
@@ -40,6 +40,9 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -49,6 +52,9 @@
 
 import org.junit.Test;
 
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
+import com.redhat.thermostat.storage.core.auth.StatementDescriptorMetadataFactory;
 import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration;
 import com.redhat.thermostat.storage.internal.dao.DAOImplStatementDescriptorRegistration;
 
@@ -88,4 +94,32 @@
         assertNotNull(memoryStatReg);
         assertEquals(1, memoryStatReg.getStatementDescriptors().size());
     }
+    
+    @Test
+    public void canGetMetadataForHostLatestQuery() {
+        PreparedParameter agentIdParam = mock(PreparedParameter.class);
+        String agentId = "agentId";
+        when(agentIdParam.getValue()).thenReturn(agentId);
+        PreparedParameter[] params = new PreparedParameter[] {
+                agentIdParam
+        };
+        
+        StatementDescriptorMetadataFactory factory = new MemoryStatDAOImplStatementDescriptorRegistration();
+        DescriptorMetadata data = factory.getDescriptorMetadata(MemoryStatDAOImplStatementDescriptorRegistration.descriptor, params);
+        assertNotNull(data);
+        assertTrue(data.hasAgentId());
+        assertFalse(data.hasVmId());
+        assertEquals(agentId, data.getAgentId());
+    }
+    
+    @Test
+    public void unknownDescriptorThrowsException() {
+        StatementDescriptorMetadataFactory factory = new MemoryStatDAOImplStatementDescriptorRegistration();
+        try {
+            factory.getDescriptorMetadata("QUERY foo-bar WHERE 'a' = 'b'", null);
+            fail("should have thrown exception");
+        } catch (IllegalArgumentException e) {
+            // pass
+        }
+    }
 }
--- a/integration-tests/src/test/java/com/redhat/thermostat/itest/WebAppTest.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/integration-tests/src/test/java/com/redhat/thermostat/itest/WebAppTest.java	Thu Jul 25 17:45:33 2013 +0200
@@ -84,6 +84,7 @@
 import com.redhat.thermostat.storage.core.StatementDescriptor;
 import com.redhat.thermostat.storage.core.StatementExecutionException;
 import com.redhat.thermostat.storage.core.Storage;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
 import com.redhat.thermostat.test.FreePortFinder;
 import com.redhat.thermostat.test.FreePortFinder.TryPort;
 import com.redhat.thermostat.vm.classstat.common.VmClassStatDAO;
@@ -117,6 +118,12 @@
      * WebAppTestStatementDescriptorRegistration
      */
     public static final Set<String> TRUSTED_DESCRIPTORS;
+    /*
+     * Map which maps a string descriptor to DescriptorMetadata.
+     * See also: WebAppTestStatementDescriptorRegistration
+     * 
+     */
+    public static final Map<String, DescriptorMetadata> METADATA_MAPPING;
     // descriptive name -> descriptor mapping
     private static final Map<String, String> DESCRIPTOR_MAP;
     
@@ -146,11 +153,15 @@
         descMap.put(KEY_AUTHORIZED_QUERY_OR, "QUERY cpu-stats WHERE 'timeStamp' > ?l OR 'timeStamp' < ?l SORT 'timeStamp' ASC");
         descMap.put(KEY_SET_DEFAULT_AGENT_ID, "QUERY vm-cpu-stats");
         Set<String> trustedDescriptors = new HashSet<>();
+        Map<String, DescriptorMetadata> metadata = new HashMap<>();
+        DescriptorMetadata descMetadata = new DescriptorMetadata();
         for (String val: descMap.values()) {
             trustedDescriptors.add(val);
+            metadata.put(val, descMetadata);
         }
         TRUSTED_DESCRIPTORS = trustedDescriptors;
         DESCRIPTOR_MAP = descMap;
+        METADATA_MAPPING = metadata;
     }
     
     
@@ -459,7 +470,8 @@
                 Roles.READ,
                 Roles.LOGIN,
                 Roles.ACCESS_REALM,
-                Roles.PREPARE_STATEMENT
+                Roles.PREPARE_STATEMENT,
+                Roles.GRANT_READ_ALL
         };
         Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
         webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
@@ -483,7 +495,8 @@
                 Roles.READ,
                 Roles.LOGIN,
                 Roles.ACCESS_REALM,
-                Roles.PREPARE_STATEMENT
+                Roles.PREPARE_STATEMENT,
+                Roles.GRANT_READ_ALL
         };
         Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
         webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
@@ -506,7 +519,8 @@
                 Roles.READ,
                 Roles.LOGIN,
                 Roles.ACCESS_REALM,
-                Roles.PREPARE_STATEMENT
+                Roles.PREPARE_STATEMENT,
+                Roles.GRANT_READ_ALL
         };
         Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
         webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
@@ -529,7 +543,8 @@
                 Roles.READ,
                 Roles.LOGIN,
                 Roles.ACCESS_REALM,
-                Roles.PREPARE_STATEMENT
+                Roles.PREPARE_STATEMENT,
+                Roles.GRANT_READ_ALL
         };
         Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
         webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
@@ -552,7 +567,8 @@
                 Roles.READ,
                 Roles.LOGIN,
                 Roles.ACCESS_REALM,
-                Roles.PREPARE_STATEMENT
+                Roles.PREPARE_STATEMENT,
+                Roles.GRANT_READ_ALL
         };
         Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
         webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
@@ -575,7 +591,8 @@
                 Roles.READ,
                 Roles.LOGIN,
                 Roles.ACCESS_REALM,
-                Roles.PREPARE_STATEMENT
+                Roles.PREPARE_STATEMENT,
+                Roles.GRANT_READ_ALL
         };
         Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
         webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
@@ -598,7 +615,8 @@
                 Roles.READ,
                 Roles.LOGIN,
                 Roles.ACCESS_REALM,
-                Roles.PREPARE_STATEMENT
+                Roles.PREPARE_STATEMENT,
+                Roles.GRANT_READ_ALL
         };
         Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
         webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
@@ -621,7 +639,8 @@
                 Roles.READ,
                 Roles.LOGIN,
                 Roles.ACCESS_REALM,
-                Roles.PREPARE_STATEMENT
+                Roles.PREPARE_STATEMENT,
+                Roles.GRANT_READ_ALL
         };
         Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
         webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
@@ -644,7 +663,8 @@
                 Roles.READ,
                 Roles.LOGIN,
                 Roles.ACCESS_REALM,
-                Roles.PREPARE_STATEMENT
+                Roles.PREPARE_STATEMENT,
+                Roles.GRANT_READ_ALL
         };
         Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
         webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
@@ -667,7 +687,8 @@
                 Roles.READ,
                 Roles.LOGIN,
                 Roles.ACCESS_REALM,
-                Roles.PREPARE_STATEMENT
+                Roles.PREPARE_STATEMENT,
+                Roles.GRANT_READ_ALL
         };
         Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
         webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
@@ -774,7 +795,8 @@
                 Roles.READ,
                 Roles.APPEND,
                 Roles.PURGE,
-                Roles.PREPARE_STATEMENT
+                Roles.PREPARE_STATEMENT,
+                Roles.GRANT_READ_ALL
         };
         Storage storage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
         UUID uuid = new UUID(42, 24);
--- a/integration-tests/src/test/java/com/redhat/thermostat/itest/WebAppTestStatementDescriptorRegistration.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/integration-tests/src/test/java/com/redhat/thermostat/itest/WebAppTestStatementDescriptorRegistration.java	Thu Jul 25 17:45:33 2013 +0200
@@ -38,6 +38,8 @@
 
 import java.util.Set;
 
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
 import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration;
 
 public class WebAppTestStatementDescriptorRegistration implements
@@ -48,4 +50,10 @@
         return WebAppTest.TRUSTED_DESCRIPTORS;
     }
 
+    @Override
+    public DescriptorMetadata getDescriptorMetadata(String descriptor,
+            PreparedParameter[] params) {
+        return WebAppTest.METADATA_MAPPING.get(descriptor);
+    }
+
 }
--- a/numa/common/src/main/java/com/redhat/thermostat/numa/common/internal/NumaDAOImplStatementDescriptorRegistration.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/numa/common/src/main/java/com/redhat/thermostat/numa/common/internal/NumaDAOImplStatementDescriptorRegistration.java	Thu Jul 25 17:45:33 2013 +0200
@@ -41,6 +41,8 @@
 
 import com.redhat.thermostat.numa.common.NumaDAO;
 import com.redhat.thermostat.storage.core.HostLatestPojoListGetter;
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
 import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration;
 
 /**
@@ -50,16 +52,35 @@
  */
 public class NumaDAOImplStatementDescriptorRegistration implements
         StatementDescriptorRegistration {
-
-    @Override
-    public Set<String> getStatementDescriptors() {
-        Set<String> descs = new HashSet<>(2);
+    
+    private final Set<String> descs;
+    
+    public NumaDAOImplStatementDescriptorRegistration() {
+        descs = new HashSet<>(2);
         String descriptor = String.format(
                 HostLatestPojoListGetter.HOST_LATEST_QUERY_FORMAT,
                 NumaDAO.numaStatCategory.getName());
         descs.add(descriptor);
         descs.add(NumaDAOImpl.QUERY_NUMA_INFO);
+    }
+
+    @Override
+    public Set<String> getStatementDescriptors() {
         return descs;
     }
 
+    @Override
+    public DescriptorMetadata getDescriptorMetadata(String descriptor,
+            PreparedParameter[] params) {
+        if (descs.contains(descriptor)) {
+            // both queries use agentId
+            String agentId = (String)params[0].getValue();
+            DescriptorMetadata metadata = new DescriptorMetadata(agentId);
+            return metadata;
+        } else {
+            throw new IllegalArgumentException("Unknown descriptor: ->"
+                    + descriptor + "<-");
+        }
+    }
+
 }
--- a/numa/common/src/test/java/com/redhat/thermostat/numa/common/internal/NumaDAOImplStatementDescriptorRegistrationTest.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/numa/common/src/test/java/com/redhat/thermostat/numa/common/internal/NumaDAOImplStatementDescriptorRegistrationTest.java	Thu Jul 25 17:45:33 2013 +0200
@@ -40,6 +40,9 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -49,6 +52,11 @@
 
 import org.junit.Test;
 
+import com.redhat.thermostat.numa.common.NumaDAO;
+import com.redhat.thermostat.storage.core.HostLatestPojoListGetter;
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
+import com.redhat.thermostat.storage.core.auth.StatementDescriptorMetadataFactory;
 import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration;
 import com.redhat.thermostat.storage.internal.dao.DAOImplStatementDescriptorRegistration;
 
@@ -88,4 +96,52 @@
         assertNotNull(numaReg);
         assertEquals(2, numaReg.getStatementDescriptors().size());
     }
+    
+    @Test
+    public void canGetMetadataForHostLatestQuery() {
+        PreparedParameter agentIdParam = mock(PreparedParameter.class);
+        String agentId = "agentId";
+        when(agentIdParam.getValue()).thenReturn(agentId);
+        PreparedParameter[] params = new PreparedParameter[] {
+                agentIdParam
+        };
+        
+        StatementDescriptorMetadataFactory factory = new NumaDAOImplStatementDescriptorRegistration();
+        String desc = String.format(
+                HostLatestPojoListGetter.HOST_LATEST_QUERY_FORMAT,
+                NumaDAO.numaStatCategory.getName());
+        DescriptorMetadata data = factory.getDescriptorMetadata(desc, params);
+        assertNotNull(data);
+        assertTrue(data.hasAgentId());
+        assertFalse(data.hasVmId());
+        assertEquals(agentId, data.getAgentId());
+    }
+    
+    @Test
+    public void canGetMetadataForNumaInfoQuery() {
+        PreparedParameter agentIdParam = mock(PreparedParameter.class);
+        String agentId = "agentId";
+        when(agentIdParam.getValue()).thenReturn(agentId);
+        PreparedParameter[] params = new PreparedParameter[] {
+                agentIdParam
+        };
+        
+        StatementDescriptorMetadataFactory factory = new NumaDAOImplStatementDescriptorRegistration();
+        DescriptorMetadata data = factory.getDescriptorMetadata(NumaDAOImpl.QUERY_NUMA_INFO, params);
+        assertNotNull(data);
+        assertTrue(data.hasAgentId());
+        assertFalse(data.hasVmId());
+        assertEquals(agentId, data.getAgentId());
+    }
+    
+    @Test
+    public void unknownDescriptorThrowsException() {
+        StatementDescriptorMetadataFactory factory = new NumaDAOImplStatementDescriptorRegistration();
+        try {
+            factory.getDescriptorMetadata("QUERY foo-bar WHERE 'a' = 'b'", null);
+            fail("should have thrown exception");
+        } catch (IllegalArgumentException e) {
+            // pass
+        }
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/auth/DescriptorMetadata.java	Thu Jul 25 17:45:33 2013 +0200
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.storage.core.auth;
+
+import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.StatementDescriptor;
+
+/**
+ * Data describing a statement descriptor.
+ *
+ * @see StatementDescriptor
+ */
+public final class DescriptorMetadata {
+
+    private final String agentId;
+    private final String vmId;
+    
+    /**
+     * Constructor.
+     * 
+     * @param agentId A non-null string representation of the agent UUID.
+     * @param vmId A non-null string representation of the vm UUID.
+     */
+    public DescriptorMetadata(String agentId, String vmId) {
+        this.agentId = agentId;
+        this.vmId = vmId;
+    }
+    
+    /**
+     * Constructor, setting vmId null.
+     * 
+     * @param agentId A non-null string representation of the agent UUID.
+     */
+    public DescriptorMetadata(String agentId) {
+        this(agentId, null);
+    }
+    
+    /**
+     * Constructor, setting agentId and vmId null.
+     */
+    public DescriptorMetadata() {
+        this(null, null);
+    }
+
+    /**
+     * @return The value of the {@link Key#AGENT_ID} parameter which was used to
+     *         complete the query or null if there was none.
+     */
+    public final String getAgentId() {
+        return agentId; 
+    }
+    
+    /**
+     * @return The value of the {@link Key#VM_ID} parameter which was used to
+     *         complete the query or null if there was none.
+     */
+    public final String getVmId() {
+        return vmId;
+    }
+    
+    /**
+     * 
+     * @return true if there is a {@link Key#AGENT_ID} parameter, false otherwise.
+     */
+    public final boolean hasAgentId() {
+        return agentId != null;
+    }
+    
+    /**
+     * 
+     * @return true if there is a {@link Key#VM_ID} parameter, false otherwise.
+     */
+    public final boolean hasVmId() {
+        return vmId != null;
+    }
+    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/auth/StatementDescriptorMetadataFactory.java	Thu Jul 25 17:45:33 2013 +0200
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.storage.core.auth;
+
+import com.redhat.thermostat.storage.core.PreparedParameter;
+
+/**
+ * Factory for {@link DescriptorMetadata}. The web storage entpoint uses these
+ * factories in order to instantiate DescriptorMetadata objects for descriptors.
+ * 
+ */
+public interface StatementDescriptorMetadataFactory {
+
+    /**
+     * @param descriptor
+     *            The string representation of the statement descriptor for
+     *            which to get the metadata for.
+     * @param params
+     *            An array containing free parameter values for the given
+     *            descriptor. This is guaranteed to be not null and has the same
+     *            number of elements (in the same order) as the original query
+     *            descriptor had free variables.
+     * @return The metadata describing the descriptor.
+     * 
+     * @see DescriptorMetadata
+     */
+    DescriptorMetadata getDescriptorMetadata(String descriptor, PreparedParameter[] params);
+}
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/auth/StatementDescriptorRegistration.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/auth/StatementDescriptorRegistration.java	Thu Jul 25 17:45:33 2013 +0200
@@ -47,12 +47,14 @@
  * the set of all trusted statement descriptors.
  *
  */
-public interface StatementDescriptorRegistration {
+public interface StatementDescriptorRegistration extends StatementDescriptorMetadataFactory {
 
     /**
      * 
      * @return A set of string descriptors which should get
-     *         added to the trusted registry.
+     *         added to the trusted registry. The returned set
+     *         must not contain <code>null</code>.
      */
     Set<String> getStatementDescriptors();
+    
 }
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/dao/DAOImplStatementDescriptorRegistration.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/dao/DAOImplStatementDescriptorRegistration.java	Thu Jul 25 17:45:33 2013 +0200
@@ -39,6 +39,9 @@
 import java.util.HashSet;
 import java.util.Set;
 
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
+import com.redhat.thermostat.storage.core.auth.StatementDescriptorMetadataFactory;
 import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration;
 
 /**
@@ -47,8 +50,8 @@
  *
  */
 public class DAOImplStatementDescriptorRegistration implements
-        StatementDescriptorRegistration {
-
+        StatementDescriptorRegistration, StatementDescriptorMetadataFactory {
+    
     @Override
     public Set<String> getStatementDescriptors() {
         Set<String> daoDescs = new HashSet<>();
@@ -64,4 +67,46 @@
         return daoDescs;
     }
 
+    @Override
+    public DescriptorMetadata getDescriptorMetadata(String descriptor,
+            PreparedParameter[] params) {
+        if (descriptor.equals(AgentInfoDAOImpl.QUERY_AGENT_INFO)) {
+            String agentId = (String)params[0].getValue();
+            DescriptorMetadata metadata = new DescriptorMetadata(agentId);
+            return metadata;
+        } else if (descriptor.equals(AgentInfoDAOImpl.QUERY_ALIVE_AGENTS)) {
+            DescriptorMetadata metadata = new DescriptorMetadata();
+            return metadata;
+        } else if (descriptor.equals(AgentInfoDAOImpl.QUERY_ALL_AGENTS)) {
+            DescriptorMetadata metadata = new DescriptorMetadata();
+            return metadata;
+        } else if (descriptor.equals(BackendInfoDAOImpl.QUERY_BACKEND_INFO)) {
+            String agentId = (String)params[0].getValue();
+            DescriptorMetadata metadata = new DescriptorMetadata(agentId);
+            return metadata;
+        } else if (descriptor.equals(HostInfoDAOImpl.QUERY_HOST_INFO)) {
+            String agentId = (String)params[0].getValue();
+            DescriptorMetadata metadata = new DescriptorMetadata(agentId);
+            return metadata;
+        } else if (descriptor.equals(HostInfoDAOImpl.QUERY_ALL_HOSTS)) {
+            DescriptorMetadata metadata = new DescriptorMetadata();
+            return metadata;
+        } else if (descriptor.equals(NetworkInterfaceInfoDAOImpl.QUERY_NETWORK_INFO)) {
+            String agentId = (String)params[0].getValue();
+            DescriptorMetadata metadata = new DescriptorMetadata(agentId);
+            return metadata;
+        } else if (descriptor.equals(VmInfoDAOImpl.QUERY_ALL_VMS)) {
+            String agentId = (String)params[0].getValue();
+            DescriptorMetadata metadata = new DescriptorMetadata(agentId);
+            return metadata;
+        } else if (descriptor.equals(VmInfoDAOImpl.QUERY_VM_INFO)) {
+            String agentId = (String)params[0].getValue();
+            String vmId = (String)params[1].getValue();
+            DescriptorMetadata metadata = new DescriptorMetadata(agentId, vmId);
+            return metadata;
+        } else {
+            throw new IllegalArgumentException("Unknown descriptor: ->" + descriptor + "<-");
+        }
+    }
+
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/core/auth/DescriptorMetadataTest.java	Thu Jul 25 17:45:33 2013 +0200
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.storage.core.auth;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class DescriptorMetadataTest {
+
+    @Test
+    public void testBasics() {
+        DescriptorMetadata metaData = new DescriptorMetadata();
+        assertNull(metaData.getAgentId());
+        assertNull(metaData.getVmId());
+        assertFalse(metaData.hasAgentId());
+        assertFalse(metaData.hasVmId());
+        
+        String agentId = "some-agent-id";
+        
+        metaData = new DescriptorMetadata(agentId);
+        assertNotNull(metaData.getAgentId());
+        assertEquals(agentId, metaData.getAgentId());
+        assertTrue(metaData.hasAgentId());
+        assertNull(metaData.getVmId());
+        assertFalse(metaData.hasVmId());
+        
+        String vmId = "some vm id";
+        metaData = new DescriptorMetadata(agentId, vmId);
+        assertNotNull(metaData.getAgentId());
+        assertEquals(agentId, metaData.getAgentId());
+        assertTrue(metaData.hasAgentId());
+        assertNotNull(metaData.getVmId());
+        assertTrue(metaData.hasVmId());
+        assertEquals(vmId, metaData.getVmId());
+    }
+}
--- a/storage/core/src/test/java/com/redhat/thermostat/storage/internal/dao/DAOImplStatementDescriptorRegistrationTest.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/dao/DAOImplStatementDescriptorRegistrationTest.java	Thu Jul 25 17:45:33 2013 +0200
@@ -38,6 +38,11 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -46,6 +51,9 @@
 
 import org.junit.Test;
 
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
+import com.redhat.thermostat.storage.core.auth.StatementDescriptorMetadataFactory;
 import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration;
 
 public class DAOImplStatementDescriptorRegistrationTest {
@@ -74,4 +82,147 @@
         assertEquals(1, registrations.size());
         assertEquals(9, registrations.get(0).getStatementDescriptors().size());
     }
+    
+    @Test
+    public void canGetMetadataForAgentAliveQuery() {
+        StatementDescriptorMetadataFactory factory = new DAOImplStatementDescriptorRegistration();
+        DescriptorMetadata data = factory.getDescriptorMetadata(AgentInfoDAOImpl.QUERY_ALIVE_AGENTS, null);
+        assertNotNull(data);
+        assertFalse(data.hasAgentId());
+        assertFalse(data.hasVmId());
+    }
+    
+    @Test
+    public void canGetMetadataForAgentInfoQuery() {
+        PreparedParameter agentIdParam = mock(PreparedParameter.class);
+        String agentId = "agentId";
+        when(agentIdParam.getValue()).thenReturn(agentId);
+        PreparedParameter[] params = new PreparedParameter[] {
+                agentIdParam
+        };
+        
+        StatementDescriptorMetadataFactory factory = new DAOImplStatementDescriptorRegistration();
+        DescriptorMetadata data = factory.getDescriptorMetadata(AgentInfoDAOImpl.QUERY_AGENT_INFO, params);
+        assertNotNull(data);
+        assertFalse(data.hasVmId());
+        assertEquals(agentId, data.getAgentId());
+    }
+    
+    @Test
+    public void canGetMetadataForAllAgentsQuery() {
+        StatementDescriptorMetadataFactory factory = new DAOImplStatementDescriptorRegistration();
+        DescriptorMetadata data = factory.getDescriptorMetadata(AgentInfoDAOImpl.QUERY_ALL_AGENTS, null);
+        assertNotNull(data);
+        assertFalse(data.hasAgentId());
+        assertFalse(data.hasVmId());
+    }
+    
+    @Test
+    public void canGetMetadataForBackendInfoQuery() {
+        PreparedParameter agentIdParam = mock(PreparedParameter.class);
+        String agentId = "agentId";
+        when(agentIdParam.getValue()).thenReturn(agentId);
+        PreparedParameter[] params = new PreparedParameter[] {
+                agentIdParam
+        };
+        
+        StatementDescriptorMetadataFactory factory = new DAOImplStatementDescriptorRegistration();
+        DescriptorMetadata data = factory.getDescriptorMetadata(BackendInfoDAOImpl.QUERY_BACKEND_INFO, params);
+        assertNotNull(data);
+        assertTrue(data.hasAgentId());
+        assertFalse(data.hasVmId());
+        assertEquals(agentId, data.getAgentId());
+    }
+    
+    @Test
+    public void canGetMetadataForHostInfoQuery() {
+        PreparedParameter agentIdParam = mock(PreparedParameter.class);
+        String agentId = "agentId";
+        when(agentIdParam.getValue()).thenReturn(agentId);
+        PreparedParameter[] params = new PreparedParameter[] {
+                agentIdParam
+        };
+        
+        StatementDescriptorMetadataFactory factory = new DAOImplStatementDescriptorRegistration();
+        DescriptorMetadata data = factory.getDescriptorMetadata(HostInfoDAOImpl.QUERY_HOST_INFO, params);
+        assertNotNull(data);
+        assertTrue(data.hasAgentId());
+        assertFalse(data.hasVmId());
+        assertEquals(agentId, data.getAgentId());
+    }
+    
+    @Test
+    public void canGetMetadataForAllHostsQuery() {
+        StatementDescriptorMetadataFactory factory = new DAOImplStatementDescriptorRegistration();
+        DescriptorMetadata data = factory.getDescriptorMetadata(HostInfoDAOImpl.QUERY_ALL_HOSTS, null);
+        assertNotNull(data);
+        assertFalse(data.hasAgentId());
+        assertFalse(data.hasVmId());
+    }
+    
+    @Test
+    public void canGetMetadataForNetworkInfoQuery() {
+        PreparedParameter agentIdParam = mock(PreparedParameter.class);
+        String agentId = "agentId";
+        when(agentIdParam.getValue()).thenReturn(agentId);
+        PreparedParameter[] params = new PreparedParameter[] {
+                agentIdParam
+        };
+        
+        StatementDescriptorMetadataFactory factory = new DAOImplStatementDescriptorRegistration();
+        DescriptorMetadata data = factory.getDescriptorMetadata(NetworkInterfaceInfoDAOImpl.QUERY_NETWORK_INFO, params);
+        assertNotNull(data);
+        assertTrue(data.hasAgentId());
+        assertFalse(data.hasVmId());
+        assertEquals(agentId, data.getAgentId());
+    }
+    
+    @Test
+    public void canGetMetadataForVmInfoAllQuery() {
+        PreparedParameter agentIdParam = mock(PreparedParameter.class);
+        String agentId = "agentId";
+        when(agentIdParam.getValue()).thenReturn(agentId);
+        PreparedParameter[] params = new PreparedParameter[] {
+                agentIdParam
+        };
+        
+        StatementDescriptorMetadataFactory factory = new DAOImplStatementDescriptorRegistration();
+        DescriptorMetadata data = factory.getDescriptorMetadata(VmInfoDAOImpl.QUERY_ALL_VMS, params);
+        assertNotNull(data);
+        assertTrue(data.hasAgentId());
+        assertFalse(data.hasVmId());
+        assertEquals(agentId, data.getAgentId());
+    }
+    
+    @Test
+    public void canGetMetadataForSpecificVmInfoQuery() {
+        PreparedParameter agentIdParam = mock(PreparedParameter.class);
+        PreparedParameter vmIdParam = mock(PreparedParameter.class);
+        String agentId = "agentId";
+        String vmId = "vmId";
+        when(agentIdParam.getValue()).thenReturn(agentId);
+        when(vmIdParam.getValue()).thenReturn(vmId);
+        PreparedParameter[] params = new PreparedParameter[] {
+                agentIdParam, vmIdParam
+        };
+        
+        StatementDescriptorMetadataFactory factory = new DAOImplStatementDescriptorRegistration();
+        DescriptorMetadata data = factory.getDescriptorMetadata(VmInfoDAOImpl.QUERY_VM_INFO, params);
+        assertNotNull(data);
+        assertTrue(data.hasVmId());
+        assertTrue(data.hasAgentId());
+        assertEquals(agentId, data.getAgentId());
+        assertEquals(vmId, data.getVmId());
+    }
+    
+    @Test
+    public void unknownDescriptorThrowsException() {
+        StatementDescriptorMetadataFactory factory = new DAOImplStatementDescriptorRegistration();
+        try {
+            factory.getDescriptorMetadata("QUERY foo-bar WHERE 'a' = 'b'", null);
+            fail("should have thrown exception");
+        } catch (IllegalArgumentException e) {
+            // pass
+        }
+    }
 }
--- a/thread/collector/src/main/java/com/redhat/thermostat/thread/dao/impl/ThreadDaoImplStatementDescriptorRegistration.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/thread/collector/src/main/java/com/redhat/thermostat/thread/dao/impl/ThreadDaoImplStatementDescriptorRegistration.java	Thu Jul 25 17:45:33 2013 +0200
@@ -39,6 +39,9 @@
 import java.util.HashSet;
 import java.util.Set;
 
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
+import com.redhat.thermostat.storage.core.auth.StatementDescriptorMetadataFactory;
 import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration;
 
 /**
@@ -47,18 +50,37 @@
  *
  */
 public class ThreadDaoImplStatementDescriptorRegistration implements
-        StatementDescriptorRegistration {
+        StatementDescriptorRegistration, StatementDescriptorMetadataFactory {
 
-    @Override
-    public Set<String> getStatementDescriptors() {
-        Set<String> descs = new HashSet<>(6);
+    private final Set<String> descs;
+    
+    public ThreadDaoImplStatementDescriptorRegistration() {
+        descs = new HashSet<>(6);
         descs.add(ThreadDaoImpl.QUERY_LATEST_DEADLOCK_INFO);
         descs.add(ThreadDaoImpl.QUERY_LATEST_HARVESTING_STATUS);
         descs.add(ThreadDaoImpl.QUERY_LATEST_SUMMARY);
         descs.add(ThreadDaoImpl.QUERY_SUMMARY_SINCE);
         descs.add(ThreadDaoImpl.QUERY_THREAD_CAPS);
         descs.add(ThreadDaoImpl.QUERY_THREAD_INFO);
+    }
+    
+    @Override
+    public Set<String> getStatementDescriptors() {
         return descs;
     }
 
+    @Override
+    public DescriptorMetadata getDescriptorMetadata(String descriptor,
+            PreparedParameter[] params) {
+        if (descs.contains(descriptor)) {
+            // All queries hava agentId/vmId parameters
+            String agentId = (String)params[0].getValue();
+            String vmId = (String)params[1].getValue();
+            DescriptorMetadata metadata = new DescriptorMetadata(agentId, vmId);
+            return metadata;
+        } else {
+            throw new IllegalArgumentException("Unknown descriptor ->" + descriptor + "<-");
+        }
+    }
+
 }
--- a/thread/collector/src/test/java/com/redhat/thermostat/thread/dao/impl/ThreadDAOImplStatementDescriptorRegistrationTest.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/thread/collector/src/test/java/com/redhat/thermostat/thread/dao/impl/ThreadDAOImplStatementDescriptorRegistrationTest.java	Thu Jul 25 17:45:33 2013 +0200
@@ -40,6 +40,9 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -49,11 +52,26 @@
 
 import org.junit.Test;
 
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
+import com.redhat.thermostat.storage.core.auth.StatementDescriptorMetadataFactory;
 import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration;
 import com.redhat.thermostat.storage.internal.dao.DAOImplStatementDescriptorRegistration;
 
 public class ThreadDAOImplStatementDescriptorRegistrationTest {
 
+    static class Triple<S, T, U> {
+        final S first;
+        final T second;
+        final U third;
+
+        public Triple(S first, T second, U third) {
+            this.first = first;
+            this.second = second;
+            this.third = third;
+        }
+    }
+    
     @Test
     public void registersAllQueries() {
         ThreadDaoImplStatementDescriptorRegistration reg = new ThreadDaoImplStatementDescriptorRegistration();
@@ -88,4 +106,81 @@
         assertNotNull(threadDaoReg);
         assertEquals(6, threadDaoReg.getStatementDescriptors().size());
     }
+    
+    private Triple<String, String, PreparedParameter[]> setupForMetaDataTest() {
+        PreparedParameter agentIdParam = mock(PreparedParameter.class);
+        PreparedParameter vmIdParam = mock(PreparedParameter.class);
+        String agentId = "agentId";
+        String vmId = "vmId";
+        when(agentIdParam.getValue()).thenReturn(agentId);
+        when(vmIdParam.getValue()).thenReturn(vmId);
+        PreparedParameter[] params = new PreparedParameter[] { agentIdParam,
+                vmIdParam };
+        return new Triple<String, String, PreparedParameter[]>(agentId, vmId,
+                params);
+    }
+
+    private void assertThreadMetadata(
+            Triple<String, String, PreparedParameter[]> triple,
+            DescriptorMetadata data) {
+        assertNotNull(data);
+        assertEquals(triple.first, data.getAgentId());
+        assertEquals(triple.second, data.getVmId());
+    }
+    
+    @Test
+    public void canGetMetadataForLatestDeadlockQuery() {
+        Triple<String, String, PreparedParameter[]> triple = setupForMetaDataTest(); 
+        
+        StatementDescriptorMetadataFactory factory = new ThreadDaoImplStatementDescriptorRegistration();
+        DescriptorMetadata data = factory.getDescriptorMetadata(ThreadDaoImpl.QUERY_LATEST_DEADLOCK_INFO, triple.third);
+        assertThreadMetadata(triple, data);
+    }
+
+    @Test
+    public void canGetMetadataForThreadCapsQuery() {
+        Triple<String, String, PreparedParameter[]> triple = setupForMetaDataTest(); 
+        
+        StatementDescriptorMetadataFactory factory = new ThreadDaoImplStatementDescriptorRegistration();
+        DescriptorMetadata data = factory.getDescriptorMetadata(ThreadDaoImpl.QUERY_THREAD_CAPS, triple.third);
+        assertThreadMetadata(triple, data);
+    }
+    
+    @Test
+    public void canGetMetadataForLatestHarvestingStatusQuery() {
+        Triple<String, String, PreparedParameter[]> triple = setupForMetaDataTest(); 
+        
+        StatementDescriptorMetadataFactory factory = new ThreadDaoImplStatementDescriptorRegistration();
+        DescriptorMetadata data = factory.getDescriptorMetadata(ThreadDaoImpl.QUERY_LATEST_HARVESTING_STATUS, triple.third);
+        assertThreadMetadata(triple, data);
+    }
+    
+    @Test
+    public void canGetMetadataForLatestSummaryQuery() {
+        Triple<String, String, PreparedParameter[]> triple = setupForMetaDataTest(); 
+        
+        StatementDescriptorMetadataFactory factory = new ThreadDaoImplStatementDescriptorRegistration();
+        DescriptorMetadata data = factory.getDescriptorMetadata(ThreadDaoImpl.QUERY_LATEST_SUMMARY, triple.third);
+        assertThreadMetadata(triple, data);
+    }
+    
+    @Test
+    public void canGetMetadataThreadInfoQuery() {
+        Triple<String, String, PreparedParameter[]> triple = setupForMetaDataTest(); 
+        
+        StatementDescriptorMetadataFactory factory = new ThreadDaoImplStatementDescriptorRegistration();
+        DescriptorMetadata data = factory.getDescriptorMetadata(ThreadDaoImpl.QUERY_THREAD_INFO, triple.third);
+        assertThreadMetadata(triple, data);
+    }
+    
+    @Test
+    public void unknownDescriptorThrowsException() {
+        StatementDescriptorMetadataFactory factory = new ThreadDaoImplStatementDescriptorRegistration();
+        try {
+            factory.getDescriptorMetadata("QUERY foo-bar WHERE 'a' = 'b'", null);
+            fail("should have thrown exception");
+        } catch (IllegalArgumentException e) {
+            // pass
+        }
+    }
 }
--- a/vm-classstat/common/src/main/java/com/redhat/thermostat/vm/classstat/common/internal/VmClassStatDAOImplStatementDescriptorRegistration.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/vm-classstat/common/src/main/java/com/redhat/thermostat/vm/classstat/common/internal/VmClassStatDAOImplStatementDescriptorRegistration.java	Thu Jul 25 17:45:33 2013 +0200
@@ -39,7 +39,9 @@
 import java.util.HashSet;
 import java.util.Set;
 
+import com.redhat.thermostat.storage.core.PreparedParameter;
 import com.redhat.thermostat.storage.core.VmLatestPojoListGetter;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
 import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration;
 import com.redhat.thermostat.vm.classstat.common.VmClassStatDAO;
 
@@ -50,14 +52,28 @@
  */
 public class VmClassStatDAOImplStatementDescriptorRegistration implements
         StatementDescriptorRegistration {
+    
+    static final String QUERY = String.format(VmLatestPojoListGetter.VM_LATEST_QUERY_FORMAT,
+            VmClassStatDAO.vmClassStatsCategory.getName());
 
     @Override
     public Set<String> getStatementDescriptors() {
-        Set<String> descs = new HashSet<>();
-        String vmLatestClassStatQuery = String.format(VmLatestPojoListGetter.VM_LATEST_QUERY_FORMAT,
-                VmClassStatDAO.vmClassStatsCategory.getName());
-        descs.add(vmLatestClassStatQuery);
+        Set<String> descs = new HashSet<>(1);
+        descs.add(QUERY);
         return descs;
     }
 
+    @Override
+    public DescriptorMetadata getDescriptorMetadata(String descriptor,
+            PreparedParameter[] params) {
+        if (descriptor.equals(QUERY)) {
+            String agentId = (String)params[0].getValue();
+            String vmId = (String)params[1].getValue();
+            DescriptorMetadata metadata = new DescriptorMetadata(agentId, vmId);
+            return metadata;
+        } else {
+            throw new IllegalArgumentException("Unknown descriptor ->" + descriptor + "<-");
+        }
+    }
+
 }
--- a/vm-classstat/common/src/test/java/com/redhat/thermostat/vm/classstat/common/internal/VmClassStatDAOImplStatementDescriptorRegistrationTest.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/vm-classstat/common/src/test/java/com/redhat/thermostat/vm/classstat/common/internal/VmClassStatDAOImplStatementDescriptorRegistrationTest.java	Thu Jul 25 17:45:33 2013 +0200
@@ -40,6 +40,9 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -49,6 +52,9 @@
 
 import org.junit.Test;
 
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
+import com.redhat.thermostat.storage.core.auth.StatementDescriptorMetadataFactory;
 import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration;
 import com.redhat.thermostat.storage.internal.dao.DAOImplStatementDescriptorRegistration;
 
@@ -89,4 +95,33 @@
         assertEquals(1, vmClassStatReg.getStatementDescriptors().size());
     }
     
+    @Test
+    public void canGetMetadataForLatestClassStatQuery() {
+        PreparedParameter agentIdParam = mock(PreparedParameter.class);
+        PreparedParameter vmIdParam = mock(PreparedParameter.class);
+        String agentId = "agentId";
+        String vmId = "vmId";
+        when(agentIdParam.getValue()).thenReturn(agentId);
+        when(vmIdParam.getValue()).thenReturn(vmId);
+        PreparedParameter[] params = new PreparedParameter[] { agentIdParam,
+                vmIdParam };
+        
+        StatementDescriptorMetadataFactory factory = new VmClassStatDAOImplStatementDescriptorRegistration();
+        DescriptorMetadata data = factory.getDescriptorMetadata(VmClassStatDAOImplStatementDescriptorRegistration.QUERY, params);
+        assertNotNull(data);
+        assertEquals(agentId, data.getAgentId());
+        assertEquals(vmId, data.getVmId());
+    }
+    
+    @Test
+    public void unknownDescriptorThrowsException() {
+        StatementDescriptorMetadataFactory factory = new VmClassStatDAOImplStatementDescriptorRegistration();
+        try {
+            factory.getDescriptorMetadata("QUERY foo-bar WHERE 'a' = 'b'", null);
+            fail("should have thrown exception");
+        } catch (IllegalArgumentException e) {
+            // pass
+        }
+    }
+    
 }
--- a/vm-cpu/common/src/main/java/com/redhat/thermostat/vm/cpu/common/internal/VmCpuStatDAOImplStatementDescriptorRegistration.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/vm-cpu/common/src/main/java/com/redhat/thermostat/vm/cpu/common/internal/VmCpuStatDAOImplStatementDescriptorRegistration.java	Thu Jul 25 17:45:33 2013 +0200
@@ -39,7 +39,9 @@
 import java.util.HashSet;
 import java.util.Set;
 
+import com.redhat.thermostat.storage.core.PreparedParameter;
 import com.redhat.thermostat.storage.core.VmLatestPojoListGetter;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
 import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration;
 import com.redhat.thermostat.vm.cpu.common.VmCpuStatDAO;
 
@@ -50,14 +52,28 @@
  */
 public class VmCpuStatDAOImplStatementDescriptorRegistration implements
         StatementDescriptorRegistration {
+    
+    static final String descriptor = String.format(VmLatestPojoListGetter.VM_LATEST_QUERY_FORMAT,
+            VmCpuStatDAO.vmCpuStatCategory.getName());
 
     @Override
     public Set<String> getStatementDescriptors() {
         Set<String> descs = new HashSet<>();
-        String descriptor = String.format(VmLatestPojoListGetter.VM_LATEST_QUERY_FORMAT,
-                VmCpuStatDAO.vmCpuStatCategory.getName());
         descs.add(descriptor);
         return descs;
     }
+    
+    @Override
+    public DescriptorMetadata getDescriptorMetadata(String descriptor,
+            PreparedParameter[] params) {
+        if (descriptor.equals(VmCpuStatDAOImplStatementDescriptorRegistration.descriptor)) {
+            String agentId = (String)params[0].getValue();
+            String vmId = (String)params[1].getValue();
+            DescriptorMetadata metadata = new DescriptorMetadata(agentId, vmId);
+            return metadata;
+        } else {
+            throw new IllegalArgumentException("Unknown descriptor: ->" + descriptor + "<-");
+        }
+    }
 
 }
--- a/vm-cpu/common/src/test/java/com/redhat/thermostat/vm/cpu/common/internal/VmCpuStatDAOImplStatementDescriptorRegistrationTest.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/vm-cpu/common/src/test/java/com/redhat/thermostat/vm/cpu/common/internal/VmCpuStatDAOImplStatementDescriptorRegistrationTest.java	Thu Jul 25 17:45:33 2013 +0200
@@ -40,6 +40,9 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -49,6 +52,9 @@
 
 import org.junit.Test;
 
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
+import com.redhat.thermostat.storage.core.auth.StatementDescriptorMetadataFactory;
 import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration;
 import com.redhat.thermostat.storage.internal.dao.DAOImplStatementDescriptorRegistration;
 
@@ -88,4 +94,33 @@
         assertNotNull(vmCpuStatReg);
         assertEquals(1, vmCpuStatReg.getStatementDescriptors().size());
     }
+    
+    @Test
+    public void canGetMetadataForLatestCpuStatQuery() {
+        PreparedParameter agentIdParam = mock(PreparedParameter.class);
+        PreparedParameter vmIdParam = mock(PreparedParameter.class);
+        String agentId = "agentId";
+        String vmId = "vmId";
+        when(agentIdParam.getValue()).thenReturn(agentId);
+        when(vmIdParam.getValue()).thenReturn(vmId);
+        PreparedParameter[] params = new PreparedParameter[] { agentIdParam,
+                vmIdParam };
+        
+        StatementDescriptorMetadataFactory factory = new VmCpuStatDAOImplStatementDescriptorRegistration();
+        DescriptorMetadata data = factory.getDescriptorMetadata(VmCpuStatDAOImplStatementDescriptorRegistration.descriptor, params);
+        assertNotNull(data);
+        assertEquals(agentId, data.getAgentId());
+        assertEquals(vmId, data.getVmId());
+    }
+    
+    @Test
+    public void unknownDescriptorThrowsException() {
+        StatementDescriptorMetadataFactory factory = new VmCpuStatDAOImplStatementDescriptorRegistration();
+        try {
+            factory.getDescriptorMetadata("QUERY foo-bar WHERE 'a' = 'b'", null);
+            fail("should have thrown exception");
+        } catch (IllegalArgumentException e) {
+            // pass
+        }
+    }
 }
--- a/vm-gc/common/src/main/java/com/redhat/thermostat/vm/gc/common/internal/VmGcStatDAOImplStatementDescriptorRegistration.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/vm-gc/common/src/main/java/com/redhat/thermostat/vm/gc/common/internal/VmGcStatDAOImplStatementDescriptorRegistration.java	Thu Jul 25 17:45:33 2013 +0200
@@ -39,25 +39,44 @@
 import java.util.HashSet;
 import java.util.Set;
 
+import com.redhat.thermostat.storage.core.PreparedParameter;
 import com.redhat.thermostat.storage.core.VmLatestPojoListGetter;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
 import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration;
 import com.redhat.thermostat.vm.gc.common.VmGcStatDAO;
 
 /**
  * Registers the prepared query issued by this maven module via
  * {@link VmLatestPojoListGetter}.
- *
+ * 
  */
 public class VmGcStatDAOImplStatementDescriptorRegistration implements
         StatementDescriptorRegistration {
 
+    static final String descriptor = String.format(
+            VmLatestPojoListGetter.VM_LATEST_QUERY_FORMAT,
+            VmGcStatDAO.vmGcStatCategory.getName());
+
     @Override
     public Set<String> getStatementDescriptors() {
         Set<String> descs = new HashSet<>();
-        String descriptor = String.format(VmLatestPojoListGetter.VM_LATEST_QUERY_FORMAT,
-                VmGcStatDAO.vmGcStatCategory.getName());
         descs.add(descriptor);
         return descs;
     }
 
+    @Override
+    public DescriptorMetadata getDescriptorMetadata(String descriptor,
+            PreparedParameter[] params) {
+        if (descriptor
+                .equals(VmGcStatDAOImplStatementDescriptorRegistration.descriptor)) {
+            String agentId = (String) params[0].getValue();
+            String vmId = (String) params[1].getValue();
+            DescriptorMetadata metadata = new DescriptorMetadata(agentId, vmId);
+            return metadata;
+        } else {
+            throw new IllegalArgumentException("Unknown descriptor: ->"
+                    + descriptor + "<-");
+        }
+    }
+
 }
--- a/vm-gc/common/src/test/java/com/redhat/thermostat/vm/gc/common/internal/VmGcStatDAOImplStatementDescriptorRegistrationTest.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/vm-gc/common/src/test/java/com/redhat/thermostat/vm/gc/common/internal/VmGcStatDAOImplStatementDescriptorRegistrationTest.java	Thu Jul 25 17:45:33 2013 +0200
@@ -4,6 +4,9 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -13,6 +16,9 @@
 
 import org.junit.Test;
 
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
+import com.redhat.thermostat.storage.core.auth.StatementDescriptorMetadataFactory;
 import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration;
 import com.redhat.thermostat.storage.internal.dao.DAOImplStatementDescriptorRegistration;
 
@@ -88,4 +94,33 @@
         assertNotNull(vmGcStatReg);
         assertEquals(1, vmGcStatReg.getStatementDescriptors().size());
     }
+    
+    @Test
+    public void canGetMetadataForLatestGcStatQuery() {
+        PreparedParameter agentIdParam = mock(PreparedParameter.class);
+        PreparedParameter vmIdParam = mock(PreparedParameter.class);
+        String agentId = "agentId";
+        String vmId = "vmId";
+        when(agentIdParam.getValue()).thenReturn(agentId);
+        when(vmIdParam.getValue()).thenReturn(vmId);
+        PreparedParameter[] params = new PreparedParameter[] { agentIdParam,
+                vmIdParam };
+        
+        StatementDescriptorMetadataFactory factory = new VmGcStatDAOImplStatementDescriptorRegistration();
+        DescriptorMetadata data = factory.getDescriptorMetadata(VmGcStatDAOImplStatementDescriptorRegistration.descriptor, params);
+        assertNotNull(data);
+        assertEquals(agentId, data.getAgentId());
+        assertEquals(vmId, data.getVmId());
+    }
+    
+    @Test
+    public void unknownDescriptorThrowsException() {
+        StatementDescriptorMetadataFactory factory = new VmGcStatDAOImplStatementDescriptorRegistration();
+        try {
+            factory.getDescriptorMetadata("QUERY foo-bar WHERE 'a' = 'b'", null);
+            fail("should have thrown exception");
+        } catch (IllegalArgumentException e) {
+            // pass
+        }
+    }
 }
--- a/vm-heap-analysis/common/src/main/java/com/redhat/thermostat/vm/heap/analysis/common/internal/HeapDAOImplStatementDescriptorRegistration.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/vm-heap-analysis/common/src/main/java/com/redhat/thermostat/vm/heap/analysis/common/internal/HeapDAOImplStatementDescriptorRegistration.java	Thu Jul 25 17:45:33 2013 +0200
@@ -39,6 +39,8 @@
 import java.util.HashSet;
 import java.util.Set;
 
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
 import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration;
 
 /**
@@ -57,4 +59,23 @@
         return descs;
     }
 
+    @Override
+    public DescriptorMetadata getDescriptorMetadata(String descriptor,
+            PreparedParameter[] params) {
+        if (descriptor.equals(HeapDAOImpl.QUERY_ALL_HEAPS)) {
+            String agentId = (String)params[0].getValue();
+            String vmId = (String)params[1].getValue();
+            DescriptorMetadata metadata = new DescriptorMetadata(agentId, vmId);
+            return metadata;
+        } else if (descriptor.equals(HeapDAOImpl.QUERY_HEAP_INFO)) {
+            DescriptorMetadata metadata = new DescriptorMetadata();
+            return metadata;
+        } else {
+            throw new IllegalArgumentException("Unknown descriptor: ->"
+                    + descriptor + "<-");
+        }
+    }
+    
+    
+
 }
--- a/vm-heap-analysis/common/src/test/java/com/redhat/thermostat/vm/heap/analysis/common/internal/HeapDAOImplStatementDescriptorRegistrationTest.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/vm-heap-analysis/common/src/test/java/com/redhat/thermostat/vm/heap/analysis/common/internal/HeapDAOImplStatementDescriptorRegistrationTest.java	Thu Jul 25 17:45:33 2013 +0200
@@ -40,6 +40,9 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -49,6 +52,9 @@
 
 import org.junit.Test;
 
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
+import com.redhat.thermostat.storage.core.auth.StatementDescriptorMetadataFactory;
 import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration;
 import com.redhat.thermostat.storage.internal.dao.DAOImplStatementDescriptorRegistration;
 
@@ -88,4 +94,42 @@
         assertNotNull(heapDaoReg);
         assertEquals(2, heapDaoReg.getStatementDescriptors().size());
     }
+    
+    @Test
+    public void canGetMetadataForAllHeapsQuery() {
+        PreparedParameter agentIdParam = mock(PreparedParameter.class);
+        PreparedParameter vmIdParam = mock(PreparedParameter.class);
+        String agentId = "agentId";
+        String vmId = "vmId";
+        when(agentIdParam.getValue()).thenReturn(agentId);
+        when(vmIdParam.getValue()).thenReturn(vmId);
+        PreparedParameter[] params = new PreparedParameter[] { agentIdParam,
+                vmIdParam };
+        
+        StatementDescriptorMetadataFactory factory = new HeapDAOImplStatementDescriptorRegistration();
+        DescriptorMetadata data = factory.getDescriptorMetadata(HeapDAOImpl.QUERY_ALL_HEAPS, params);
+        assertNotNull(data);
+        assertEquals(agentId, data.getAgentId());
+        assertEquals(vmId, data.getVmId());
+    }
+    
+    @Test
+    public void canGetMetadataForHeapInfoQuery() {
+        StatementDescriptorMetadataFactory factory = new HeapDAOImplStatementDescriptorRegistration();
+        DescriptorMetadata data = factory.getDescriptorMetadata(HeapDAOImpl.QUERY_HEAP_INFO, null);
+        assertNotNull(data);
+        assertFalse(data.hasAgentId());
+        assertFalse(data.hasVmId());
+    }
+    
+    @Test
+    public void unknownDescriptorThrowsException() {
+        StatementDescriptorMetadataFactory factory = new HeapDAOImplStatementDescriptorRegistration();
+        try {
+            factory.getDescriptorMetadata("QUERY foo-bar WHERE 'a' = 'b'", null);
+            fail("should have thrown exception");
+        } catch (IllegalArgumentException e) {
+            // pass
+        }
+    }
 }
--- a/vm-jmx/common/src/main/java/com/redhat/thermostat/vm/jmx/common/internal/JmxNotificationDAOImplStatementDescriptorRegistration.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/vm-jmx/common/src/main/java/com/redhat/thermostat/vm/jmx/common/internal/JmxNotificationDAOImplStatementDescriptorRegistration.java	Thu Jul 25 17:45:33 2013 +0200
@@ -39,6 +39,8 @@
 import java.util.HashSet;
 import java.util.Set;
 
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
 import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration;
 
 /**
@@ -48,13 +50,33 @@
  */
 public class JmxNotificationDAOImplStatementDescriptorRegistration implements
         StatementDescriptorRegistration {
+    
+    private final Set<String> descs;
+    
+    public JmxNotificationDAOImplStatementDescriptorRegistration() {
+        descs = new HashSet<>(2);
+        descs.add(JmxNotificationDAOImpl.QUERY_LATEST_NOTIFICATION_STATUS);
+        descs.add(JmxNotificationDAOImpl.QUERY_NOTIFICATIONS);
+    }
 
     @Override
     public Set<String> getStatementDescriptors() {
-        Set<String> descs = new HashSet<>(2);
-        descs.add(JmxNotificationDAOImpl.QUERY_LATEST_NOTIFICATION_STATUS);
-        descs.add(JmxNotificationDAOImpl.QUERY_NOTIFICATIONS);
         return descs;
     }
 
+    @Override
+    public DescriptorMetadata getDescriptorMetadata(String descriptor,
+            PreparedParameter[] params) {
+        if (descs.contains(descriptor)) {
+            // both queries we know about have agent/vmId parameters
+            String agentId = (String)params[0].getValue();
+            String vmId = (String)params[1].getValue();
+            DescriptorMetadata metadata = new DescriptorMetadata(agentId, vmId);
+            return metadata;
+        } else {
+            throw new IllegalArgumentException("Unknown descriptor: ->"
+                    + descriptor + "<-");
+        }
+    }
+
 }
--- a/vm-jmx/common/src/test/java/com/redhat/thermostat/vm/jmx/common/internal/JmxNotificationDAOImplStatementDescriptorRegistrationTest.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/vm-jmx/common/src/test/java/com/redhat/thermostat/vm/jmx/common/internal/JmxNotificationDAOImplStatementDescriptorRegistrationTest.java	Thu Jul 25 17:45:33 2013 +0200
@@ -40,6 +40,9 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -49,6 +52,9 @@
 
 import org.junit.Test;
 
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
+import com.redhat.thermostat.storage.core.auth.StatementDescriptorMetadataFactory;
 import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration;
 import com.redhat.thermostat.storage.internal.dao.DAOImplStatementDescriptorRegistration;
 
@@ -88,4 +94,51 @@
         assertNotNull(jmxDaoReg);
         assertEquals(2, jmxDaoReg.getStatementDescriptors().size());
     }
+    
+    @Test
+    public void canGetMetadataForLatestNotificationsQuery() {
+        PreparedParameter agentIdParam = mock(PreparedParameter.class);
+        PreparedParameter vmIdParam = mock(PreparedParameter.class);
+        String agentId = "agentId";
+        String vmId = "vmId";
+        when(agentIdParam.getValue()).thenReturn(agentId);
+        when(vmIdParam.getValue()).thenReturn(vmId);
+        PreparedParameter[] params = new PreparedParameter[] { agentIdParam,
+                vmIdParam };
+        
+        StatementDescriptorMetadataFactory factory = new JmxNotificationDAOImplStatementDescriptorRegistration();
+        DescriptorMetadata data = factory.getDescriptorMetadata(JmxNotificationDAOImpl.QUERY_LATEST_NOTIFICATION_STATUS, params);
+        assertNotNull(data);
+        assertEquals(agentId, data.getAgentId());
+        assertEquals(vmId, data.getVmId());
+    }
+    
+    @Test
+    public void canGetMetadataForNotificationsQuery() {
+        PreparedParameter agentIdParam = mock(PreparedParameter.class);
+        PreparedParameter vmIdParam = mock(PreparedParameter.class);
+        String agentId = "agentId";
+        String vmId = "vmId";
+        when(agentIdParam.getValue()).thenReturn(agentId);
+        when(vmIdParam.getValue()).thenReturn(vmId);
+        PreparedParameter[] params = new PreparedParameter[] { agentIdParam,
+                vmIdParam };
+        
+        StatementDescriptorMetadataFactory factory = new JmxNotificationDAOImplStatementDescriptorRegistration();
+        DescriptorMetadata data = factory.getDescriptorMetadata(JmxNotificationDAOImpl.QUERY_NOTIFICATIONS, params);
+        assertNotNull(data);
+        assertEquals(agentId, data.getAgentId());
+        assertEquals(vmId, data.getVmId());
+    }
+    
+    @Test
+    public void unknownDescriptorThrowsException() {
+        StatementDescriptorMetadataFactory factory = new JmxNotificationDAOImplStatementDescriptorRegistration();
+        try {
+            factory.getDescriptorMetadata("QUERY foo-bar WHERE 'a' = 'b'", null);
+            fail("should have thrown exception");
+        } catch (IllegalArgumentException e) {
+            // pass
+        }
+    }
 }
--- a/vm-memory/common/src/main/java/com/redhat/thermostat/vm/memory/common/internal/VmMemoryStatDAOImplStatementDescriptorRegistration.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/vm-memory/common/src/main/java/com/redhat/thermostat/vm/memory/common/internal/VmMemoryStatDAOImplStatementDescriptorRegistration.java	Thu Jul 25 17:45:33 2013 +0200
@@ -39,7 +39,9 @@
 import java.util.HashSet;
 import java.util.Set;
 
+import com.redhat.thermostat.storage.core.PreparedParameter;
 import com.redhat.thermostat.storage.core.VmLatestPojoListGetter;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
 import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration;
 import com.redhat.thermostat.vm.memory.common.VmMemoryStatDAO;
 
@@ -50,15 +52,35 @@
  */
 public class VmMemoryStatDAOImplStatementDescriptorRegistration implements
         StatementDescriptorRegistration {
-
-    @Override
-    public Set<String> getStatementDescriptors() {
-        Set<String> descs = new HashSet<>(2);
+    
+    private final Set<String> descs;
+    
+    public VmMemoryStatDAOImplStatementDescriptorRegistration() {
+        descs = new HashSet<>(2);
         String descriptor = String.format(VmLatestPojoListGetter.VM_LATEST_QUERY_FORMAT, 
                 VmMemoryStatDAO.vmMemoryStatsCategory.getName());
         descs.add(descriptor);
         descs.add(VmMemoryStatDAOImpl.QUERY_LATEST);
+    }
+
+    @Override
+    public Set<String> getStatementDescriptors() {
         return descs;
     }
 
+    @Override
+    public DescriptorMetadata getDescriptorMetadata(String descriptor,
+            PreparedParameter[] params) {
+        if (descs.contains(descriptor)) {
+            // both queries have agentId/vmId
+            String agentId = (String)params[0].getValue();
+            String vmId = (String)params[1].getValue();
+            DescriptorMetadata metadata = new DescriptorMetadata(agentId, vmId);
+            return metadata;
+        } else {
+            throw new IllegalArgumentException("Unknown descriptor: ->"
+                    + descriptor + "<-");
+        }
+    }
+
 }
--- a/vm-memory/common/src/test/java/com/redhat/thermostat/vm/memory/common/internal/VmMemoryStatDAOImplStatementDescriptorRegistrationTest.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/vm-memory/common/src/test/java/com/redhat/thermostat/vm/memory/common/internal/VmMemoryStatDAOImplStatementDescriptorRegistrationTest.java	Thu Jul 25 17:45:33 2013 +0200
@@ -40,6 +40,9 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -49,8 +52,13 @@
 
 import org.junit.Test;
 
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.core.VmLatestPojoListGetter;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
+import com.redhat.thermostat.storage.core.auth.StatementDescriptorMetadataFactory;
 import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration;
 import com.redhat.thermostat.storage.internal.dao.DAOImplStatementDescriptorRegistration;
+import com.redhat.thermostat.vm.memory.common.VmMemoryStatDAO;
 
 public class VmMemoryStatDAOImplStatementDescriptorRegistrationTest {
 
@@ -88,4 +96,53 @@
         assertNotNull(vmMemoryDaoReg);
         assertEquals(2, vmMemoryDaoReg.getStatementDescriptors().size());
     }
+    
+    @Test
+    public void canGetMetadataForVmLatestVmMemoryStatsQuery() {
+        PreparedParameter agentIdParam = mock(PreparedParameter.class);
+        PreparedParameter vmIdParam = mock(PreparedParameter.class);
+        String agentId = "agentId";
+        String vmId = "vmId";
+        when(agentIdParam.getValue()).thenReturn(agentId);
+        when(vmIdParam.getValue()).thenReturn(vmId);
+        PreparedParameter[] params = new PreparedParameter[] { agentIdParam,
+                vmIdParam };
+        
+        String desc = String.format(VmLatestPojoListGetter.VM_LATEST_QUERY_FORMAT, 
+                VmMemoryStatDAO.vmMemoryStatsCategory.getName());
+        StatementDescriptorMetadataFactory factory = new VmMemoryStatDAOImplStatementDescriptorRegistration();
+        DescriptorMetadata data = factory.getDescriptorMetadata(desc, params);
+        assertNotNull(data);
+        assertEquals(agentId, data.getAgentId());
+        assertEquals(vmId, data.getVmId());
+    }
+    
+    @Test
+    public void canGetMetadataForVmLatestVmMemoryStats2Query() {
+        PreparedParameter agentIdParam = mock(PreparedParameter.class);
+        PreparedParameter vmIdParam = mock(PreparedParameter.class);
+        String agentId = "agentId";
+        String vmId = "vmId";
+        when(agentIdParam.getValue()).thenReturn(agentId);
+        when(vmIdParam.getValue()).thenReturn(vmId);
+        PreparedParameter[] params = new PreparedParameter[] { agentIdParam,
+                vmIdParam };
+        
+        StatementDescriptorMetadataFactory factory = new VmMemoryStatDAOImplStatementDescriptorRegistration();
+        DescriptorMetadata data = factory.getDescriptorMetadata(VmMemoryStatDAOImpl.QUERY_LATEST, params);
+        assertNotNull(data);
+        assertEquals(agentId, data.getAgentId());
+        assertEquals(vmId, data.getVmId());
+    }
+    
+    @Test
+    public void unknownDescriptorThrowsException() {
+        StatementDescriptorMetadataFactory factory = new VmMemoryStatDAOImplStatementDescriptorRegistration();
+        try {
+            factory.getDescriptorMetadata("QUERY foo-bar WHERE 'a' = 'b'", null);
+            fail("should have thrown exception");
+        } catch (IllegalArgumentException e) {
+            // pass
+        }
+    }
 }
--- a/web/server/pom.xml	Wed Aug 07 12:23:47 2013 -0400
+++ b/web/server/pom.xml	Thu Jul 25 17:45:33 2013 +0200
@@ -68,6 +68,13 @@
       <version>${jetty.version}</version>
       <scope>test</scope>
     </dependency>
+    <!-- Required for JAASLoginService used in WebStorageEndpointTest -->
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-plus</artifactId>
+      <version>${jetty.version}</version>
+      <scope>test</scope>
+    </dependency>
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
       <artifactId>jetty-webapp</artifactId>
--- a/web/server/src/main/java/com/redhat/thermostat/web/server/KnownDescriptorRegistry.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/KnownDescriptorRegistry.java	Thu Jul 25 17:45:33 2013 +0200
@@ -37,10 +37,13 @@
 package com.redhat.thermostat.web.server;
 
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.ServiceLoader;
 import java.util.Set;
 
+import com.redhat.thermostat.storage.core.auth.StatementDescriptorMetadataFactory;
 import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration;
 
 /**
@@ -51,26 +54,37 @@
 
     private static final ServiceLoader<StatementDescriptorRegistration> TRUSTED_DESCS = ServiceLoader
             .load(StatementDescriptorRegistration.class);
-    private final Iterable<StatementDescriptorRegistration> actualTrustedDescs;
+    private final Set<String> trustedSet;
+    private final Map<String, StatementDescriptorMetadataFactory> descriptorMetadataFactories;
     
     KnownDescriptorRegistry() {
-        this.actualTrustedDescs = TRUSTED_DESCS;
+        this(TRUSTED_DESCS);
     }
     
     KnownDescriptorRegistry(Iterable<StatementDescriptorRegistration> trustedDescs) {
-        this.actualTrustedDescs = trustedDescs;
-    }
-    
-    final Set<String> getRegisteredDescriptors() {
-        Set<String> trustedSet = new HashSet<>();
-        for (StatementDescriptorRegistration reg: actualTrustedDescs) {
+        descriptorMetadataFactories = new HashMap<>();
+        trustedSet = new HashSet<>();
+        for (StatementDescriptorRegistration reg: trustedDescs) {
             Set<String> newCandidates = reg.getStatementDescriptors();
             if (newCandidates.contains(null)) {
                 throw new IllegalStateException("null statement descriptor not acceptable!");
             }
+            // prepare the reverse lookup metadata map
+            StatementDescriptorMetadataFactory factory = (StatementDescriptorMetadataFactory) reg;
+            for (String descKey: newCandidates) {
+                descriptorMetadataFactories.put(descKey, factory);
+            }
             trustedSet.addAll(newCandidates);
         }
+    }
+    
+    final Set<String> getRegisteredDescriptors() {
         // return a read-only set of all descriptors
         return Collections.unmodifiableSet(trustedSet);
     }
+    
+    final Map<String, StatementDescriptorMetadataFactory> getDescriptorMetadataFactories() {
+        // return a read-only mapping
+        return Collections.unmodifiableMap(descriptorMetadataFactories);
+    }
 }
--- a/web/server/src/main/java/com/redhat/thermostat/web/server/PreparedStatementHolder.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/PreparedStatementHolder.java	Thu Jul 25 17:45:33 2013 +0200
@@ -37,6 +37,7 @@
 package com.redhat.thermostat.web.server;
 
 import com.redhat.thermostat.storage.core.PreparedStatement;
+import com.redhat.thermostat.storage.core.StatementDescriptor;
 import com.redhat.thermostat.storage.model.Pojo;
 
 class PreparedStatementHolder<T extends Pojo> {
@@ -44,11 +45,13 @@
     private final int id;
     private final PreparedStatement<T> stmt;
     private final Class<T> dataClass;
+    private final StatementDescriptor<T> desc;
     
-    PreparedStatementHolder(int id, PreparedStatement<T> stmt, Class<T> dataClass) {
+    PreparedStatementHolder(int id, PreparedStatement<T> stmt, Class<T> dataClass, StatementDescriptor<T> desc) {
         this.id = id;
         this.stmt = stmt;
         this.dataClass = dataClass;
+        this.desc = desc;
     }
 
     int getId() {
@@ -62,4 +65,8 @@
     Class<T> getDataClass() {
         return dataClass;
     }
+
+    StatementDescriptor<T> getStatementDescriptor() {
+        return desc;
+    }
 }
--- a/web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java	Thu Jul 25 17:45:33 2013 +0200
@@ -42,6 +42,9 @@
 import java.io.OutputStream;
 import java.io.Writer;
 import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.Principal;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -50,6 +53,7 @@
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import javax.security.auth.Subject;
 import javax.servlet.ServletConfig;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
@@ -86,7 +90,11 @@
 import com.redhat.thermostat.storage.core.StatementDescriptor;
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.core.Update;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
+import com.redhat.thermostat.storage.core.auth.StatementDescriptorMetadataFactory;
 import com.redhat.thermostat.storage.model.Pojo;
+import com.redhat.thermostat.storage.query.BinaryLogicalExpression;
+import com.redhat.thermostat.storage.query.BinaryLogicalOperator;
 import com.redhat.thermostat.storage.query.Expression;
 import com.redhat.thermostat.storage.query.Operator;
 import com.redhat.thermostat.web.common.ExpressionSerializer;
@@ -102,7 +110,9 @@
 import com.redhat.thermostat.web.common.WebQueryResponseSerializer;
 import com.redhat.thermostat.web.common.WebRemove;
 import com.redhat.thermostat.web.common.WebUpdate;
+import com.redhat.thermostat.web.server.auth.FilterResult;
 import com.redhat.thermostat.web.server.auth.Roles;
+import com.redhat.thermostat.web.server.auth.UserPrincipal;
 import com.redhat.thermostat.web.server.auth.WebStoragePathHandler;
 
 @SuppressWarnings("serial")
@@ -111,6 +121,7 @@
     static final String CMDC_AUTHORIZATION_GRANT_ROLE_PREFIX = "thermostat-cmdc-grant-";
     private static final String TOKEN_MANAGER_TIMEOUT_PARAM = "token-manager-timeout";
     private static final String TOKEN_MANAGER_KEY = "token-manager";
+    private static final String JETTY_JAAS_USER_PRINCIPAL_CLASS_NAME = "org.eclipse.jetty.plus.jaas.JAASUserPrincipal";
 
     // our strings can contain non-ASCII characters. Use UTF-8
     // see also PR 1344
@@ -139,6 +150,8 @@
     
     // read-only set of all known statement descriptors we trust and allow
     private Set<String> knownStatementDescriptors;
+    // read-only map of known descriptors => descriptor metadata
+    private Map<String, StatementDescriptorMetadataFactory> descMetadataFactories;
 
     @Override
     public void init(ServletConfig config) throws ServletException {
@@ -173,6 +186,7 @@
         // Set the set of statement descriptors which we trust
         KnownDescriptorRegistry descRegistry = KnownDescriptorRegistryFactory.getInstance();
         knownStatementDescriptors = descRegistry.getRegisteredDescriptors();
+        descMetadataFactories = descRegistry.getDescriptorMetadataFactories();
     }
     
     @Override
@@ -288,7 +302,7 @@
         String queryDescrParam = req.getParameter("query-descriptor");
         String categoryIdParam = req.getParameter("category-id");
         Integer catId = gson.fromJson(categoryIdParam, Integer.class);
-        Category<?> cat = getCategoryFromId(catId);
+        Category<T> cat = (Category<T>)getCategoryFromId(catId);
         WebPreparedStatementResponse response = new WebPreparedStatementResponse();
         if (cat == null) {
             // bad category? we refuse to accept this
@@ -297,7 +311,7 @@
             writeResponse(resp, response, WebPreparedStatementResponse.class);
             return;
         }
-        StatementDescriptor<?> desc = new StatementDescriptor<>(cat, queryDescrParam);
+        StatementDescriptor<T> desc = new StatementDescriptor<>(cat, queryDescrParam);
         // Check if descriptor is trusted (i.e. known)
         if (!knownStatementDescriptors.contains(desc.getQueryDescriptor())) {
             String msg = "Attempted to prepare a statement descriptor which we " +
@@ -338,7 +352,7 @@
             }
             PreparedStatementHolder<T> holder = new PreparedStatementHolder<T>(
                     currentPreparedStmtId, targetPreparedStatement,
-                    (Class<T>) cat.getDataClass());
+                    (Class<T>) cat.getDataClass(), desc);
             preparedStmts.put(desc, holder);
             preparedStatementIds.put(currentPreparedStmtId, holder);
             ParsedStatement<?> parsed = targetPreparedStatement
@@ -566,6 +580,7 @@
         WebPreparedStatement<T> stmt = gson.fromJson(queryParam, WebPreparedStatement.class);
         
         PreparedParameters p = stmt.getParams();
+        PreparedParameter[] params = p.getParams();
         PreparedStatementHolder<T> targetStmtHolder = getStatementHolderFromId(stmt.getStatementId());
         PreparedStatement<T> targetStmt = targetStmtHolder.getStmt();
         ParsedStatement<T> parsed = targetStmt.getParsedStatement();
@@ -573,15 +588,20 @@
         ArrayList<T> resultList = new ArrayList<>();
         WebQueryResponse<T> response = new WebQueryResponse<>();
         try {
-            targetQuery = (Query<T>)parsed.patchStatement(p.getParams());
+            targetQuery = (Query<T>)parsed.patchStatement(params);
             response.setResponseCode(WebQueryResponse.SUCCESS);
         } catch (IllegalPatchException e) {
             response.setResponseCode(WebQueryResponse.ILLEGAL_PATCH);
             writeResponse(resp, response, WebQueryResponse.class);
             return;
         }
-        // TODO: Do proper query filtering
-        targetQuery = fixQuery(targetQuery, stmt.getStatementId());
+        
+        StatementDescriptor<T> desc = targetStmtHolder.getStatementDescriptor();
+        StatementDescriptorMetadataFactory factory = descMetadataFactories.get(desc.getQueryDescriptor());
+        DescriptorMetadata actualMetadata = factory.getDescriptorMetadata(desc.getQueryDescriptor(), params);
+        
+        UserPrincipal userPrincipal = getUserPrincipal(req);
+        targetQuery = getQueryForPrincipal(userPrincipal, targetQuery, desc, actualMetadata);
         Cursor<T> cursor = targetQuery.execute();
         while (cursor.hasNext()) {
             resultList.add(cursor.next());
@@ -593,11 +613,87 @@
         response.setResultList(results);
         writeResponse(resp, response, WebQueryResponse.class);
     }
+    
+    private UserPrincipal getUserPrincipal(HttpServletRequest req) {
+        // Since we use our own JAAS based auth module this cast is safe
+        // for Tomcat and JBoss AS 7 since they allow for configuration of
+        // user principal classes. As for Jetty, it always uses
+        // JAASUserPrincipal, but that principal has access to the subject
+        // which in turn has our user principal added.
+        Principal principal = req.getUserPrincipal();
+        
+        if (principal.getClass().getName().equals(JETTY_JAAS_USER_PRINCIPAL_CLASS_NAME)) {
+            // Jetty has our principal on the accessible subject.
+            Subject subject = null;
+            try {
+                // Do this via reflection in order to avoid a hard dependency
+                // on jetty-plus.
+                Class<?> jassUserPrincipal = Class.forName(JETTY_JAAS_USER_PRINCIPAL_CLASS_NAME);
+                Method method = jassUserPrincipal.getDeclaredMethod("getSubject");
+                subject = (Subject)method.invoke(principal);
+            } catch (ClassNotFoundException | NoSuchMethodException
+                    | SecurityException | IllegalAccessException
+                    | IllegalArgumentException | InvocationTargetException e) {
+                logger.log(Level.WARNING, e.getMessage(), e);
+            }
+            if (subject == null) {
+                throw new IllegalStateException(
+                        "Could not retrieve subject from principal of type "
+                                + JETTY_JAAS_USER_PRINCIPAL_CLASS_NAME);
+            }
+            Set<UserPrincipal> userPrincipals = subject.getPrincipals(UserPrincipal.class);
+            if (userPrincipals.size() != 1) {
+                throw new IllegalStateException("More than one thermostat user principals!");
+            }
+            return userPrincipals.iterator().next();
+        } else {
+            // A simple cast should succeed for the non-jetty case. At the very
+            // least this should work for Tomcat and JBoss AS 7.
+            return (UserPrincipal)principal;
+        }
+        
+    }
 
-    @SuppressWarnings("rawtypes")
-    private <T extends Pojo> Query fixQuery(Query<T> targetQuery, int statementId) {
-        // TODO: Change the expression so as to perform proper filtering.
-        return targetQuery;
+    /*
+     * Performs the heavy lifting of query filtering. It adds a where expression
+     * and uses conjunction to the original, unfilterered, query.
+     */
+    private <T extends Pojo> Query<T> getQueryForPrincipal(
+            UserPrincipal userPrincipal, Query<T> patchedQuery,
+            StatementDescriptor<T> desc, DescriptorMetadata metaData) {
+        Expression whereExpression = patchedQuery.getWhereExpression();
+        FilterResult result = userPrincipal.getReadFilter(desc, metaData);
+        Expression authorizationExpression = null;
+        switch (result.getType()) {
+        case ALL: // fall-through. same as next case.
+        case QUERY_EXPRESSION:
+            authorizationExpression = result.getFilterExpression();
+            break;
+        case EMPTY:
+            return getEmptyQuery();
+        default:
+            throw new IllegalStateException("Unknown type!");
+        }
+        // Handled empty already
+        if (whereExpression == null) {
+            // no where, use auth expression only
+            if (authorizationExpression != null) {
+                patchedQuery.where(authorizationExpression);
+                return patchedQuery;
+            }
+        } else {
+            if (authorizationExpression != null) {
+                Expression andExpression = new BinaryLogicalExpression<Expression, Expression>(
+                        authorizationExpression, BinaryLogicalOperator.AND,
+                        whereExpression);
+                patchedQuery.where(andExpression);
+                return patchedQuery;
+            }
+        }
+        assert( (whereExpression != null && authorizationExpression == null) ||
+                (whereExpression == null && authorizationExpression == null));
+        // nothing to tag on
+        return patchedQuery;
     }
 
     private <T extends Pojo> PreparedStatementHolder<T> getStatementHolderFromId(int statementId) {
@@ -699,6 +795,61 @@
             return false;
         }
     }
+    
+    private <T extends Pojo> Query<T> getEmptyQuery() {
+        final Query<T> empty = new Query<T>() {
+
+            @Override
+            public void where(Expression expr) {
+                // must not be called.
+                throw new IllegalStateException();
+            }
+
+            @Override
+            public void sort(Key<?> key,
+                    com.redhat.thermostat.storage.core.Query.SortDirection direction) {
+                // must not be called.
+                throw new IllegalStateException();
+            }
+
+            @Override
+            public void limit(int n) {
+                // must not be called.
+                throw new IllegalStateException();
+            }
+
+            @Override
+            public Cursor<T> execute() {
+                return getEmptyCursor();
+            }
+
+            @Override
+            public Expression getWhereExpression() {
+                // must not be called.
+                throw new IllegalStateException();
+            }
+            
+        };
+        return empty;
+    }
+    
+    private <T extends Pojo> Cursor<T> getEmptyCursor() {
+        final Cursor<T> empty = new Cursor<T>() {
+
+            @Override
+            public boolean hasNext() {
+                return false;
+            }
+
+            @Override
+            public T next() {
+                // must not be called.
+                throw new IllegalStateException();
+            }
+            
+        };
+        return empty;
+    }
 
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/auth/AbstractFilter.java	Thu Jul 25 17:45:33 2013 +0200
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.web.server.auth;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import com.redhat.thermostat.storage.model.Pojo;
+import com.redhat.thermostat.storage.query.Expression;
+import com.redhat.thermostat.web.server.auth.FilterResult.ResultType;
+
+abstract class AbstractFilter<T extends Pojo> implements StatementFilter<T> {
+
+    private static final String ALL_ROLE_NAME = "ALL";
+    protected final Set<BasicRole> userRoles;
+    
+    protected AbstractFilter(Set<BasicRole> userRoles) {
+        this.userRoles = userRoles;
+    }
+    
+    protected Set<String> getGranted(String prefix) {
+        Set<String> allowedObjectsFromRoles = new HashSet<>();
+        for (BasicRole r : userRoles) {
+            if (r.getName().startsWith(prefix)) {
+                String allowedVm = r.getName().substring(
+                        prefix.length());
+                if (!allowedVm.equals(ALL_ROLE_NAME)) {
+                    allowedObjectsFromRoles.add(allowedVm);
+                }
+            }
+        }
+        return allowedObjectsFromRoles;
+    }
+    
+    protected FilterResult allWithExpression(Expression parentExpression) {
+        if (parentExpression != null) {
+            FilterResult result = new FilterResult(
+                    ResultType.QUERY_EXPRESSION);
+            result.setFilterExpression(parentExpression);
+            return result;
+        } else {
+            return new FilterResult(ResultType.ALL);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/auth/AgentIdFilter.java	Thu Jul 25 17:45:33 2013 +0200
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.web.server.auth;
+
+import java.util.Objects;
+import java.util.Set;
+
+import com.redhat.thermostat.storage.core.Category;
+import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.StatementDescriptor;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
+import com.redhat.thermostat.storage.model.Pojo;
+import com.redhat.thermostat.storage.query.Expression;
+import com.redhat.thermostat.storage.query.ExpressionFactory;
+import com.redhat.thermostat.web.server.auth.FilterResult.ResultType;
+
+/**
+ * Filters queries based on granted agent IDs.
+ * 
+ * @see also {@link Roles#GRANT_AGENTS_READ_ALL}
+ */
+class AgentIdFilter<T extends Pojo> extends AbstractFilter<T> {
+    
+    static final RolePrincipal GRANT_AGENTS_READ_ALL = new RolePrincipal(Roles.GRANT_AGENTS_READ_ALL);
+    static final String AGENTS_BY_AGENT_ID_GRANT_ROLE_PREFIX = "thermostat-agents-grant-read-agentId-";
+    
+    AgentIdFilter(Set<BasicRole> userRoles) {
+        super(userRoles);
+    }
+
+    @Override
+    public FilterResult applyFilter(StatementDescriptor<T> desc,
+            DescriptorMetadata metaData, Expression parentExpression) {
+        if (userRoles.contains(GRANT_AGENTS_READ_ALL)) {
+            return allWithExpression(parentExpression);
+        }
+        Category<T> category = desc.getCategory();
+        // user cannot read all agents
+        if (category.getKey(Key.AGENT_ID.getName()) != null) {
+            if (metaData.hasAgentId()) {
+                // if given agent ID not in granted list, return empty
+                String agentId = Objects.requireNonNull(metaData.getAgentId());
+                RolePrincipal agentIdGrantRole = new RolePrincipal(AGENTS_BY_AGENT_ID_GRANT_ROLE_PREFIX + agentId);
+                if (!userRoles.contains(agentIdGrantRole)) {
+                    return new FilterResult(ResultType.EMPTY);
+                } else {
+                    // agentId allowed
+                    return allWithExpression(parentExpression);
+                }
+            } else {
+                // tag on in clause for agentId
+                ExpressionFactory factory = new ExpressionFactory();
+                Set<String> agentIds = getGrantedAgentsByAgentId();
+                Expression filterExpression = factory.in(Key.AGENT_ID, agentIds, String.class);
+                FilterResult result = new FilterResult(ResultType.QUERY_EXPRESSION);
+                if (parentExpression != null) {
+                    filterExpression = factory.and(parentExpression, filterExpression);
+                }
+                result.setFilterExpression(filterExpression);
+                return result;
+            }
+        } else {
+            // can't do anything here, let it through for next stage.
+            return allWithExpression(parentExpression);
+        }
+    }
+
+    private Set<String> getGrantedAgentsByAgentId() {
+        return getGranted(AGENTS_BY_AGENT_ID_GRANT_ROLE_PREFIX);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/auth/FilterResult.java	Thu Jul 25 17:45:33 2013 +0200
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.web.server.auth;
+
+import com.redhat.thermostat.storage.query.Expression;
+
+public class FilterResult {
+
+    public enum ResultType {
+        /** Statement would return an empty result */
+        EMPTY,
+        /** Statement can go through unfiltered */
+        ALL,
+        /** Statement needs to be filtered with the given expression */
+        QUERY_EXPRESSION
+    }
+    
+    private final ResultType type;
+    private Expression filterExpression;
+    
+    FilterResult(ResultType type) {
+        this.type = type;
+    }
+
+    public Expression getFilterExpression() {
+        return filterExpression;
+    }
+
+    void setFilterExpression(Expression filterExpression) {
+        if (type != ResultType.QUERY_EXPRESSION) {
+            throw new IllegalStateException("Only query expression return type can have filter expression set");
+        }
+        this.filterExpression = filterExpression;
+    }
+
+    public ResultType getType() {
+        return type;
+    }
+    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/auth/HostnameFilter.java	Thu Jul 25 17:45:33 2013 +0200
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.web.server.auth;
+
+import java.util.Set;
+
+import com.redhat.thermostat.storage.core.StatementDescriptor;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
+import com.redhat.thermostat.storage.dao.HostInfoDAO;
+import com.redhat.thermostat.storage.model.Pojo;
+import com.redhat.thermostat.storage.query.Expression;
+import com.redhat.thermostat.storage.query.ExpressionFactory;
+import com.redhat.thermostat.web.server.auth.FilterResult.ResultType;
+
+/**
+ * 
+ * Filters based on granted host names.
+ * 
+ * @see also {@link Roles#GRANT_HOSTS_READ_ALL}
+ */
+class HostnameFilter<T extends Pojo> extends AbstractFilter<T> {
+    
+    static final RolePrincipal GRANT_HOSTS_READ_ALL = new RolePrincipal(Roles.GRANT_HOSTS_READ_ALL);
+    static final String HOSTS_BY_HOSTNAME_GRANT_ROLE_PREFIX = "thermostat-hosts-grant-read-hostname-";
+    
+    HostnameFilter(Set<BasicRole> userRoles) {
+        super(userRoles);
+    }
+
+    @Override
+    public FilterResult applyFilter(StatementDescriptor<T> desc,
+            DescriptorMetadata metaData, Expression parentExpression) {
+        if (userRoles.contains(GRANT_HOSTS_READ_ALL)) {
+            return allWithExpression(parentExpression);
+        }
+        // not all hosts are allowed
+        if (desc.getCategory().equals(HostInfoDAO.hostInfoCategory)) {
+            // add a hostname query expression
+            ExpressionFactory factory = new ExpressionFactory();
+            Set<String> hostnames = getGrantedHostsByHostname();
+            Expression filterExpression = factory.in(HostInfoDAO.hostNameKey, hostnames, String.class);
+            FilterResult result = new FilterResult(ResultType.QUERY_EXPRESSION);
+            if (parentExpression != null) {
+                filterExpression = factory.and(parentExpression, filterExpression);
+            }
+            result.setFilterExpression(filterExpression);
+            return result;
+        } else {
+            // can't do anything
+            return allWithExpression(parentExpression);
+        }
+    }
+
+    private Set<String> getGrantedHostsByHostname() {
+        return getGranted(HOSTS_BY_HOSTNAME_GRANT_ROLE_PREFIX);
+    }
+}
--- a/web/server/src/main/java/com/redhat/thermostat/web/server/auth/Roles.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/auth/Roles.java	Thu Jul 25 17:45:33 2013 +0200
@@ -41,6 +41,28 @@
  *
  */
 public interface Roles {
+    
+    /**
+     * Allows for a user to read records tied to any host.
+     */
+    final String GRANT_HOSTS_READ_ALL = "thermostat-hosts-grant-read-hostname-ALL";
+    /**
+     * Allows for a user to read records tied to any JVM id.
+     */
+    final String GRANT_VMS_READ_BY_VM_ID_ALL = "thermostat-vms-grant-read-vmId-ALL";
+    /**
+     * Allows for a user to read records tied to any username the JVM is running as.
+     */
+    final String GRANT_VMS_READ_BY_USERNAME_ALL = "thermostat-vms-grant-read-username-ALL";
+    /**
+     * Allows for a user to see records tied to any agent.
+     */
+    final String GRANT_AGENTS_READ_ALL = "thermostat-agents-grant-read-agentId-ALL";
+    /**
+     * Allows for a user to read all records. No restrictions are
+     * performed on as to what this user can see.
+     */
+    final String GRANT_READ_ALL = "thermostat-grant-read-ALL";
 
     final String APPEND = "thermostat-add";
     final String REPLACE = "thermostat-replace";
@@ -58,11 +80,12 @@
     final String LOGIN = "thermostat-login";
     final String ACCESS_REALM = "thermostat-realm";
     
-    final String[] ALL_ROLES = {
-            APPEND, REPLACE, UPDATE, DELETE, READ, GET_COUNT, LOAD_FILE,
-            SAVE_FILE, PURGE, REGISTER_CATEGORY, CMD_CHANNEL_GENERATE,
-            CMD_CHANNEL_VERIFY, LOGIN, ACCESS_REALM, PREPARE_STATEMENT
-    };
+    final String[] ALL_ROLES = { APPEND, REPLACE, UPDATE, DELETE, READ,
+            GET_COUNT, LOAD_FILE, SAVE_FILE, PURGE, REGISTER_CATEGORY,
+            CMD_CHANNEL_GENERATE, CMD_CHANNEL_VERIFY, LOGIN, ACCESS_REALM,
+            PREPARE_STATEMENT, GRANT_AGENTS_READ_ALL, GRANT_HOSTS_READ_ALL,
+            GRANT_VMS_READ_BY_USERNAME_ALL, GRANT_VMS_READ_BY_VM_ID_ALL,
+            GRANT_READ_ALL };
     
     final String[] AGENT_ROLES = {
             APPEND, REPLACE, UPDATE, DELETE, SAVE_FILE, PURGE,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/auth/StatementFilter.java	Thu Jul 25 17:45:33 2013 +0200
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.web.server.auth;
+
+import com.redhat.thermostat.storage.core.PreparedStatement;
+import com.redhat.thermostat.storage.core.StatementDescriptor;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
+import com.redhat.thermostat.storage.model.Pojo;
+import com.redhat.thermostat.storage.query.Expression;
+
+/**
+ * A filter suitable to get applied to {@link PreparedStatement}s before they
+ * are executed.
+ *
+ */
+interface StatementFilter<T extends Pojo> {
+
+    /**
+     * Applies this filter. Note that filters may be chained.
+     * 
+     * @param desc
+     *            The statement descriptor to apply the filter to.
+     * @param metaData
+     *            Metadata pertaining to the given descriptor.
+     * @param parentExpression
+     *            The Expression as constructed by the previous Filter. May be
+     *            null.
+     * @return A filtered result with the Expression set to use for filtering if
+     *         result type was QUERY_EXPRESSION.
+     */
+    FilterResult applyFilter(StatementDescriptor<T> desc,
+            DescriptorMetadata metaData, Expression parentExpression);
+    
+}
--- a/web/server/src/main/java/com/redhat/thermostat/web/server/auth/UserPrincipal.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/auth/UserPrincipal.java	Thu Jul 25 17:45:33 2013 +0200
@@ -38,16 +38,26 @@
 
 import java.io.Serializable;
 import java.security.Principal;
+import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 
+import com.redhat.thermostat.storage.core.StatementDescriptor;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
+import com.redhat.thermostat.storage.model.Pojo;
+import com.redhat.thermostat.storage.query.Expression;
+import com.redhat.thermostat.web.server.auth.FilterResult.ResultType;
+
 /**
  * Class representing thermostat users
  *
  */
 public class UserPrincipal implements Serializable, Principal {
     
+    private static final RolePrincipal GRANT_READ_ALL = new RolePrincipal(Roles.GRANT_READ_ALL);
+    
     private static final Set<BasicRole> EMPTY_SET = new HashSet<>(0);
     private static final long serialVersionUID = 2646753284881445421L;
     // The set of roles this user is a member of (they may be nested)
@@ -70,7 +80,7 @@
      * @return The set of roles this principal is a member of. An empty set
      *         if this user has no role memberships.
      */
-    public Set<BasicRole> getRoles() {
+    public final Set<BasicRole> getRoles() {
         if (roles == null) {
             return EMPTY_SET;
         }
@@ -87,6 +97,66 @@
         this.roles = Objects.requireNonNull(roles);
     }
     
+    /**
+     * Prepare a read filter for this user which can be applied prior executing
+     * trusted prepared queries.
+     * 
+     * @param desc
+     *            The descriptor for which to get the filter.
+     * @param metaData
+     *            Metadata for the provided descriptor.
+     * 
+     * @return An {@link FilterResult} which can be used to make a decision on
+     *         which records to return.
+     */
+    public <T extends Pojo> FilterResult getReadFilter(StatementDescriptor<T> desc, DescriptorMetadata metaData) {
+        if (getRoles().contains(GRANT_READ_ALL)) {
+            // user can see everything, no filtering is happening at all.
+            return new FilterResult(ResultType.ALL);
+        }
+        List<StatementFilter<T>> filters = buildFilters();
+        
+        // perform filtering using our list of filters
+        Expression parentExpression = null;
+        FilterResult overallResult = null;
+        for (StatementFilter<T> filter: filters) {
+            overallResult = filter.applyFilter(desc, metaData, parentExpression);
+            switch (overallResult.getType()) {
+            case ALL: // fall-through, expression == null
+            case QUERY_EXPRESSION:
+                // continue filtering
+                parentExpression = overallResult.getFilterExpression();
+                break;
+            case EMPTY:
+                // no point continuing, already nothing
+                return overallResult;
+            default:
+                throw new IllegalStateException("Unknown result type!");
+            }
+        }
+        // done filtering
+        return overallResult;
+    }
+
+    /*
+     * Filters are applied in order. Passing through one which didn't return
+     * empty continues the filter chain. Currently, we filter by:
+     * agent IDs -> hostnames -> vm IDs -> vm usernames
+     */
+    private <T extends Pojo> List<StatementFilter<T>> buildFilters() {
+        List<StatementFilter<T>> filters = new ArrayList<>();
+        Set<BasicRole> roles = getRoles();
+        AgentIdFilter<T> agentIdFilter = new AgentIdFilter<>(roles);
+        HostnameFilter<T> hostnameFilter = new HostnameFilter<>(roles);
+        VmIdFilter<T> vmIdFilter = new VmIdFilter<>(roles);
+        VmUsernameFilter<T> vmUsernameFilter = new VmUsernameFilter<>(roles);
+        filters.add(agentIdFilter);
+        filters.add(hostnameFilter);
+        filters.add(vmIdFilter);
+        filters.add(vmUsernameFilter);
+        return filters;
+    }
+    
     @Override
     public String getName() {
         return name;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/auth/VmIdFilter.java	Thu Jul 25 17:45:33 2013 +0200
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.web.server.auth;
+
+import java.util.Set;
+
+import com.redhat.thermostat.storage.core.Category;
+import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.StatementDescriptor;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
+import com.redhat.thermostat.storage.model.Pojo;
+import com.redhat.thermostat.storage.query.Expression;
+import com.redhat.thermostat.storage.query.ExpressionFactory;
+import com.redhat.thermostat.web.server.auth.FilterResult.ResultType;
+
+/**
+ * Filters based on granted VM IDs.
+ * 
+ * @see also {@link Roles#GRANT_VMS_READ_BY_VM_ID_ALL}
+ */
+class VmIdFilter<T extends Pojo> extends AbstractFilter<T> {
+    
+    static final RolePrincipal GRANT_VMS_BY_ID_READ_ALL = new RolePrincipal(Roles.GRANT_VMS_READ_BY_VM_ID_ALL);
+    static final String VMS_BY_VM_ID_GRANT_ROLE_PREFIX = "thermostat-vms-grant-read-vmId-";
+    
+    VmIdFilter(Set<BasicRole> userRoles) {
+        super(userRoles);
+    }
+
+    @Override
+    public FilterResult applyFilter(StatementDescriptor<T> desc,
+            DescriptorMetadata metaData, Expression parentExpression) {
+        if (userRoles.contains(GRANT_VMS_BY_ID_READ_ALL)) {
+            return allWithExpression(parentExpression);
+        }
+        // perform filtering on vmId
+        Category<T> category = desc.getCategory();
+        if (category.getKey(Key.VM_ID.getName()) != null) {
+            if (metaData.hasVmId()) {
+                String vmId = metaData.getVmId();
+                RolePrincipal grantedByVmId = new RolePrincipal(VMS_BY_VM_ID_GRANT_ROLE_PREFIX + vmId);
+                if (!userRoles.contains(grantedByVmId)) {
+                    return new FilterResult(ResultType.EMPTY);
+                } else {
+                    return allWithExpression(parentExpression);
+                }
+            } else {
+                // add vmId IN clause
+                ExpressionFactory factory = new ExpressionFactory();
+                Set<String> vmIds = getGrantedVmsByVmId();
+                Expression filterExpression = factory.in(Key.VM_ID, vmIds, String.class);
+                if (parentExpression != null) {
+                    filterExpression = factory.and(parentExpression, filterExpression);
+                }
+                FilterResult result = new FilterResult(ResultType.QUERY_EXPRESSION);
+                result.setFilterExpression(filterExpression);
+                return result;
+            }
+        } else {
+            // can't do much
+            return allWithExpression(parentExpression);
+        }
+    }
+    
+    private Set<String> getGrantedVmsByVmId() {
+        return getGranted(VMS_BY_VM_ID_GRANT_ROLE_PREFIX);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/auth/VmUsernameFilter.java	Thu Jul 25 17:45:33 2013 +0200
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.web.server.auth;
+
+import java.util.Set;
+
+import com.redhat.thermostat.storage.core.StatementDescriptor;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
+import com.redhat.thermostat.storage.dao.VmInfoDAO;
+import com.redhat.thermostat.storage.model.Pojo;
+import com.redhat.thermostat.storage.query.Expression;
+import com.redhat.thermostat.storage.query.ExpressionFactory;
+import com.redhat.thermostat.web.server.auth.FilterResult.ResultType;
+
+/**
+ * Filters based on the granted VM usernames. I.e. the Unix username the
+ * individual VM runs as.
+ * 
+ * @see also {@link Roles#GRANT_VMS_READ_BY_USERNAME_ALL}
+ */
+class VmUsernameFilter<T extends Pojo> extends AbstractFilter<T> {
+
+    static final RolePrincipal GRANT_VMS_USERNAME_READ_ALL = new RolePrincipal(Roles.GRANT_VMS_READ_BY_USERNAME_ALL);
+    static final String VMS_BY_USERNAME_GRANT_ROLE_PREFIX = "thermostat-vms-grant-read-username-";
+    
+    VmUsernameFilter(Set<BasicRole> userRoles) {
+        super(userRoles);
+    }
+    
+    @Override
+    public FilterResult applyFilter(StatementDescriptor<T> desc,
+            DescriptorMetadata metaData, Expression parentExpression) {
+        if (userRoles.contains(GRANT_VMS_USERNAME_READ_ALL)) {
+            return allWithExpression(parentExpression);
+        }
+        // perform filtering on the username of the vm
+        if (desc.getCategory().equals(VmInfoDAO.vmInfoCategory)) {
+            ExpressionFactory factory = new ExpressionFactory();
+            Set<String> vmUsernames = getGrantedVmsByUsername();
+            Expression filterExpression = factory.in(VmInfoDAO.usernameKey, vmUsernames, String.class);
+            if (parentExpression != null) {
+                filterExpression = factory.and(parentExpression, filterExpression);
+            }
+            FilterResult result = new FilterResult(ResultType.QUERY_EXPRESSION);
+            result.setFilterExpression(filterExpression);
+            return result;
+        } else {
+            // can't do much
+            return allWithExpression(parentExpression);
+        }
+    }
+    
+    private Set<String> getGrantedVmsByUsername() {
+        return getGranted(VMS_BY_USERNAME_GRANT_ROLE_PREFIX);
+    }
+
+}
--- a/web/server/src/main/webapp/WEB-INF/web.xml	Wed Aug 07 12:23:47 2013 -0400
+++ b/web/server/src/main/webapp/WEB-INF/web.xml	Thu Jul 25 17:45:33 2013 +0200
@@ -19,4 +19,23 @@
     <servlet-name>reststorage-servlet</servlet-name>
     <url-pattern>/storage/*</url-pattern>
   </servlet-mapping>
+  
+  <security-constraint>
+    <web-resource-collection>
+      <web-resource-name>Entire Application</web-resource-name>
+      <url-pattern>/*</url-pattern>
+    </web-resource-collection>
+    <auth-constraint>
+      <role-name>thermostat-realm</role-name>
+    </auth-constraint>
+  </security-constraint>
+
+  <login-config>
+    <auth-method>BASIC</auth-method>
+    <realm-name>Thermostat Realm</realm-name>
+  </login-config>
+
+  <security-role>
+    <role-name>thermostat-realm</role-name>
+  </security-role>
 </web-app>
--- a/web/server/src/test/java/com/redhat/thermostat/web/server/KnownDescriptorRegistryTest.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/web/server/src/test/java/com/redhat/thermostat/web/server/KnownDescriptorRegistryTest.java	Thu Jul 25 17:45:33 2013 +0200
@@ -43,10 +43,13 @@
 
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Set;
 
 import org.junit.Test;
 
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
 import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration;
 
 public class KnownDescriptorRegistryTest {
@@ -87,9 +90,8 @@
         descs.add("QUERY test WHERE 'a' = ?s");
         descs.add(null);
         Iterable<StatementDescriptorRegistration> regs = getRegs(descs);
-        KnownDescriptorRegistry reg = new KnownDescriptorRegistry(regs);
         try {
-            reg.getRegisteredDescriptors();
+            new KnownDescriptorRegistry(regs);
             fail("Should not have accepted null descriptor");
         } catch (IllegalStateException e) {
             assertEquals("null statement descriptor not acceptable!", e.getMessage());
@@ -98,7 +100,7 @@
 
     private Iterable<StatementDescriptorRegistration> getRegs(Set<String> descs) {
         StatementDescriptorRegistration reg = new TestStatementDescriptorRegistration(
-                descs);
+                descs, null);
         StatementDescriptorRegistration[] regs = new StatementDescriptorRegistration[] { reg };
         return Arrays.asList(regs);
     }
@@ -106,15 +108,23 @@
     private static class TestStatementDescriptorRegistration implements StatementDescriptorRegistration {
 
         private final Set<String> descs;
+        private final Map<String, DescriptorMetadata> metadata;
         
-        private TestStatementDescriptorRegistration(Set<String> descs) {
+        private TestStatementDescriptorRegistration(Set<String> descs, Map<String, DescriptorMetadata> metadata) {
             this.descs = descs;
+            this.metadata = metadata;
         }
         
         @Override
         public Set<String> getStatementDescriptors() {
             return descs;
         }
+
+        @Override
+        public DescriptorMetadata getDescriptorMetadata(String descriptor,
+                PreparedParameter[] params) {
+            return metadata.get(descriptor);
+        }
         
     }
 }
--- a/web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java	Thu Jul 25 17:45:33 2013 +0200
@@ -62,6 +62,7 @@
 import java.net.ProtocolException;
 import java.net.URL;
 import java.net.URLEncoder;
+import java.security.Principal;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -69,11 +70,13 @@
 import java.util.Map;
 import java.util.Set;
 
+import javax.security.auth.Subject;
 import javax.servlet.ServletConfig;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletResponse;
 
 import org.apache.commons.codec.binary.Base64;
+import org.eclipse.jetty.plus.jaas.JAASLoginService;
 import org.eclipse.jetty.security.DefaultUserIdentity;
 import org.eclipse.jetty.security.LoginService;
 import org.eclipse.jetty.security.MappedLoginService;
@@ -106,11 +109,12 @@
 import com.redhat.thermostat.storage.core.Remove;
 import com.redhat.thermostat.storage.core.Replace;
 import com.redhat.thermostat.storage.core.StatementDescriptor;
-import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.core.Update;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
 import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration;
 import com.redhat.thermostat.storage.model.BasePojo;
 import com.redhat.thermostat.storage.model.Pojo;
+import com.redhat.thermostat.storage.query.BinarySetMembershipExpression;
 import com.redhat.thermostat.storage.query.Expression;
 import com.redhat.thermostat.storage.query.ExpressionFactory;
 import com.redhat.thermostat.storage.query.Operator;
@@ -129,7 +133,10 @@
 import com.redhat.thermostat.web.common.WebQueryResponseSerializer;
 import com.redhat.thermostat.web.common.WebRemove;
 import com.redhat.thermostat.web.common.WebUpdate;
+import com.redhat.thermostat.web.server.auth.BasicRole;
+import com.redhat.thermostat.web.server.auth.RolePrincipal;
 import com.redhat.thermostat.web.server.auth.Roles;
+import com.redhat.thermostat.web.server.auth.UserPrincipal;
 import com.redhat.thermostat.web.server.auth.WebStoragePathHandler;
 
 public class WebStorageEndpointTest {
@@ -207,7 +214,6 @@
     private void startServer(int port, LoginService loginService) throws Exception {
         server = new Server(port);
         WebAppContext ctx = new WebAppContext("src/main/webapp", "/");
-        ctx.getSecurityHandler().setAuthMethod("BASIC");
         ctx.getSecurityHandler().setLoginService(loginService);
         server.setHandler(ctx);
         server.start();
@@ -284,11 +290,12 @@
         String strDescriptor = "QUERY " + category.getName() + " WHERE '" + key1.getName() + "' = ?s SORT '" + key1.getName() + "' DSC LIMIT 42";
         // setup a statement descriptor set so as to mimic a not trusted desc
         String wrongDescriptor = "QUERY something-other WHERE 'a' = true";
-        setupTrustedStatementRegistry(wrongDescriptor);
+        setupTrustedStatementRegistry(wrongDescriptor, null);
         
         String[] roleNames = new String[] {
                 Roles.REGISTER_CATEGORY,
-                Roles.PREPARE_STATEMENT
+                Roles.PREPARE_STATEMENT,
+                Roles.ACCESS_REALM,
         };
         String testuser = "testuser";
         String password = "testpassword";
@@ -331,16 +338,20 @@
     @Test
     public void authorizedPrepareQueryWithTrustedDescriptor() throws Exception {
         String strDescriptor = "QUERY " + category.getName() + " WHERE '" + key1.getName() + "' = ?s SORT '" + key1.getName() + "' DSC LIMIT 42";
-        setupTrustedStatementRegistry(strDescriptor);
+        // metadata which basically does no filtering. There's another test which
+        // asserts only allowed data (via ACL) gets returned.
+        DescriptorMetadata metadata = new DescriptorMetadata();        
+        setupTrustedStatementRegistry(strDescriptor, metadata);
         
-        String[] roleNames = new String[] {
-                Roles.REGISTER_CATEGORY,
-                Roles.PREPARE_STATEMENT,
-                Roles.READ
-        };
-        String testuser = "testuser";
-        String password = "testpassword";
-        final LoginService loginService = new TestLoginService(testuser, password, roleNames); 
+        Set<BasicRole> roles = new HashSet<>();
+        roles.add(new RolePrincipal(Roles.REGISTER_CATEGORY));
+        roles.add(new RolePrincipal(Roles.PREPARE_STATEMENT));
+        roles.add(new RolePrincipal(Roles.READ));
+        roles.add(new RolePrincipal(Roles.ACCESS_REALM));
+        UserPrincipal testUser = new UserPrincipal("ignored1");
+        testUser.setRoles(roles);
+        
+        final LoginService loginService = new TestJAASLoginService(testUser);
         port = FreePortFinder.findFreePort(new TryPort() {
             
             @Override
@@ -348,7 +359,7 @@
                 startServer(port, loginService);
             }
         });
-        registerCategory(testuser, password);
+        registerCategory("ignored1", "ignored2");
         
         TestClass expected1 = new TestClass();
         expected1.setKey1("fluff1");
@@ -381,7 +392,7 @@
         URL url = new URL(endpoint + "/prepare-statement");
         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
         conn.setRequestMethod("POST");
-        sendAuthentication(conn, testuser, password);
+        sendAuthentication(conn, "ignored1", "ignored2");
         conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
         conn.setDoInput(true);
         conn.setDoOutput(true);
@@ -410,7 +421,7 @@
         url = new URL(endpoint + "/query-execute");
         HttpURLConnection conn2 = (HttpURLConnection) url.openConnection();
         conn2.setRequestMethod("POST");
-        sendAuthentication(conn2, testuser, password);
+        sendAuthentication(conn2, "ignored1", "ignored2");
         conn2.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
         conn2.setDoInput(true);
         conn2.setDoOutput(true);
@@ -432,14 +443,152 @@
 
         assertEquals("application/json; charset=UTF-8", conn2.getContentType());
         verify(mockMongoQuery).execute();
+        verify(mockMongoQuery).getWhereExpression();
         verifyNoMoreInteractions(mockMongoQuery);
     }
     
-    private void setupTrustedStatementRegistry(String strDescriptor) {
+    /*
+     * 
+     * This test simulates a case where the mongo query would return more than
+     * a user can see. In this case only records matching agentIds which are
+     * allowed via roles should get returned.
+     */
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @Test
+    public void authorizedFilteredQuery() throws Exception {
+        Category oldCategory = category;
+        // redefine category to include the agentId key in the category.
+        // undone via a the try-finally block.
+        category = new Category("test-authorizedFilteredQuery", TestClass.class, key1, key2, Key.AGENT_ID);
+        try {
+            String strDescriptor = "QUERY " + category.getName() + " WHERE '" +
+                    key1.getName() + "' = ?s SORT '" + key1.getName() + "' DSC LIMIT 42";
+            DescriptorMetadata metadata = new DescriptorMetadata();
+            setupTrustedStatementRegistry(strDescriptor, metadata);
+            
+            Set<BasicRole> roles = new HashSet<>();
+            roles.add(new RolePrincipal(Roles.REGISTER_CATEGORY));
+            roles.add(new RolePrincipal(Roles.PREPARE_STATEMENT));
+            roles.add(new RolePrincipal(Roles.READ));
+            roles.add(new RolePrincipal(Roles.ACCESS_REALM));
+            String fakeAgentId = "someAgentId";
+            roles.add(new RolePrincipal("thermostat-agents-grant-read-agentId-" + fakeAgentId));
+            UserPrincipal testUser = new UserPrincipal("ignored1");
+            testUser.setRoles(roles);
+            
+            final LoginService loginService = new TestJAASLoginService(testUser);
+            port = FreePortFinder.findFreePort(new TryPort() {
+                
+                @Override
+                public void tryPort(int port) throws Exception {
+                    startServer(port, loginService);
+                }
+            });
+            registerCategory("ignored1", "ignored2");
+            
+            TestClass expected1 = new TestClass();
+            expected1.setKey1("fluff1");
+            expected1.setKey2(42);
+            TestClass expected2 = new TestClass();
+            expected2.setKey1("fluff2");
+            expected2.setKey2(43);
+            // prepare-statement does this under the hood
+            Query<TestClass> mockMongoQuery = mock(Query.class);
+            
+            when(mockStorage.createQuery(eq(category))).thenReturn(mockMongoQuery);
+    
+            Cursor<TestClass> cursor = mock(Cursor.class);
+            when(cursor.hasNext()).thenReturn(true).thenReturn(true).thenReturn(false);
+            when(cursor.next()).thenReturn(expected1).thenReturn(expected2);
+            
+            PreparedStatement mockPreparedQuery = mock(PreparedStatement.class);
+            when(mockStorage.prepareStatement(any(StatementDescriptor.class))).thenReturn(mockPreparedQuery);
+            
+            ParsedStatement mockParsedStatement = mock(ParsedStatement.class);
+            when(mockParsedStatement.getNumParams()).thenReturn(1);
+            when(mockParsedStatement.patchStatement(any(PreparedParameter[].class))).thenReturn(mockMongoQuery);
+            when(mockPreparedQuery.getParsedStatement()).thenReturn(mockParsedStatement);
+            
+            // The web layer
+            when(mockPreparedQuery.executeQuery()).thenReturn(cursor);
+            // And the mongo layer
+            when(mockMongoQuery.execute()).thenReturn(cursor);
+    
+            String endpoint = getEndpoint();
+            URL url = new URL(endpoint + "/prepare-statement");
+            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+            conn.setRequestMethod("POST");
+            sendAuthentication(conn, "ignored1", "ignored2");
+            conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
+            conn.setDoInput(true);
+            conn.setDoOutput(true);
+            Gson gson = new GsonBuilder()
+                    .registerTypeHierarchyAdapter(WebQueryResponse.class, new WebQueryResponseSerializer<>())
+                    .registerTypeAdapter(Pojo.class, new ThermostatGSONConverter())
+                    .registerTypeAdapter(WebPreparedStatement.class, new WebPreparedStatementSerializer())
+                    .registerTypeAdapter(PreparedParameter.class, new PreparedParameterSerializer()).create();
+            OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
+            String body = "query-descriptor=" + URLEncoder.encode(strDescriptor, "UTF-8") + "&category-id=" + categoryId;
+            out.write(body + "\n");
+            out.flush();
+    
+            Reader in = new InputStreamReader(conn.getInputStream());
+            WebPreparedStatementResponse response = gson.fromJson(in, WebPreparedStatementResponse.class);
+            assertEquals(1, response.getNumFreeVariables());
+            assertEquals(0, response.getStatementId());
+            assertEquals("application/json; charset=UTF-8", conn.getContentType());
+            
+            
+            
+            // now execute the query we've just prepared
+            WebPreparedStatement<TestClass> stmt = new WebPreparedStatement<>(1, 0);
+            stmt.setString(0, "fluff");
+            
+            url = new URL(endpoint + "/query-execute");
+            HttpURLConnection conn2 = (HttpURLConnection) url.openConnection();
+            conn2.setRequestMethod("POST");
+            sendAuthentication(conn2, "ignored1", "ignored2");
+            conn2.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
+            conn2.setDoInput(true);
+            conn2.setDoOutput(true);
+            
+            out = new OutputStreamWriter(conn2.getOutputStream());
+            body = "prepared-stmt=" + gson.toJson(stmt, WebPreparedStatement.class);
+            out.write(body + "\n");
+            out.flush();
+    
+            in = new InputStreamReader(conn2.getInputStream());
+            Type typeToken = new TypeToken<WebQueryResponse<TestClass>>(){}.getType();
+            WebQueryResponse<TestClass> result = gson.fromJson(in, typeToken);
+            TestClass[] results = result.getResultList();
+            assertEquals(2, results.length);
+            assertEquals("fluff1", results[0].getKey1());
+            assertEquals(42, results[0].getKey2());
+            assertEquals("fluff2", results[1].getKey1());
+            assertEquals(43, results[1].getKey2());
+    
+            assertEquals("application/json; charset=UTF-8", conn2.getContentType());
+            verify(mockMongoQuery).execute();
+            verify(mockMongoQuery).getWhereExpression();
+            ArgumentCaptor<Expression> expressionCaptor = ArgumentCaptor.forClass(Expression.class);
+            verify(mockMongoQuery).where(expressionCaptor.capture());
+            verifyNoMoreInteractions(mockMongoQuery);
+            
+            Expression capturedExpression = expressionCaptor.getValue();
+            assertTrue(capturedExpression instanceof BinarySetMembershipExpression);
+            Set<String> agentIds = new HashSet<>();
+            agentIds.add(fakeAgentId);
+            Expression expectedExpression = new ExpressionFactory().in(Key.AGENT_ID, agentIds, String.class);
+            assertEquals(expectedExpression, capturedExpression);
+        } finally {
+            category = oldCategory; 
+        }
+    }
+    
+    private void setupTrustedStatementRegistry(String strDescriptor, DescriptorMetadata metadata) {
         Set<String> descs = new HashSet<>();
         descs.add(strDescriptor);
-        StatementDescriptorRegistration reg = mock(StatementDescriptorRegistration.class);
-        when(reg.getStatementDescriptors()).thenReturn(descs);
+        StatementDescriptorRegistration reg = new TestStatementDescriptorRegistration(descs, metadata);
         List<StatementDescriptorRegistration> regs = new ArrayList<>(1);
         regs.add(reg);
         KnownDescriptorRegistry registry = new KnownDescriptorRegistry(regs);
@@ -461,6 +610,7 @@
     private void doUnauthorizedTest(String pathForEndPoint, String failMessage) throws Exception {
         String[] insufficientRoleNames = new String[] {
                 Roles.REGISTER_CATEGORY,
+                Roles.ACCESS_REALM
         };
         doUnauthorizedTest(pathForEndPoint, failMessage, insufficientRoleNames, true);
     }
@@ -496,7 +646,8 @@
     public void authorizedReplacePutPojo() throws Exception {
         String[] roleNames = new String[] {
                 Roles.REPLACE,
-                Roles.REGISTER_CATEGORY
+                Roles.REGISTER_CATEGORY,
+                Roles.ACCESS_REALM
         };
         String testuser = "testuser";
         String password = "testpassword";
@@ -546,6 +697,7 @@
     public void unauthorizedReplacePutPojo() throws Exception {
         String[] insufficientRoleNames = new String[] {
                 Roles.REGISTER_CATEGORY,
+                Roles.ACCESS_REALM
         };
         String testuser = "testuser";
         String password = "testpassword";
@@ -587,7 +739,8 @@
     public void authorizedInsertPutPojo() throws Exception {
         String[] roleNames = new String[] {
                 Roles.APPEND,
-                Roles.REGISTER_CATEGORY
+                Roles.REGISTER_CATEGORY,
+                Roles.ACCESS_REALM
         };
         String testuser = "testuser";
         String password = "testpassword";
@@ -637,6 +790,7 @@
     public void unauthorizedInsertPutPojo() throws Exception {
         String[] insufficientRoleNames = new String[] {
                 Roles.REGISTER_CATEGORY,
+                Roles.ACCESS_REALM
         };
         String testuser = "testuser";
         String password = "testpassword";
@@ -684,7 +838,8 @@
     public void authorizedRemovePojo() throws Exception {
         String[] roleNames = new String[] {
                 Roles.DELETE,
-                Roles.REGISTER_CATEGORY
+                Roles.REGISTER_CATEGORY,
+                Roles.ACCESS_REALM
         };
         String testuser = "testuser";
         String password = "testpassword";
@@ -745,7 +900,8 @@
     public void authorizedUpdatePojo() throws Exception {
         String[] roleNames = new String[] {
                 Roles.UPDATE,
-                Roles.REGISTER_CATEGORY
+                Roles.REGISTER_CATEGORY,
+                Roles.ACCESS_REALM
         };
         String testuser = "testuser";
         String password = "testpassword";
@@ -811,7 +967,8 @@
     public void authorizedGetCount() throws Exception {
         String[] roleNames = new String[] {
                 Roles.GET_COUNT,
-                Roles.REGISTER_CATEGORY
+                Roles.REGISTER_CATEGORY,
+                Roles.ACCESS_REALM
         };
         String testuser = "testuser";
         String password = "testpassword";
@@ -859,6 +1016,7 @@
     public void authorizedSaveFile() throws Exception {
         String[] roleNames = new String[] {
                 Roles.SAVE_FILE,
+                Roles.ACCESS_REALM
         };
         String testuser = "testuser";
         String password = "testpassword";
@@ -916,6 +1074,7 @@
     public void authorizedLoadFile() throws Exception {
         String[] roleNames = new String[] {
                 Roles.LOAD_FILE,
+                Roles.ACCESS_REALM
         };
         String testuser = "testuser";
         String password = "testpassword";
@@ -968,6 +1127,7 @@
     public void authorizedPurge() throws Exception {
         String[] roleNames = new String[] {
                 Roles.PURGE,
+                Roles.ACCESS_REALM
         };
         String testuser = "testuser";
         String password = "testpassword";
@@ -1036,6 +1196,7 @@
         String actionName = "testing";
         String[] roleNames = new String[] {
                 Roles.CMD_CHANNEL_GENERATE,
+                Roles.ACCESS_REALM,
                 // grant the "testing" action
                 WebStorageEndPoint.CMDC_AUTHORIZATION_GRANT_ROLE_PREFIX + actionName
         };
@@ -1055,7 +1216,9 @@
     @Test
     public void unauthorizedGenerateToken() throws Exception {
         String failMsg = "thermostat-cmdc-generate role missing, expected Forbidden!";
-        String[] insufficientRoles = new String[0];
+        String[] insufficientRoles = new String[] {
+                Roles.ACCESS_REALM
+        };
         doUnauthorizedTest("generate-token", failMsg, insufficientRoles, false);
     }
 
@@ -1065,6 +1228,7 @@
         String[] roleNames = new String[] {
                 Roles.CMD_CHANNEL_GENERATE,
                 Roles.CMD_CHANNEL_VERIFY,
+                Roles.ACCESS_REALM,
                 // grant "someAction" to be performed
                 WebStorageEndPoint.CMDC_AUTHORIZATION_GRANT_ROLE_PREFIX + actionName,
         };
@@ -1104,6 +1268,7 @@
         String[] roleNames = new String[] {
                 Roles.CMD_CHANNEL_GENERATE,
                 Roles.CMD_CHANNEL_VERIFY,
+                Roles.ACCESS_REALM,
                 // missing the thermostat-cmdc-grant-someAction role
         };
         final LoginService loginService = new TestLoginService(testuser, password, roleNames); 
@@ -1125,7 +1290,8 @@
         String[] roleNames = new String[] {
                 Roles.CMD_CHANNEL_GENERATE,
                 WebStorageEndPoint.CMDC_AUTHORIZATION_GRANT_ROLE_PREFIX + actionName,
-                Roles.CMD_CHANNEL_VERIFY
+                Roles.CMD_CHANNEL_VERIFY,
+                Roles.ACCESS_REALM
         };
         String testuser = "testuser";
         String password = "testpassword";
@@ -1163,6 +1329,7 @@
         String[] roleNames = new String[] {
                 Roles.CMD_CHANNEL_GENERATE,
                 Roles.CMD_CHANNEL_VERIFY,
+                Roles.ACCESS_REALM,
                 // Grant "someAction", this test tests the time-out
                 WebStorageEndPoint.CMDC_AUTHORIZATION_GRANT_ROLE_PREFIX + actionName
         };
@@ -1199,7 +1366,8 @@
     @Test
     public void authenticatedVerifyNonExistentToken() throws Exception {
         String[] roleNames = new String[] {
-                Roles.CMD_CHANNEL_VERIFY
+                Roles.CMD_CHANNEL_VERIFY,
+                Roles.ACCESS_REALM
         };
         String testuser = "testuser";
         String password = "testpassword";
@@ -1232,7 +1400,9 @@
     @Test
     public void unauthorizedVerifyToken() throws Exception {
         String failMsg = "thermostat-cmdc-verify role missing, expected Forbidden!";
-        String[] insufficientRoles = new String[0];
+        String[] insufficientRoles = new String[] {
+                Roles.ACCESS_REALM
+        };
         doUnauthorizedTest("verify-token", failMsg, insufficientRoles, false);
     }
     
@@ -1332,5 +1502,69 @@
                     roleNames);
         }
     }
+    
+    private static class TestJAASLoginService extends JAASLoginService {
+        
+        private final UserPrincipal userPrincipal;
+        
+        private TestJAASLoginService(UserPrincipal userPrincipal) {
+            this.userPrincipal = userPrincipal;
+        }
+        
+        @Override
+        public UserIdentity login(String username, Object credentials) {
+            return new TestUserIdentity(userPrincipal);
+        }
+        
+        private static class TestUserIdentity implements UserIdentity {
+
+            private final UserPrincipal userPrincipal;
+            
+            private TestUserIdentity(UserPrincipal principal) {
+                this.userPrincipal = principal;
+            }
+            
+            @Override
+            public Subject getSubject() {
+                throw new IllegalStateException("Not implemented");
+            }
+
+            @Override
+            public Principal getUserPrincipal() {
+                return userPrincipal;
+            }
+
+            @Override
+            public boolean isUserInRole(String role, Scope scope) {
+                RolePrincipal rolePrincipal = new RolePrincipal(role);
+                return userPrincipal.getRoles().contains(rolePrincipal);
+            }
+            
+        }
+    }
+    
+    private static class TestStatementDescriptorRegistration implements StatementDescriptorRegistration {
+
+        private final Set<String> descriptorSet;
+        private final DescriptorMetadata metadata;
+        private TestStatementDescriptorRegistration(Set<String> descriptorSet, DescriptorMetadata metadata) {
+            assertEquals(1, descriptorSet.size());
+            this.descriptorSet = descriptorSet;
+            this.metadata = metadata;
+        }
+        
+        @Override
+        public DescriptorMetadata getDescriptorMetadata(String descriptor,
+                PreparedParameter[] params) {
+            return metadata;
+        }
+
+        @Override
+        public Set<String> getStatementDescriptors() {
+            return descriptorSet;
+        }
+        
+    }
+    
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/server/src/test/java/com/redhat/thermostat/web/server/auth/AgentIdFilterTest.java	Thu Jul 25 17:45:33 2013 +0200
@@ -0,0 +1,239 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.web.server.auth;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.Matchers.eq;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.storage.core.Category;
+import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.StatementDescriptor;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
+import com.redhat.thermostat.storage.query.BinaryLogicalExpression;
+import com.redhat.thermostat.storage.query.BinarySetMembershipExpression;
+import com.redhat.thermostat.storage.query.Expression;
+import com.redhat.thermostat.storage.query.ExpressionFactory;
+import com.redhat.thermostat.web.server.auth.FilterResult.ResultType;
+
+public class AgentIdFilterTest {
+    
+    @Test
+    public void testReadAll() {
+        Set<BasicRole> roles = new HashSet<>();
+        RolePrincipal agentReadAll = new RolePrincipal(Roles.GRANT_AGENTS_READ_ALL);
+        roles.add(agentReadAll);
+        
+        AgentIdFilter<?> filter = new AgentIdFilter<>(roles);
+        FilterResult result = filter.applyFilter(null, null, null);
+        assertEquals(ResultType.ALL, result.getType());
+        assertEquals(null, result.getFilterExpression());
+    }
+    
+    @Test
+    public void testReadAllAddToParent() {
+        Set<BasicRole> roles = new HashSet<>();
+        RolePrincipal agentReadAll = new RolePrincipal(Roles.GRANT_AGENTS_READ_ALL);
+        roles.add(agentReadAll);
+        
+        ExpressionFactory factory = new ExpressionFactory();
+        Expression parentExpression = factory.equalTo(Key.AGENT_ID, "testKey");
+        AgentIdFilter<?> filter = new AgentIdFilter<>(roles);
+        FilterResult result = filter.applyFilter(null, null, parentExpression);
+        assertEquals(ResultType.QUERY_EXPRESSION, result.getType());
+        assertEquals(parentExpression, result.getFilterExpression());
+    }
+    
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    @Test
+    public void addsAgentIdInQuery() {
+        String agentId = UUID.randomUUID().toString();
+        Set<BasicRole> roles = new HashSet<>();
+        RolePrincipal agent1Role = new RolePrincipal(AgentIdFilter.AGENTS_BY_AGENT_ID_GRANT_ROLE_PREFIX + agentId);
+        roles.add(agent1Role);
+        DescriptorMetadata metadata = new DescriptorMetadata();
+        StatementDescriptor desc = mock(StatementDescriptor.class);
+        Category category = mock(Category.class);
+        Key<?> mockKey = mock(Key.class);
+        // returning non-null will work
+        when(category.getKey(eq(Key.AGENT_ID.getName()))).thenReturn(mockKey);
+        when(desc.getCategory()).thenReturn(category);
+        AgentIdFilter<?> filter = new AgentIdFilter<>(roles);
+        FilterResult result = filter.applyFilter(desc, metadata, null);
+        assertEquals(ResultType.QUERY_EXPRESSION, result.getType());
+        assertNotNull(result.getFilterExpression());
+        Expression actual = result.getFilterExpression();
+        assertTrue(actual instanceof BinarySetMembershipExpression);
+        Set<String> agentIdSet = new HashSet<>();
+        agentIdSet.add(agentId);
+        Expression expected = new ExpressionFactory().in(Key.AGENT_ID, agentIdSet, String.class);
+        assertEquals(expected, actual);
+    }
+    
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Test
+    public void addsAgentIdInQueryToParentExpression() {
+        String agentId = UUID.randomUUID().toString();
+        Set<BasicRole> roles = new HashSet<>();
+        RolePrincipal agent1Role = new RolePrincipal(AgentIdFilter.AGENTS_BY_AGENT_ID_GRANT_ROLE_PREFIX + agentId);
+        roles.add(agent1Role);
+        DescriptorMetadata metadata = new DescriptorMetadata();
+        StatementDescriptor desc = mock(StatementDescriptor.class);
+        Category category = mock(Category.class);
+        Key<?> mockKey = mock(Key.class);
+        // returning non-null will work
+        when(category.getKey(eq(Key.AGENT_ID.getName()))).thenReturn(mockKey);
+        when(desc.getCategory()).thenReturn(category);
+        AgentIdFilter<?> filter = new AgentIdFilter<>(roles);
+        ExpressionFactory factory = new ExpressionFactory();
+        Expression parentExpression = factory.equalTo(Key.AGENT_ID, "testKey");
+        FilterResult result = filter.applyFilter(desc, metadata, parentExpression);
+        assertEquals(ResultType.QUERY_EXPRESSION, result.getType());
+        assertNotNull(result.getFilterExpression());
+        Expression actual = result.getFilterExpression();
+        assertTrue(actual instanceof BinaryLogicalExpression);
+        Set<String> agentIdSet = new HashSet<>();
+        agentIdSet.add(agentId);
+        Expression expectedIn = factory.in(Key.AGENT_ID, agentIdSet, String.class);
+        Expression expected = factory.and(parentExpression, expectedIn);
+        assertEquals(expected, actual);
+    }
+    
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Test
+    public void addsAgentIdInQuery2() {
+        String agentId = UUID.randomUUID().toString();
+        String agentId2 = UUID.randomUUID().toString();
+        Set<BasicRole> roles = new HashSet<>();
+        RolePrincipal agent1Role = new RolePrincipal(AgentIdFilter.AGENTS_BY_AGENT_ID_GRANT_ROLE_PREFIX + agentId);
+        RolePrincipal agent2Role = new RolePrincipal(AgentIdFilter.AGENTS_BY_AGENT_ID_GRANT_ROLE_PREFIX + agentId2);
+        roles.add(agent1Role);
+        roles.add(agent2Role);
+        DescriptorMetadata metadata = new DescriptorMetadata();
+        StatementDescriptor desc = mock(StatementDescriptor.class);
+        Category category = mock(Category.class);
+        Key<?> mockKey = mock(Key.class);
+        // returning non-null will work
+        when(category.getKey(eq(Key.AGENT_ID.getName()))).thenReturn(mockKey);
+        when(desc.getCategory()).thenReturn(category);
+        AgentIdFilter<?> filter = new AgentIdFilter<>(roles);
+        FilterResult result = filter.applyFilter(desc, metadata, null);
+        assertEquals(ResultType.QUERY_EXPRESSION, result.getType());
+        assertNotNull(result.getFilterExpression());
+        Expression actual = result.getFilterExpression();
+        assertTrue(actual instanceof BinarySetMembershipExpression);
+        Set<String> agentIdSet = new HashSet<>();
+        agentIdSet.add(agentId);
+        agentIdSet.add(agentId2);
+        Expression expected = new ExpressionFactory().in(Key.AGENT_ID, agentIdSet, String.class);
+        assertEquals(expected, actual);
+    }
+    
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Test
+    public void returnsEmptyIfAgentIdDoesNotMatch() {
+        String agentId = UUID.randomUUID().toString();
+        Set<BasicRole> roles = new HashSet<>();
+        RolePrincipal agentReadAll = new RolePrincipal(AgentIdFilter.AGENTS_BY_AGENT_ID_GRANT_ROLE_PREFIX + agentId);
+        roles.add(agentReadAll);
+        String wrongAgentId = "something else than agentId";
+        // assert precondition
+        assertFalse(agentId.equals(wrongAgentId));
+        
+        DescriptorMetadata metadata = new DescriptorMetadata(wrongAgentId);
+        StatementDescriptor desc = mock(StatementDescriptor.class);
+        Category category = mock(Category.class);
+        Key<?> mockKey = mock(Key.class);
+        // returning non-null will work
+        when(category.getKey(eq(Key.AGENT_ID.getName()))).thenReturn(mockKey);
+        when(desc.getCategory()).thenReturn(category);
+        assertTrue(metadata.hasAgentId());
+        AgentIdFilter<?> filter = new AgentIdFilter<>(roles);
+        FilterResult result = filter.applyFilter(desc, metadata, null);
+        assertEquals(ResultType.EMPTY, result.getType());
+        assertNull(result.getFilterExpression());
+    }
+    
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Test
+    public void returnsAllForUnrelatedQuery() {
+        Set<BasicRole> roles = new HashSet<>();
+        
+        DescriptorMetadata metadata = new DescriptorMetadata();
+        StatementDescriptor desc = mock(StatementDescriptor.class);
+        Category category = mock(Category.class);
+        // want for the agent id key to not be present in category
+        when(category.getKey(eq(Key.AGENT_ID.getName()))).thenReturn(null);
+        when(desc.getCategory()).thenReturn(category);
+        assertFalse(metadata.hasAgentId());
+        AgentIdFilter<?> filter = new AgentIdFilter<>(roles);
+        FilterResult result = filter.applyFilter(desc, metadata, null);
+        assertEquals(ResultType.ALL, result.getType());
+        assertNull(result.getFilterExpression());
+    }
+    
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @Test
+    public void returnsParentExpressionForUnrelatedQuery() {
+        Set<BasicRole> roles = new HashSet<>();
+        
+        Expression parentExpression = new ExpressionFactory().equalTo(Key.AGENT_ID, "testKey");
+        DescriptorMetadata metadata = new DescriptorMetadata();
+        StatementDescriptor desc = mock(StatementDescriptor.class);
+        Category category = mock(Category.class);
+        // want for the agent id key to not be present in category
+        when(category.getKey(eq(Key.AGENT_ID.getName()))).thenReturn(null);
+        when(desc.getCategory()).thenReturn(category);
+        assertFalse(metadata.hasAgentId());
+        AgentIdFilter<?> filter = new AgentIdFilter<>(roles);
+        FilterResult result = filter.applyFilter(desc, metadata, parentExpression);
+        assertEquals(ResultType.QUERY_EXPRESSION, result.getType());
+        assertNotNull(result.getFilterExpression());
+        assertEquals(parentExpression, result.getFilterExpression());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/server/src/test/java/com/redhat/thermostat/web/server/auth/HostnameFilterTest.java	Thu Jul 25 17:45:33 2013 +0200
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.web.server.auth;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.StatementDescriptor;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
+import com.redhat.thermostat.storage.dao.AgentInfoDAO;
+import com.redhat.thermostat.storage.dao.HostInfoDAO;
+import com.redhat.thermostat.storage.model.AgentInformation;
+import com.redhat.thermostat.storage.model.HostInfo;
+import com.redhat.thermostat.storage.query.BinaryLogicalExpression;
+import com.redhat.thermostat.storage.query.BinarySetMembershipExpression;
+import com.redhat.thermostat.storage.query.Expression;
+import com.redhat.thermostat.storage.query.ExpressionFactory;
+import com.redhat.thermostat.web.server.auth.FilterResult.ResultType;
+
+public class HostnameFilterTest {
+
+    @Test
+    public void testReadAll() {
+        Set<BasicRole> roles = new HashSet<>();
+        RolePrincipal hostnameReadAll = new RolePrincipal(Roles.GRANT_HOSTS_READ_ALL);
+        roles.add(hostnameReadAll);
+        
+        HostnameFilter<?> filter = new HostnameFilter<>(roles);
+        FilterResult result = filter.applyFilter(null, null, null);
+        assertEquals(ResultType.ALL, result.getType());
+        assertEquals(null, result.getFilterExpression());
+    }
+    
+    @Test
+    public void testReadAllAddsToParentExpression() {
+        Set<BasicRole> roles = new HashSet<>();
+        RolePrincipal hostnameReadAll = new RolePrincipal(Roles.GRANT_HOSTS_READ_ALL);
+        roles.add(hostnameReadAll);
+        
+        ExpressionFactory factory = new ExpressionFactory();
+        Expression parentExpression = factory.equalTo(Key.AGENT_ID, "testKey");
+        HostnameFilter<?> filter = new HostnameFilter<>(roles);
+        FilterResult result = filter.applyFilter(null, null, parentExpression);
+        assertEquals(ResultType.QUERY_EXPRESSION, result.getType());
+        assertEquals(parentExpression, result.getFilterExpression());
+    }
+    
+    @Test
+    public void addsHostnameInQueryForHostInfo() {
+        String testHostname = "testhost.example.com";
+        Set<BasicRole> roles = new HashSet<>();
+        RolePrincipal hostnameRole = new RolePrincipal(HostnameFilter.HOSTS_BY_HOSTNAME_GRANT_ROLE_PREFIX + testHostname);
+        roles.add(hostnameRole);
+        
+        DescriptorMetadata metadata = new DescriptorMetadata();
+        @SuppressWarnings("unchecked")
+        StatementDescriptor<HostInfo> desc = mock(StatementDescriptor.class);
+        when(desc.getCategory()).thenReturn(HostInfoDAO.hostInfoCategory);
+        
+        Set<String> hostnames = new HashSet<>();
+        hostnames.add(testHostname);
+        Expression expected = new ExpressionFactory().in(HostInfoDAO.hostNameKey, hostnames, String.class);
+        HostnameFilter<HostInfo> filter = new HostnameFilter<>(roles);
+        FilterResult result = filter.applyFilter(desc, metadata, null);
+        assertEquals(ResultType.QUERY_EXPRESSION, result.getType());
+        assertNotNull(result.getFilterExpression());
+        Expression actual = result.getFilterExpression();
+        assertTrue(actual instanceof BinarySetMembershipExpression);
+        assertEquals(expected, actual);
+    }
+    
+    @Test
+    public void addsHostnameInQueryForHostInfoAndAddsParentExpression() {
+        String testHostname = "testhost.example.com";
+        Set<BasicRole> roles = new HashSet<>();
+        RolePrincipal hostnameRole = new RolePrincipal(HostnameFilter.HOSTS_BY_HOSTNAME_GRANT_ROLE_PREFIX + testHostname);
+        roles.add(hostnameRole);
+        
+        DescriptorMetadata metadata = new DescriptorMetadata();
+        @SuppressWarnings("unchecked")
+        StatementDescriptor<HostInfo> desc = mock(StatementDescriptor.class);
+        when(desc.getCategory()).thenReturn(HostInfoDAO.hostInfoCategory);
+        
+        Set<String> hostnames = new HashSet<>();
+        hostnames.add(testHostname);
+        ExpressionFactory factory = new ExpressionFactory();
+        Expression parentExpression = factory.equalTo(Key.AGENT_ID, "testKey");
+        Expression expectedIn = factory.in(HostInfoDAO.hostNameKey, hostnames, String.class);
+        Expression expected = factory.and(parentExpression, expectedIn);
+        HostnameFilter<HostInfo> filter = new HostnameFilter<>(roles);
+        FilterResult result = filter.applyFilter(desc, metadata, parentExpression);
+        assertEquals(ResultType.QUERY_EXPRESSION, result.getType());
+        assertNotNull(result.getFilterExpression());
+        Expression actual = result.getFilterExpression();
+        assertTrue(actual instanceof BinaryLogicalExpression);
+        assertEquals(expected, actual);
+    }
+    
+    @Test
+    public void byPassesFilterForUnrelatedQuery() {
+        Set<BasicRole> roles = new HashSet<>();
+        
+        DescriptorMetadata metadata = new DescriptorMetadata();
+
+        @SuppressWarnings("unchecked")
+        StatementDescriptor<AgentInformation> desc = mock(StatementDescriptor.class);
+        when(desc.getCategory()).thenReturn(AgentInfoDAO.CATEGORY);
+        
+        HostnameFilter<AgentInformation> filter = new HostnameFilter<>(roles);
+        FilterResult result = filter.applyFilter(desc, metadata, null);
+        assertEquals(ResultType.ALL, result.getType());
+        assertNull(result.getFilterExpression());
+    }
+    
+    @Test
+    public void byPassesFilterForUnrelatedQueryAndParentExpression() {
+        Set<BasicRole> roles = new HashSet<>();
+        
+        DescriptorMetadata metadata = new DescriptorMetadata();
+        @SuppressWarnings("unchecked")
+        StatementDescriptor<AgentInformation> desc = mock(StatementDescriptor.class);
+        when(desc.getCategory()).thenReturn(AgentInfoDAO.CATEGORY);
+        
+        ExpressionFactory factory = new ExpressionFactory();
+        Expression parentExpression = factory.equalTo(Key.AGENT_ID, "testKey");
+        HostnameFilter<AgentInformation> filter = new HostnameFilter<>(roles);
+        FilterResult result = filter.applyFilter(desc, metadata, parentExpression);
+        assertEquals(ResultType.QUERY_EXPRESSION, result.getType());
+        assertNotNull(result.getFilterExpression());
+        assertEquals(parentExpression, result.getFilterExpression());
+    }
+}
--- a/web/server/src/test/java/com/redhat/thermostat/web/server/auth/UserPrincipalTest.java	Wed Aug 07 12:23:47 2013 -0400
+++ b/web/server/src/test/java/com/redhat/thermostat/web/server/auth/UserPrincipalTest.java	Thu Jul 25 17:45:33 2013 +0200
@@ -38,9 +38,13 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import java.security.Principal;
 import java.util.HashSet;
@@ -48,6 +52,18 @@
 
 import org.junit.Test;
 
+import com.redhat.thermostat.storage.core.Category;
+import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.StatementDescriptor;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
+import com.redhat.thermostat.storage.dao.HostInfoDAO;
+import com.redhat.thermostat.storage.dao.VmInfoDAO;
+import com.redhat.thermostat.storage.model.HostInfo;
+import com.redhat.thermostat.storage.model.VmInfo;
+import com.redhat.thermostat.storage.query.Expression;
+import com.redhat.thermostat.storage.query.ExpressionFactory;
+import com.redhat.thermostat.web.server.auth.FilterResult.ResultType;
+
 public class UserPrincipalTest {
 
     @Test(expected = NullPointerException.class)
@@ -98,6 +114,214 @@
         assertTrue(p.equals(principal));
     }
     
+    @Test
+    public void readAllRoleBypassesReadFilters() {
+        Set<BasicRole> roles = new HashSet<>();
+        RolePrincipal readEverything = new RolePrincipal(Roles.GRANT_READ_ALL);
+        roles.add(readEverything);
+        SimplePrincipal testMe = new SimplePrincipal("test me");
+        testMe.setRoles(roles);
+        FilterResult result = testMe.getReadFilter(null, null);
+        assertEquals(ResultType.ALL, result.getType());
+        assertNull(result.getFilterExpression());
+    }
+    
+    @Test
+    public void testEntireFilterChainNoSpecificAgentIdVmId() {
+        String agentId = "someAgentID";
+        String vmId = "someVmID";
+        String vmUsername = "someUser";
+        RolePrincipal readAllHosts = new RolePrincipal(Roles.GRANT_HOSTS_READ_ALL);
+        RolePrincipal readAgentId = new RolePrincipal(AgentIdFilter.AGENTS_BY_AGENT_ID_GRANT_ROLE_PREFIX + agentId);
+        RolePrincipal readVmId = new RolePrincipal(VmIdFilter.VMS_BY_VM_ID_GRANT_ROLE_PREFIX + vmId);
+        RolePrincipal readVmUsername = new RolePrincipal(VmUsernameFilter.VMS_BY_USERNAME_GRANT_ROLE_PREFIX + vmUsername);
+        
+        Set<BasicRole> roles = new HashSet<>();
+        roles.add(readAllHosts);
+        roles.add(readAgentId);
+        roles.add(readVmUsername);
+        roles.add(readVmId);
+        
+        assertFalse(roles.contains(AgentIdFilter.GRANT_AGENTS_READ_ALL));
+        assertTrue(roles.contains(HostnameFilter.GRANT_HOSTS_READ_ALL));
+        assertFalse(roles.contains(VmIdFilter.GRANT_VMS_BY_ID_READ_ALL));
+        assertFalse(roles.contains(VmUsernameFilter.GRANT_VMS_USERNAME_READ_ALL));
+        SimplePrincipal testMe = new SimplePrincipal("test me");
+        testMe.setRoles(roles);
+        @SuppressWarnings("unchecked")
+        StatementDescriptor<VmInfo> desc = mock(StatementDescriptor.class);
+        when(desc.getCategory()).thenReturn(VmInfoDAO.vmInfoCategory);
+        
+        // fake a query for a category with agentId attributes and vmId
+        // attributes present, but no specific agentId/vmId present.
+        DescriptorMetadata metadata = new DescriptorMetadata();
+        assertFalse(metadata.hasAgentId());
+        assertFalse(metadata.hasVmId());
+        
+        // should pass through agentId -> hostname -> vmId -> vmUsername filters
+        FilterResult result = testMe.getReadFilter(desc, metadata);
+        
+        assertEquals(ResultType.QUERY_EXPRESSION, result.getType());
+        assertNotNull(result.getFilterExpression());
+        Expression actual = result.getFilterExpression();
+        ExpressionFactory factory = new ExpressionFactory();
+        Set<String> agentIds = new HashSet<>();
+        agentIds.add(agentId);
+        Set<String> vmIds = new HashSet<>();
+        vmIds.add(vmId);
+        Expression agentInExpr = factory.in(Key.AGENT_ID, agentIds, String.class);
+        Expression vmIdInExpr = factory.in(Key.VM_ID, vmIds, String.class);
+        Set<String> vmUsernames = new HashSet<>();
+        vmUsernames.add(vmUsername);
+        Expression vmIdUsernameInExpr = factory.in(VmInfoDAO.usernameKey, vmUsernames, String.class);
+        Expression expected = factory.and(factory.and(agentInExpr, vmIdInExpr), vmIdUsernameInExpr);
+        assertEquals(expected, actual);
+    }
+    
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Test
+    public void testEntireFilterChainSpecificAgentIdVmId() {
+        String agentId = "someAgentID";
+        String vmId = "someVmID";
+        RolePrincipal readAgentId = new RolePrincipal(AgentIdFilter.AGENTS_BY_AGENT_ID_GRANT_ROLE_PREFIX + agentId);
+        RolePrincipal readVmId = new RolePrincipal(VmIdFilter.VMS_BY_VM_ID_GRANT_ROLE_PREFIX + vmId);
+        
+        Set<BasicRole> roles = new HashSet<>();
+        roles.add(HostnameFilter.GRANT_HOSTS_READ_ALL);
+        roles.add(readAgentId);
+        roles.add(VmUsernameFilter.GRANT_VMS_USERNAME_READ_ALL);
+        roles.add(readVmId);
+        
+        assertFalse(roles.contains(AgentIdFilter.GRANT_AGENTS_READ_ALL));
+        assertTrue(roles.contains(HostnameFilter.GRANT_HOSTS_READ_ALL));
+        assertFalse(roles.contains(VmIdFilter.GRANT_VMS_BY_ID_READ_ALL));
+        assertTrue(roles.contains(VmUsernameFilter.GRANT_VMS_USERNAME_READ_ALL));
+        SimplePrincipal testMe = new SimplePrincipal("test me");
+        testMe.setRoles(roles);
+        StatementDescriptor desc = mock(StatementDescriptor.class);
+        Category mockCategory = mock(Category.class);
+        Key<?> agentKey = mock(Key.class);
+        // want for the agent id key to be present in category
+        when(mockCategory.getKey(eq(Key.AGENT_ID.getName()))).thenReturn(agentKey);
+        Key<?> vmKey = mock(Key.class);
+        // want for the vm id key to be present in category
+        when(mockCategory.getKey(eq(Key.VM_ID.getName()))).thenReturn(vmKey);
+        when(desc.getCategory()).thenReturn(mockCategory);
+        
+        // fake a query for a category with agentId attributes and vmId
+        // attributes present and also specific agentId/vmId present.
+        DescriptorMetadata metadata = new DescriptorMetadata(agentId, vmId);
+        assertTrue(metadata.hasAgentId());
+        assertTrue(metadata.hasVmId());
+        
+        // should pass through agentId -> hostname -> vmId -> vmUsername filters
+        FilterResult result = testMe.getReadFilter(desc, metadata);
+        
+        // should return all, since ACL allows specific agentId/vmIds
+        assertEquals(ResultType.ALL, result.getType());
+        assertNull(result.getFilterExpression());
+    }
+    
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Test
+    public void testEntireFilterChainSpecificAgentIdVmIdPlusHostname() {
+        String agentId = "someAgentID";
+        String vmId = "someVmID";
+        String hostname = "somehost.example.com";
+        RolePrincipal readAgentId = new RolePrincipal(AgentIdFilter.AGENTS_BY_AGENT_ID_GRANT_ROLE_PREFIX + agentId);
+        RolePrincipal readVmId = new RolePrincipal(VmIdFilter.VMS_BY_VM_ID_GRANT_ROLE_PREFIX + vmId);
+        RolePrincipal readHostname = new RolePrincipal(HostnameFilter.HOSTS_BY_HOSTNAME_GRANT_ROLE_PREFIX + hostname);
+        
+        Set<BasicRole> roles = new HashSet<>();
+        roles.add(readHostname);
+        roles.add(readAgentId);
+        roles.add(VmUsernameFilter.GRANT_VMS_USERNAME_READ_ALL);
+        roles.add(readVmId);
+        
+        assertFalse(roles.contains(AgentIdFilter.GRANT_AGENTS_READ_ALL));
+        assertFalse(roles.contains(HostnameFilter.GRANT_HOSTS_READ_ALL));
+        assertFalse(roles.contains(VmIdFilter.GRANT_VMS_BY_ID_READ_ALL));
+        assertTrue(roles.contains(VmUsernameFilter.GRANT_VMS_USERNAME_READ_ALL));
+        SimplePrincipal testMe = new SimplePrincipal("test me");
+        testMe.setRoles(roles);
+        StatementDescriptor<HostInfo> desc = mock(StatementDescriptor.class);
+        Category mockCategory = mock(Category.class);
+        when(desc.getCategory()).thenReturn(HostInfoDAO.hostInfoCategory);
+        Key<?> agentKey = mock(Key.class);
+        // want for the agent id key to be present in category
+        when(mockCategory.getKey(eq(Key.AGENT_ID.getName()))).thenReturn(agentKey);
+        Key<?> vmKey = mock(Key.class);
+        // want for the vm id key to be present in category
+        when(mockCategory.getKey(eq(Key.VM_ID.getName()))).thenReturn(vmKey);
+        
+        // fake a query for a category with agentId attributes and vmId
+        // attributes present and also specific agentId/vmId present.
+        DescriptorMetadata metadata = new DescriptorMetadata(agentId, vmId);
+        assertTrue(metadata.hasAgentId());
+        assertTrue(metadata.hasVmId());
+        
+        // should pass through agentId -> hostname -> vmId -> vmUsername filters
+        FilterResult result = testMe.getReadFilter(desc, metadata);
+        
+        // should return query expression in order to allow only specific
+        // hostname
+        assertEquals(ResultType.QUERY_EXPRESSION, result.getType());
+        assertNotNull(result.getFilterExpression());
+        Expression actual = result.getFilterExpression();
+        ExpressionFactory factory = new ExpressionFactory();
+        Set<String> hostnames = new HashSet<>();
+        hostnames.add(hostname);
+        Expression expected = factory.in(HostInfoDAO.hostNameKey, hostnames, String.class);
+        assertEquals(expected, actual);
+    }
+    
+    @Test
+    public void testEntireFilterChainSpecificAgentIdVmIdPlusVmUsername() {
+        String agentId = "someAgentID";
+        String vmId = "someVmID";
+        String vmUserame = "someOwningUser";
+        RolePrincipal readAgentId = new RolePrincipal(AgentIdFilter.AGENTS_BY_AGENT_ID_GRANT_ROLE_PREFIX + agentId);
+        RolePrincipal readVmId = new RolePrincipal(VmIdFilter.VMS_BY_VM_ID_GRANT_ROLE_PREFIX + vmId);
+        RolePrincipal readVmUsername = new RolePrincipal(VmUsernameFilter.VMS_BY_USERNAME_GRANT_ROLE_PREFIX + vmUserame);
+        
+        Set<BasicRole> roles = new HashSet<>();
+        roles.add(readVmUsername);
+        roles.add(readAgentId);
+        roles.add(HostnameFilter.GRANT_HOSTS_READ_ALL);
+        roles.add(readVmId);
+        
+        assertFalse(roles.contains(AgentIdFilter.GRANT_AGENTS_READ_ALL));
+        assertTrue(roles.contains(HostnameFilter.GRANT_HOSTS_READ_ALL));
+        assertFalse(roles.contains(VmIdFilter.GRANT_VMS_BY_ID_READ_ALL));
+        assertFalse(roles.contains(VmUsernameFilter.GRANT_VMS_USERNAME_READ_ALL));
+        SimplePrincipal testMe = new SimplePrincipal("test me");
+        testMe.setRoles(roles);
+        @SuppressWarnings("unchecked")
+        StatementDescriptor<VmInfo> desc = mock(StatementDescriptor.class);
+        when(desc.getCategory()).thenReturn(VmInfoDAO.vmInfoCategory);
+        
+        // fake a query for a category with agentId attributes and vmId
+        // attributes present and also specific agentId/vmId present.
+        DescriptorMetadata metadata = new DescriptorMetadata(agentId, vmId);
+        assertTrue(metadata.hasAgentId());
+        assertTrue(metadata.hasVmId());
+        
+        // should pass through agentId -> hostname -> vmId -> vmUsername filters
+        FilterResult result = testMe.getReadFilter(desc, metadata);
+        
+        // should return query expression in order to allow only specific
+        // owning vm username.
+        assertEquals(ResultType.QUERY_EXPRESSION, result.getType());
+        assertNotNull(result.getFilterExpression());
+        Expression actual = result.getFilterExpression();
+        ExpressionFactory factory = new ExpressionFactory();
+        Set<String> usernames = new HashSet<>();
+        usernames.add(vmUserame);
+        Expression expected = factory.in(VmInfoDAO.usernameKey, usernames, String.class);
+        assertEquals(expected, actual);
+    }
+    
+    
     @SuppressWarnings("serial")
     private static class SimplePrincipal extends UserPrincipal {
         
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/server/src/test/java/com/redhat/thermostat/web/server/auth/VmIdFilterTest.java	Thu Jul 25 17:45:33 2013 +0200
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.web.server.auth;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.storage.core.Category;
+import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.StatementDescriptor;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
+import com.redhat.thermostat.storage.query.BinaryLogicalExpression;
+import com.redhat.thermostat.storage.query.BinarySetMembershipExpression;
+import com.redhat.thermostat.storage.query.Expression;
+import com.redhat.thermostat.storage.query.ExpressionFactory;
+import com.redhat.thermostat.web.server.auth.FilterResult.ResultType;
+
+public class VmIdFilterTest {
+
+    @Test
+    public void testReadAll() {
+        Set<BasicRole> roles = new HashSet<>();
+        RolePrincipal vmIdReadAll = new RolePrincipal(Roles.GRANT_VMS_READ_BY_VM_ID_ALL);
+        roles.add(vmIdReadAll);
+        
+        VmIdFilter<?> filter = new VmIdFilter<>(roles);
+        FilterResult result = filter.applyFilter(null, null, null);
+        assertEquals(ResultType.ALL, result.getType());
+        assertEquals(null, result.getFilterExpression());
+    }
+    
+    @Test
+    public void testReadAllAndParentExpression() {
+        Set<BasicRole> roles = new HashSet<>();
+        RolePrincipal vmIdReadAll = new RolePrincipal(Roles.GRANT_VMS_READ_BY_VM_ID_ALL);
+        roles.add(vmIdReadAll);
+        
+        ExpressionFactory factory = new ExpressionFactory();
+        Expression parentExpression = factory.equalTo(Key.AGENT_ID, "testKey");
+        VmIdFilter<?> filter = new VmIdFilter<>(roles);
+        FilterResult result = filter.applyFilter(null, null, parentExpression);
+        assertEquals(ResultType.QUERY_EXPRESSION, result.getType());
+        assertEquals(parentExpression, result.getFilterExpression());
+    }
+    
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Test
+    public void addsVmIdInQuery() {
+        String vmId = UUID.randomUUID().toString();
+        Set<BasicRole> roles = new HashSet<>();
+        RolePrincipal vmIdRole = new RolePrincipal(VmIdFilter.VMS_BY_VM_ID_GRANT_ROLE_PREFIX + vmId);
+        roles.add(vmIdRole);
+        DescriptorMetadata metadata = new DescriptorMetadata();
+        StatementDescriptor desc = mock(StatementDescriptor.class);
+        Category category = mock(Category.class);
+        Key<?> vmIdKey = mock(Key.class);
+        // any non-null key for vmId will do
+        when(category.getKey(eq(Key.VM_ID.getName()))).thenReturn(vmIdKey);
+        when(desc.getCategory()).thenReturn(category);
+        VmIdFilter<?> filter = new VmIdFilter<>(roles);
+        FilterResult result = filter.applyFilter(desc, metadata, null);
+        assertEquals(ResultType.QUERY_EXPRESSION, result.getType());
+        assertNotNull(result.getFilterExpression());
+        Expression actual = result.getFilterExpression();
+        assertTrue(actual instanceof BinarySetMembershipExpression);
+        Set<String> vmIdSet = new HashSet<>();
+        vmIdSet.add(vmId);
+        Expression expected = new ExpressionFactory().in(Key.VM_ID, vmIdSet, String.class);
+        assertEquals(expected, actual);
+    }
+    
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Test
+    public void addsVmIdInQueryAndParentExpression() {
+        String vmId = UUID.randomUUID().toString();
+        Set<BasicRole> roles = new HashSet<>();
+        RolePrincipal vmIdRole = new RolePrincipal(VmIdFilter.VMS_BY_VM_ID_GRANT_ROLE_PREFIX + vmId);
+        roles.add(vmIdRole);
+        DescriptorMetadata metadata = new DescriptorMetadata();
+        StatementDescriptor desc = mock(StatementDescriptor.class);
+        Category category = mock(Category.class);
+        Key<?> vmIdKey = mock(Key.class);
+        // any non-null key for vmId will do
+        when(category.getKey(eq(Key.VM_ID.getName()))).thenReturn(vmIdKey);
+        when(desc.getCategory()).thenReturn(category);
+        VmIdFilter<?> filter = new VmIdFilter<>(roles);
+        ExpressionFactory factory = new ExpressionFactory();
+        Expression parentExpression = factory.equalTo(Key.AGENT_ID, "testKey");
+        FilterResult result = filter.applyFilter(desc, metadata, parentExpression);
+        assertEquals(ResultType.QUERY_EXPRESSION, result.getType());
+        assertNotNull(result.getFilterExpression());
+        Expression actual = result.getFilterExpression();
+        assertTrue(actual instanceof BinaryLogicalExpression);
+        Set<String> vmIdSet = new HashSet<>();
+        vmIdSet.add(vmId);
+        
+        Expression expectedIn = factory.in(Key.VM_ID, vmIdSet, String.class);
+        Expression expected = factory.and(parentExpression, expectedIn);
+        assertEquals(expected, actual);
+    }
+    
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @Test
+    public void addsVmIdInQuery2() {
+        String vmId = UUID.randomUUID().toString();
+        String vmId2 = UUID.randomUUID().toString();
+        Set<BasicRole> roles = new HashSet<>();
+        RolePrincipal vm1Role = new RolePrincipal(VmIdFilter.VMS_BY_VM_ID_GRANT_ROLE_PREFIX + vmId);
+        RolePrincipal vm2Role = new RolePrincipal(VmIdFilter.VMS_BY_VM_ID_GRANT_ROLE_PREFIX + vmId2);
+        roles.add(vm1Role);
+        roles.add(vm2Role);
+        DescriptorMetadata metadata = new DescriptorMetadata();
+        StatementDescriptor desc = mock(StatementDescriptor.class);
+        Category category = mock(Category.class);
+        Key<?> vmIdKey = mock(Key.class);
+        // any non-null key for vmId will do
+        when(category.getKey(eq(Key.VM_ID.getName()))).thenReturn(vmIdKey);
+        when(desc.getCategory()).thenReturn(category);
+        VmIdFilter<?> filter = new VmIdFilter<>(roles);
+        FilterResult result = filter.applyFilter(desc, metadata, null);
+        assertEquals(ResultType.QUERY_EXPRESSION, result.getType());
+        assertNotNull(result.getFilterExpression());
+        Expression actual = result.getFilterExpression();
+        assertTrue(actual instanceof BinarySetMembershipExpression);
+        Set<String> vmIdSet = new HashSet<>();
+        vmIdSet.add(vmId);
+        vmIdSet.add(vmId2);
+        Expression expected = new ExpressionFactory().in(Key.VM_ID, vmIdSet, String.class);
+        assertEquals(expected, actual);
+    }
+    
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @Test
+    public void returnsEmptyIfVmIdDoesNotMatch() {
+        String vmId = UUID.randomUUID().toString();
+        Set<BasicRole> roles = new HashSet<>();
+        RolePrincipal vmIdRole = new RolePrincipal(VmIdFilter.VMS_BY_VM_ID_GRANT_ROLE_PREFIX + vmId);
+        roles.add(vmIdRole);
+        String wrongVmId = "something other than vmId";
+        // assert precondition
+        assertFalse(vmId.equals(wrongVmId));
+        
+        DescriptorMetadata metadata = new DescriptorMetadata(null, wrongVmId);
+        StatementDescriptor desc = mock(StatementDescriptor.class);
+        Category category = mock(Category.class);
+        Key<?> vmIdKey = mock(Key.class);
+        // any non-null key for vmId will do
+        when(category.getKey(eq(Key.VM_ID.getName()))).thenReturn(vmIdKey);
+        when(desc.getCategory()).thenReturn(category);
+        assertTrue(metadata.hasVmId());
+        VmIdFilter<?> filter = new VmIdFilter<>(roles);
+        FilterResult result = filter.applyFilter(desc, metadata, null);
+        assertEquals(ResultType.EMPTY, result.getType());
+        assertNull(result.getFilterExpression());
+    }
+    
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @Test
+    public void returnsAllForUnrelatedQuery() {
+        Set<BasicRole> roles = new HashSet<>();
+        
+        DescriptorMetadata metadata = new DescriptorMetadata();
+        StatementDescriptor desc = mock(StatementDescriptor.class);
+        Category category = mock(Category.class);
+        // want to have a null retval of vmId
+        when(category.getKey(eq(Key.VM_ID.getName()))).thenReturn(null);
+        when(desc.getCategory()).thenReturn(category);
+        assertFalse(metadata.hasAgentId());
+        assertFalse(metadata.hasVmId());
+        VmIdFilter<?> filter = new VmIdFilter<>(roles);
+        FilterResult result = filter.applyFilter(desc, metadata, null);
+        assertEquals(ResultType.ALL, result.getType());
+        assertNull(result.getFilterExpression());
+    }
+    
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Test
+    public void returnsParentExpressionForUnrelatedQuery() {
+        Set<BasicRole> roles = new HashSet<>();
+        
+        ExpressionFactory factory = new ExpressionFactory();
+        Expression parentExpression = factory.equalTo(Key.AGENT_ID, "testKey");
+        DescriptorMetadata metadata = new DescriptorMetadata();
+        StatementDescriptor desc = mock(StatementDescriptor.class);
+        Category category = mock(Category.class);
+        // want to have a null retval of vmId
+        when(category.getKey(eq(Key.VM_ID.getName()))).thenReturn(null);
+        when(desc.getCategory()).thenReturn(category);
+        assertFalse(metadata.hasAgentId());
+        assertFalse(metadata.hasVmId());
+        VmIdFilter<?> filter = new VmIdFilter<>(roles);
+        FilterResult result = filter.applyFilter(desc, metadata, parentExpression);
+        assertEquals(ResultType.QUERY_EXPRESSION, result.getType());
+        assertNotNull(result.getFilterExpression());
+        assertEquals(parentExpression, result.getFilterExpression());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/server/src/test/java/com/redhat/thermostat/web/server/auth/VmUsernameFilterTest.java	Thu Jul 25 17:45:33 2013 +0200
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.web.server.auth;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.StatementDescriptor;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
+import com.redhat.thermostat.storage.dao.AgentInfoDAO;
+import com.redhat.thermostat.storage.dao.VmInfoDAO;
+import com.redhat.thermostat.storage.model.AgentInformation;
+import com.redhat.thermostat.storage.model.VmInfo;
+import com.redhat.thermostat.storage.query.BinaryLogicalExpression;
+import com.redhat.thermostat.storage.query.BinarySetMembershipExpression;
+import com.redhat.thermostat.storage.query.Expression;
+import com.redhat.thermostat.storage.query.ExpressionFactory;
+import com.redhat.thermostat.web.server.auth.FilterResult.ResultType;
+
+public class VmUsernameFilterTest {
+
+    @Test
+    public void testReadAll() {
+        Set<BasicRole> roles = new HashSet<>();
+        RolePrincipal vmUsernameReadAll = new RolePrincipal(Roles.GRANT_VMS_READ_BY_USERNAME_ALL);
+        roles.add(vmUsernameReadAll);
+        
+        VmUsernameFilter<?> filter = new VmUsernameFilter<>(roles);
+        FilterResult result = filter.applyFilter(null, null, null);
+        assertEquals(ResultType.ALL, result.getType());
+        assertEquals(null, result.getFilterExpression());
+    }
+    
+    @Test
+    public void testReadAllWithParentExpression() {
+        Set<BasicRole> roles = new HashSet<>();
+        RolePrincipal vmUsernameReadAll = new RolePrincipal(Roles.GRANT_VMS_READ_BY_USERNAME_ALL);
+        roles.add(vmUsernameReadAll);
+        
+        ExpressionFactory factory = new ExpressionFactory();
+        Expression parentExpression = factory.equalTo(Key.AGENT_ID, "testKey");
+        VmUsernameFilter<?> filter = new VmUsernameFilter<>(roles);
+        FilterResult result = filter.applyFilter(null, null, parentExpression);
+        assertEquals(ResultType.QUERY_EXPRESSION, result.getType());
+        assertEquals(parentExpression, result.getFilterExpression());
+    }
+    
+    @Test
+    public void addsVmUsernameInQueryForVmInfo() {
+        String testUsername = "fooBar";
+        Set<BasicRole> roles = new HashSet<>();
+        RolePrincipal vmUsernameRole = new RolePrincipal(VmUsernameFilter.VMS_BY_USERNAME_GRANT_ROLE_PREFIX + testUsername);
+        roles.add(vmUsernameRole);
+        
+        DescriptorMetadata metadata = new DescriptorMetadata();
+        @SuppressWarnings("unchecked")
+        StatementDescriptor<VmInfo> desc = mock(StatementDescriptor.class);
+        when(desc.getCategory()).thenReturn(VmInfoDAO.vmInfoCategory);
+        
+        Set<String> usernames = new HashSet<>();
+        usernames.add(testUsername);
+        Expression expected = new ExpressionFactory().in(VmInfoDAO.usernameKey, usernames, String.class);
+        VmUsernameFilter<VmInfo> filter = new VmUsernameFilter<>(roles);
+        FilterResult result = filter.applyFilter(desc, metadata, null);
+        assertEquals(ResultType.QUERY_EXPRESSION, result.getType());
+        assertNotNull(result.getFilterExpression());
+        Expression actual = result.getFilterExpression();
+        assertTrue(actual instanceof BinarySetMembershipExpression);
+        assertEquals(expected, actual);
+    }
+    
+    @Test
+    public void addsVmUsernameInQueryForVmInfoAndParentExpression() {
+        String testUsername = "fooBar";
+        Set<BasicRole> roles = new HashSet<>();
+        RolePrincipal vmUsernameRole = new RolePrincipal(VmUsernameFilter.VMS_BY_USERNAME_GRANT_ROLE_PREFIX + testUsername);
+        roles.add(vmUsernameRole);
+        
+        DescriptorMetadata metadata = new DescriptorMetadata();
+        @SuppressWarnings("unchecked")
+        StatementDescriptor<VmInfo> desc = mock(StatementDescriptor.class);
+        when(desc.getCategory()).thenReturn(VmInfoDAO.vmInfoCategory);
+        
+        Set<String> usernames = new HashSet<>();
+        usernames.add(testUsername);
+        ExpressionFactory factory = new ExpressionFactory();
+        Expression parentExpression = factory.equalTo(Key.AGENT_ID, "testKey");
+        Expression expectedIn = factory.in(VmInfoDAO.usernameKey, usernames, String.class);
+        Expression expected = factory.and(parentExpression, expectedIn);
+        VmUsernameFilter<VmInfo> filter = new VmUsernameFilter<>(roles);
+        FilterResult result = filter.applyFilter(desc, metadata, parentExpression);
+        assertEquals(ResultType.QUERY_EXPRESSION, result.getType());
+        assertNotNull(result.getFilterExpression());
+        Expression actual = result.getFilterExpression();
+        assertTrue(actual instanceof BinaryLogicalExpression);
+        assertEquals(expected, actual);
+    }
+    
+    @Test
+    public void byPassesFilterForUnrelatedQuery() {
+        Set<BasicRole> roles = new HashSet<>();
+        
+        DescriptorMetadata metadata = new DescriptorMetadata();
+        @SuppressWarnings("unchecked")
+        StatementDescriptor<AgentInformation> desc = mock(StatementDescriptor.class);
+        when(desc.getCategory()).thenReturn(AgentInfoDAO.CATEGORY);
+        
+        VmUsernameFilter<AgentInformation> filter = new VmUsernameFilter<>(roles);
+        FilterResult result = filter.applyFilter(desc, metadata, null);
+        assertEquals(ResultType.ALL, result.getType());
+        assertNull(result.getFilterExpression());
+    }
+    
+    @Test
+    public void byPassesFilterForUnrelatedQueryAndParentExpression() {
+        Set<BasicRole> roles = new HashSet<>();
+        
+        DescriptorMetadata metadata = new DescriptorMetadata();
+        @SuppressWarnings("unchecked")
+        StatementDescriptor<AgentInformation> desc = mock(StatementDescriptor.class);
+        when(desc.getCategory()).thenReturn(AgentInfoDAO.CATEGORY);
+        
+        ExpressionFactory factory = new ExpressionFactory();
+        Expression parentExpression = factory.equalTo(Key.AGENT_ID, "testKey");
+        VmUsernameFilter<AgentInformation> filter = new VmUsernameFilter<>(roles);
+        FilterResult result = filter.applyFilter(desc, metadata, parentExpression);
+        assertEquals(ResultType.QUERY_EXPRESSION, result.getType());
+        assertNotNull(result.getFilterExpression());
+        assertEquals(parentExpression, result.getFilterExpression());
+    }
+    
+}