changeset 1079:dff087c3c875

Fix coverage of authentication/authorization checks. Reviewed-by: neugens Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-April/006410.html PR952
author Severin Gehwolf <sgehwolf@redhat.com>
date Fri, 19 Apr 2013 14:09:10 +0200
parents 7f100b629033
children dd39c8d202fb
files integration-tests/src/test/java/com/redhat/thermostat/itest/WebAppTest.java web/cmd/src/main/java/com/redhat/thermostat/web/cmd/WebServiceLauncher.java web/cmd/src/test/java/com/redhat/thermostat/web/cmd/WebServiceLauncherTest.java web/server/pom.xml web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java web/server/src/main/java/com/redhat/thermostat/web/server/auth/Roles.java web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java web/war/src/main/webapp/WEB-INF/web.xml
diffstat 8 files changed, 529 insertions(+), 128 deletions(-) [+]
line wrap: on
line diff
--- a/integration-tests/src/test/java/com/redhat/thermostat/itest/WebAppTest.java	Fri May 03 12:39:38 2013 -0400
+++ b/integration-tests/src/test/java/com/redhat/thermostat/itest/WebAppTest.java	Fri Apr 19 14:09:10 2013 +0200
@@ -50,11 +50,13 @@
 import java.util.UUID;
 
 import org.eclipse.jetty.security.DefaultUserIdentity;
+import org.eclipse.jetty.security.LoginService;
 import org.eclipse.jetty.security.MappedLoginService;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.UserIdentity;
 import org.eclipse.jetty.util.security.Password;
 import org.eclipse.jetty.webapp.WebAppContext;
+import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -70,6 +72,7 @@
 import com.redhat.thermostat.vm.classstat.common.VmClassStatDAO;
 import com.redhat.thermostat.vm.classstat.common.model.VmClassStat;
 import com.redhat.thermostat.web.client.internal.WebStorage;
+import com.redhat.thermostat.web.server.auth.Roles;
 
 import expectj.Spawn;
 
@@ -89,21 +92,16 @@
         storage.expectClose();
 
         assertNoExceptions(storage.getCurrentStandardOutContents(), storage.getCurrentStandardErrContents());
-
-        port = FreePortFinder.findFreePort(new TryPort() {
-            
-            @Override
-            public void tryPort(int port) throws Exception {
-                startServer(port);
-            }
-        });
-        registerCategory();
+    }
+    
+    @After
+    public void tearDown() throws Exception {
+        server.stop();
+        server.join();
     }
 
     @AfterClass
     public static void tearDownOnce() throws Exception {
-        server.stop();
-        server.join();
 
         Spawn storage = spawnThermostat("storage", "--stop");
         storage.expect("server shutdown complete");
@@ -112,45 +110,47 @@
         assertNoExceptions(storage.getCurrentStandardOutContents(), storage.getCurrentStandardErrContents());
     }
 
-    private static void startServer(int port) throws Exception {
+    private static void startServer(int port, LoginService loginService) throws Exception {
         server = new Server(port);
         ApplicationInfo appInfo = new ApplicationInfo();
         String version = appInfo.getMavenVersion();
         String warfile = "target/libs/thermostat-web-war-" + version + ".war";
         WebAppContext ctx = new WebAppContext(warfile, "/thermostat");
         ctx.getSecurityHandler().setAuthMethod("BASIC");
-        ctx.getSecurityHandler().setLoginService(new MappedLoginService() {
-            
-            @Override
-            protected void loadUsers() throws IOException {
-                putUser("testname", new Password("testpasswd"), new String[] { "thermostat-agent" });
-                putUser("test-no-role", new Password("testpasswd"), new String[] { "fluff" });
-                putUser("test-cmd-channel", new Password("testpasswd"), new String[] { "thermostat-cmd-channel" });
-            }
-            
-            @Override
-            protected UserIdentity loadUser(String username) {
-                if (username.equals("test-cmd-channel")) {
-                    return new DefaultUserIdentity(null, null, new String[] { "thermostat-cmd-channel" });
-                }
-                return new DefaultUserIdentity(null, null, new String[] { "thermostat-agent" });
-            }
-        });
+        ctx.getSecurityHandler().setLoginService(loginService);
         server.setHandler(ctx);
         server.start();
     }
 
-    private static void registerCategory() {
+    private static void connectStorage(String username, String password) {
         String url = "http://localhost:" + port + "/thermostat/storage";
-        StartupConfiguration config = new ConnectionConfiguration(url, "testname", "testpasswd");
+        StartupConfiguration config = new ConnectionConfiguration(url, username, password);
         webStorage = new WebStorage(config);
         webStorage.setAgentId(new UUID(42, 24));
         webStorage.getConnection().connect();
-        webStorage.registerCategory(VmClassStatDAO.vmClassStatsCategory);
     }
 
     @Test
-    public void testPutFind() {
+    public void authorizedAdd() throws Exception {
+        String[] roleNames = new String[] {
+                Roles.REGISTER_CATEGORY,
+                Roles.ACCESS_REALM,
+                Roles.LOGIN,
+                Roles.APPEND
+        };
+        String testuser = "testuser";
+        String password = "testpassword";
+        final LoginService loginService = new TestLoginService(testuser, password, roleNames);
+        port = FreePortFinder.findFreePort(new TryPort() {
+            
+            @Override
+            public void tryPort(int port) throws Exception {
+                startServer(port, loginService);
+            }
+        });
+        connectStorage(testuser, password);
+        webStorage.registerCategory(VmClassStatDAO.vmClassStatsCategory);
+        
         Add add = webStorage.createAdd(VmClassStatDAO.vmClassStatsCategory);
         VmClassStat pojo = new VmClassStat();
         pojo.setAgentId("fluff");
@@ -159,7 +159,29 @@
         pojo.setVmId(987);
         add.setPojo(pojo);
         add.apply();
-
+    }
+    
+    @Test
+    public void authorizedQuery() throws Exception {
+        String[] roleNames = new String[] {
+                Roles.REGISTER_CATEGORY,
+                Roles.READ,
+                Roles.LOGIN,
+                Roles.ACCESS_REALM
+        };
+        String testuser = "testuser";
+        String password = "testpassword";
+        final LoginService loginService = new TestLoginService(testuser, password, roleNames);
+        port = FreePortFinder.findFreePort(new TryPort() {
+            
+            @Override
+            public void tryPort(int port) throws Exception {
+                startServer(port, loginService);
+            }
+        });
+        connectStorage(testuser, password);
+        webStorage.registerCategory(VmClassStatDAO.vmClassStatsCategory);
+        
         Query<VmClassStat> query = webStorage.createQuery(VmClassStatDAO.vmClassStatsCategory);
         Cursor<VmClassStat> cursor = query.execute();
         assertTrue(cursor.hasNext());
@@ -172,7 +194,25 @@
     }
 
     @Test
-    public void testLoadSave() throws IOException, InterruptedException {
+    public void authorizedLoadSave() throws Exception {
+        String[] roleNames = new String[] {
+                Roles.LOAD_FILE,
+                Roles.SAVE_FILE,
+                Roles.ACCESS_REALM,
+                Roles.LOGIN
+        };
+        String testuser = "testuser";
+        String password = "testpassword";
+        final LoginService loginService = new TestLoginService(testuser, password, roleNames);
+        port = FreePortFinder.findFreePort(new TryPort() {
+            
+            @Override
+            public void tryPort(int port) throws Exception {
+                startServer(port, loginService);
+            }
+        });
+        connectStorage(testuser, password);
+        
         byte[] data = "Hello World".getBytes();
         webStorage.saveFile("test", new ByteArrayInputStream(data));
         // Note: Apparently, saving the file takes a bit. Without this
@@ -188,4 +228,30 @@
         }
         assertEquals("Hello World", str.toString());
     }
+    
+    private static class TestLoginService extends MappedLoginService {
+
+        private final String[] roleNames;
+        private final String username;
+        private final String password;
+
+        private TestLoginService(String username, String password,
+                String[] roleNames) {
+            this.username = username;
+            this.password = password;
+            this.roleNames = roleNames;
+        }
+
+        @Override
+        protected void loadUsers() throws IOException {
+            putUser(username, new Password(password),
+                    roleNames);
+        }
+
+        @Override
+        protected UserIdentity loadUser(String username) {
+            return new DefaultUserIdentity(null, null,
+                    roleNames);
+        }
+    }
 }
--- a/web/cmd/src/main/java/com/redhat/thermostat/web/cmd/WebServiceLauncher.java	Fri May 03 12:39:38 2013 -0400
+++ b/web/cmd/src/main/java/com/redhat/thermostat/web/cmd/WebServiceLauncher.java	Fri Apr 19 14:09:10 2013 +0200
@@ -57,6 +57,7 @@
 import com.redhat.thermostat.common.utils.HostPortPair;
 import com.redhat.thermostat.storage.mongodb.MongoStorageProvider;
 import com.redhat.thermostat.web.server.WebStorageEndPoint;
+import com.redhat.thermostat.web.server.auth.Roles;
 
 class WebServiceLauncher {
 
@@ -111,7 +112,7 @@
         ConstraintMapping constraintMap = new ConstraintMapping();
         Constraint constraint = new Constraint();
         constraint.setAuthenticate(true);
-        constraint.setRoles(new String[] { "thermostat-client", "thermostat-agent", "thermostat-cmd-channel" });
+        constraint.setRoles(new String[] { Roles.ACCESS_REALM });
         constraint.setName("Entire Application");
         constraintMap.setPathSpec("/*");
         constraintMap.setConstraint(constraint);
@@ -119,18 +120,34 @@
         secHandler.setRealmName("Thermostat Realm");
         secHandler.setAuthMethod("BASIC");
         secHandler.addConstraintMapping(constraintMap);
-        secHandler.addRole("thermostat-agent");
-        secHandler.addRole("thermostat-client");
+        // inform security handler about all roles
+        for (String role : Roles.ALL_ROLES) {
+            secHandler.addRole(role);
+        }
         secHandler.setLoginService(new MappedLoginService() {
             
             @Override
             protected void loadUsers() throws IOException {
-                putUser("thermostat", new Password("thermostat"), new String[] { "thermostat-agent", "thermostat-client", "thermostat-cmd-channel" });
+                // Register a thermostat agent user
+                putUser("thermostat-agent", new Password("agent-tester"), Roles.AGENT_ROLES);
+                // Same for a client
+                putUser("thermostat-client", new Password("client-tester"), Roles.CLIENT_ROLES);
+                // A realm access test user
+                putUser("thermostat-realm-user", new Password("realm-tester"), new String[] { Roles.ACCESS_REALM });
             }
 
             @Override
             protected UserIdentity loadUser(String username) {
-                return new DefaultUserIdentity(null, null, new String[] { "thermostat-agent", "thermostat-client", "thermostat-cmd-channel" });
+                if (username.equals("thermostat-agent")) {
+                    return new DefaultUserIdentity(null, null, Roles.AGENT_ROLES);
+                } else if (username.equals("thermostat-client")) {
+                    return new DefaultUserIdentity(null, null, Roles.CLIENT_ROLES);
+                } else if (username.equals("thermostat-realm-user")) {
+                    return new DefaultUserIdentity(null, null, new String[] { Roles.ACCESS_REALM } );
+                } else {
+                    // return empty identity
+                    return new DefaultUserIdentity(null, null, new String[0]);
+                }
             }
         });
         ctx.setSecurityHandler(secHandler);
--- a/web/cmd/src/test/java/com/redhat/thermostat/web/cmd/WebServiceLauncherTest.java	Fri May 03 12:39:38 2013 -0400
+++ b/web/cmd/src/test/java/com/redhat/thermostat/web/cmd/WebServiceLauncherTest.java	Fri Apr 19 14:09:10 2013 +0200
@@ -39,6 +39,8 @@
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.when;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -109,9 +111,10 @@
     }
     
     @Test
-    @Ignore("server.start() throws NPE for some reason")
+    @Ignore("server.start() throws NPE since it's a final method declared in class AbstractLifeCycle and Mockito doesn't do final method mocking.")
     public void verifyStartDoesStartServer() throws Exception {
         Server server = mock(Server.class);
+        doNothing().when(server).start();
         WebServiceLauncher launcher = new WebServiceLauncher(server);
         launcher.setIpAddresses(dummyIp);
         launcher.setStorageURL("mongodb://test.example.org/db");
--- a/web/server/pom.xml	Fri May 03 12:39:38 2013 -0400
+++ b/web/server/pom.xml	Fri Apr 19 14:09:10 2013 +0200
@@ -136,7 +136,8 @@
             <Bundle-SymbolicName>com.redhat.thermostat.web.server</Bundle-SymbolicName>
             <Bundle-Vendor>Red Hat, Inc.</Bundle-Vendor>
             <Export-Package>
-              com.redhat.thermostat.web.server
+              com.redhat.thermostat.web.server,
+              com.redhat.thermostat.web.server.auth
             </Export-Package>
             <Private-Package>
               com.redhat.thermostat.web.server.internal
--- a/web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java	Fri May 03 12:39:38 2013 -0400
+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java	Fri Apr 19 14:09:10 2013 +0200
@@ -87,15 +87,13 @@
 import com.redhat.thermostat.web.common.WebQuery;
 import com.redhat.thermostat.web.common.WebRemove;
 import com.redhat.thermostat.web.common.WebUpdate;
+import com.redhat.thermostat.web.server.auth.Roles;
 
 @SuppressWarnings("serial")
 public class WebStorageEndPoint extends HttpServlet {
 
     private static final String TOKEN_MANAGER_TIMEOUT_PARAM = "token-manager-timeout";
     private static final String TOKEN_MANAGER_KEY = "token-manager";
-    private static final String ROLE_THERMOSTAT_AGENT = "thermostat-agent";
-    private static final String ROLE_THERMOSTAT_CLIENT = "thermostat-client";
-    private static final String ROLE_CMD_CHANNEL = "thermostat-cmd-channel";
 
     // our strings can contain non-ASCII characters. Use UTF-8
     // see also PR 1344
@@ -156,7 +154,10 @@
             // to avoid further memory leaks.
             Connection connection = storage.getConnection();
             try {
-                connection.disconnect();
+                // Tests have null connections
+                if (connection != null) {
+                    connection.disconnect();
+                }
             } finally {
                 storage.shutdown();
             }
@@ -215,16 +216,28 @@
     }
 
     private void ping(HttpServletRequest req, HttpServletResponse resp) {
+        if (! isAuthorized(req, resp, Roles.LOGIN)) {
+            return;
+        }
+        
         resp.setStatus(HttpServletResponse.SC_OK);
     }
 
     private void purge(HttpServletRequest req, HttpServletResponse resp) {
+        if (! isAuthorized(req, resp, Roles.PURGE)) {
+            return;
+        }
+        
         String agentId = req.getParameter("agentId");
         storage.purge(agentId);
         resp.setStatus(HttpServletResponse.SC_OK);
     }
 
     private void loadFile(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        if (! isAuthorized(req, resp, Roles.LOAD_FILE)) {
+            return;
+        }
+        
         String name = req.getParameter("file");
         try (InputStream data = storage.loadFile(name)) {
             OutputStream out = resp.getOutputStream();
@@ -240,6 +253,10 @@
     }
 
     private void saveFile(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+        if (! isAuthorized(req, resp, Roles.SAVE_FILE)) {
+            return;
+        }
+        
         boolean isMultipart = ServletFileUpload.isMultipartContent(req);
         if (! isMultipart) {
             throw new ServletException("expected multipart message");
@@ -264,6 +281,9 @@
     }
 
     private void getCount(HttpServletRequest req, HttpServletResponse resp) {
+        if (! isAuthorized(req, resp, Roles.GET_COUNT)) {
+            return;
+        }
         try {
             String categoryParam = req.getParameter("category");
             int categoryId = gson.fromJson(categoryParam, Integer.class);
@@ -280,6 +300,10 @@
     }
 
     private synchronized void registerCategory(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        if (! isAuthorized(req, resp, Roles.REGISTER_CATEGORY)) {
+            return;
+        }
+        
         String categoryName = req.getParameter("name");
         String categoryParam = req.getParameter("category");
         int id;
@@ -303,19 +327,25 @@
     }
 
     private void putPojo(HttpServletRequest req, HttpServletResponse resp) {
-        if (! req.isUserInRole(ROLE_THERMOSTAT_AGENT)) {
-            resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
-            return;
-        }
-
         String insertParam = req.getParameter("insert");
         WebInsert insert = gson.fromJson(insertParam, WebInsert.class);
         int categoryId = insert.getCategoryId();
         Category<?> category = getCategoryFromId(categoryId);
+        Put targetPut = null;
+        if (insert.isReplace()) {
+            if (! isAuthorized(req, resp, Roles.REPLACE)) {
+                return;
+            }
+            targetPut = storage.createReplace(category);
+        } else {
+            if (! isAuthorized(req, resp, Roles.APPEND)) {
+                return;
+            }
+            targetPut = storage.createAdd(category);
+        }
         Class<? extends Pojo> pojoCls = category.getDataClass();
         String pojoParam = req.getParameter("pojo");
         Pojo pojo = gson.fromJson(pojoParam, pojoCls);
-        Put targetPut = insert.isReplace() ? storage.createReplace(category) : storage.createAdd(category);
         targetPut.setPojo(pojo);
         targetPut.apply();
         resp.setStatus(HttpServletResponse.SC_OK);
@@ -323,6 +353,10 @@
 
     @SuppressWarnings({ "rawtypes", "unchecked" })
     private void removePojo(HttpServletRequest req, HttpServletResponse resp) {
+        if (! isAuthorized(req, resp, Roles.DELETE)) {
+            return;
+        }
+        
         String removeParam = req.getParameter("remove");
         WebRemove remove = gson.fromJson(removeParam, WebRemove.class);
         Remove targetRemove = storage.createRemove();
@@ -338,6 +372,10 @@
 
     @SuppressWarnings({ "rawtypes", "unchecked" })
     private void updatePojo(HttpServletRequest req, HttpServletResponse resp) {
+        if (! isAuthorized(req, resp, Roles.UPDATE)) {
+            return;
+        }
+        
         try {
             String updateParam = req.getParameter("update");
             WebUpdate update = gson.fromJson(updateParam, WebUpdate.class);
@@ -374,6 +412,9 @@
 
     @SuppressWarnings({ "rawtypes", "unchecked" })
     private void findAll(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        if (! isAuthorized(req, resp, Roles.READ)) {
+            return;
+        }
         String queryParam = req.getParameter("query");
         WebQuery query = gson.fromJson(queryParam, WebQuery.class);
         Query targetQuery = constructTargetQuery(query);
@@ -385,6 +426,7 @@
         writeResponse(resp, resultList.toArray());
     }
 
+    @SuppressWarnings({ "rawtypes", "unchecked" })
     private Query<?> constructTargetQuery(WebQuery<? extends Pojo> query) {
         int categoryId = query.getCategoryId();
         Category<?> category = getCategoryFromId(categoryId);
@@ -414,8 +456,7 @@
     }
 
     private void generateToken(HttpServletRequest req, HttpServletResponse resp) throws IOException {
-        if (! req.isUserInRole(ROLE_CMD_CHANNEL)) {
-            resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+        if (! isAuthorized(req, resp, Roles.CMD_CHANNEL_GENERATE) ) {
             return;
         }
         TokenManager tokenManager = (TokenManager) getServletContext().getAttribute(TOKEN_MANAGER_KEY);
@@ -428,8 +469,7 @@
     }
 
     private void verifyToken(HttpServletRequest req, HttpServletResponse resp) {
-        if (! req.isUserInRole(ROLE_CMD_CHANNEL)) {
-            resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+        if (! isAuthorized(req, resp, Roles.CMD_CHANNEL_VERIFY) ) {
             return;
         }
         TokenManager tokenManager = (TokenManager) getServletContext().getAttribute(TOKEN_MANAGER_KEY);
@@ -438,11 +478,20 @@
         byte[] token = Base64.decodeBase64(req.getParameter("token"));
         boolean verified = tokenManager.verifyToken(clientToken, token);
         if (! verified) {
-            resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+            resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
         } else {
             resp.setStatus(HttpServletResponse.SC_OK);
         }
     }
+    
+    private boolean isAuthorized(HttpServletRequest req, HttpServletResponse resp, String role) {
+        if (req.isUserInRole(role)) {
+            return true;
+        } else {
+            resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
+            return false;
+        }
+    }
 
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/auth/Roles.java	Fri Apr 19 14:09:10 2013 +0200
@@ -0,0 +1,77 @@
+/*
+ * 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;
+
+/**
+ * Roles thermostat knows about.
+ *
+ */
+public interface Roles {
+
+    final String APPEND = "thermostat-add";
+    final String REPLACE = "thermostat-replace";
+    final String UPDATE = "thermostat-update";
+    final String DELETE = "thermostat-remove";
+    final String READ = "thermostat-query";
+    final String GET_COUNT = "thermostat-get-count";
+    final String LOAD_FILE = "thermostat-load-file";
+    final String SAVE_FILE = "thermostat-save-file";
+    final String PURGE = "thermostat-purge";
+    final String REGISTER_CATEGORY = "thermostat-register-category";
+    final String CMD_CHANNEL_VERIFY = "thermostat-cmdc-verify";
+    final String CMD_CHANNEL_GENERATE = "thermostat-cmdc-generate";
+    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
+    };
+    
+    final String[] AGENT_ROLES = {
+            APPEND, REPLACE, UPDATE, DELETE, SAVE_FILE, PURGE,
+            REGISTER_CATEGORY, CMD_CHANNEL_VERIFY,
+            LOGIN, ACCESS_REALM
+    };
+    
+    final String[] CLIENT_ROLES = {
+            ACCESS_REALM, LOGIN, CMD_CHANNEL_GENERATE, LOAD_FILE,
+            GET_COUNT, READ, REGISTER_CATEGORY
+    };
+    
+}
--- a/web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java	Fri May 03 12:39:38 2013 -0400
+++ b/web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java	Fri Apr 19 14:09:10 2013 +0200
@@ -60,6 +60,7 @@
 
 import org.apache.commons.codec.binary.Base64;
 import org.eclipse.jetty.security.DefaultUserIdentity;
+import org.eclipse.jetty.security.LoginService;
 import org.eclipse.jetty.security.MappedLoginService;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.UserIdentity;
@@ -94,6 +95,7 @@
 import com.redhat.thermostat.web.common.WebQuery;
 import com.redhat.thermostat.web.common.WebRemove;
 import com.redhat.thermostat.web.common.WebUpdate;
+import com.redhat.thermostat.web.server.auth.Roles;
 
 public class WebStorageEndpointTest {
 
@@ -160,37 +162,13 @@
         mockStorage = mock(Storage.class);
         StorageWrapper.setStorage(mockStorage);
 
-        port = FreePortFinder.findFreePort(new TryPort() {
-            
-            @Override
-            public void tryPort(int port) throws Exception {
-                startServer(port);
-            }
-        });
-        registerCategory();
     }
 
-    private void startServer(int port) throws Exception {
+    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(new MappedLoginService() {
-            
-            @Override
-            protected void loadUsers() throws IOException {
-                putUser("testname", new Password("testpasswd"), new String[] { "thermostat-agent" });
-                putUser("test-no-role", new Password("testpasswd"), new String[] { "fluff" });
-                putUser("test-cmd-channel", new Password("testpasswd"), new String[] { "thermostat-cmd-channel" });
-            }
-            
-            @Override
-            protected UserIdentity loadUser(String username) {
-                if (username.equals("test-cmd-channel")) {
-                    return new DefaultUserIdentity(null, null, new String[] { "thermostat-cmd-channel" });
-                }
-                return new DefaultUserIdentity(null, null, new String[] { "thermostat-agent" });
-            }
-        });
+        ctx.getSecurityHandler().setLoginService(loginService);
         server.setHandler(ctx);
         server.start();
     }
@@ -202,7 +180,23 @@
     }
 
     @Test
-    public void testFindAllPojos() throws IOException {
+    public void authorizedFindAllPojos() throws Exception {
+        String[] roleNames = new String[] {
+                Roles.REGISTER_CATEGORY,
+                Roles.READ
+        };
+        String testuser = "testuser";
+        String password = "testpassword";
+        final LoginService loginService = new TestLoginService(testuser, password, roleNames); 
+        port = FreePortFinder.findFreePort(new TryPort() {
+            
+            @Override
+            public void tryPort(int port) throws Exception {
+                startServer(port, loginService);
+            }
+        });
+        registerCategory(testuser, password);
+        
         TestClass expected1 = new TestClass();
         expected1.setKey1("fluff1");
         expected1.setKey2(42);
@@ -222,6 +216,7 @@
         URL url = new URL(endpoint + "/find-all");
         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
         conn.setRequestMethod("POST");
+        sendAuthorization(conn, testuser, password);
         conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
         conn.setDoInput(true);
         conn.setDoOutput(true);
@@ -255,8 +250,23 @@
     }
 
     @Test
-    public void testPutPojo() throws IOException {
-
+    public void authorizedReplacePutPojo() throws Exception {
+        String[] roleNames = new String[] {
+                Roles.REPLACE,
+                Roles.REGISTER_CATEGORY
+        };
+        String testuser = "testuser";
+        String password = "testpassword";
+        final LoginService loginService = new TestLoginService(testuser, password, roleNames); 
+        port = FreePortFinder.findFreePort(new TryPort() {
+            
+            @Override
+            public void tryPort(int port) throws Exception {
+                startServer(port, loginService);
+            }
+        });
+        registerCategory(testuser, password);
+        
         Replace replace = mock(Replace.class);
         when(mockStorage.createReplace(any(Category.class))).thenReturn(replace);
 
@@ -269,7 +279,7 @@
         URL url = new URL(endpoint + "/put-pojo");
         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
         conn.setRequestMethod("POST");
-        sendAuthorization(conn, "testname", "testpasswd");
+        sendAuthorization(conn, testuser, password);
 
         conn.setDoOutput(true);
         conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
@@ -296,8 +306,24 @@
     }
 
     @Test
-    public void testRemovePojo() throws IOException {
-
+    public void authorizedRemovePojo() throws Exception {
+        String[] roleNames = new String[] {
+                Roles.DELETE,
+                Roles.REGISTER_CATEGORY
+        };
+        String testuser = "testuser";
+        String password = "testpassword";
+        final LoginService loginService = new TestLoginService(testuser, password, roleNames); 
+        port = FreePortFinder.findFreePort(new TryPort() {
+            
+            @Override
+            public void tryPort(int port) throws Exception {
+                startServer(port, loginService);
+            }
+        });
+        registerCategory(testuser, password);
+        
+        
         Remove mockRemove = mock(Remove.class);
         when(mockRemove.from(any(Category.class))).thenReturn(mockRemove);
         when(mockRemove.where(any(Key.class), any())).thenReturn(mockRemove);
@@ -308,6 +334,8 @@
 
         URL url = new URL(endpoint + "/remove-pojo");
         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+        conn.setRequestMethod("POST");
+        sendAuthorization(conn, testuser, password);
         conn.setDoOutput(true);
         conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
         Map<Category<?>,Integer> categoryIds = new HashMap<>();
@@ -328,8 +356,23 @@
     }
 
     @Test
-    public void testUpdatePojo() throws IOException {
-
+    public void authorizedUpdatePojo() throws Exception {
+        String[] roleNames = new String[] {
+                Roles.UPDATE,
+                Roles.REGISTER_CATEGORY
+        };
+        String testuser = "testuser";
+        String password = "testpassword";
+        final LoginService loginService = new TestLoginService(testuser, password, roleNames); 
+        port = FreePortFinder.findFreePort(new TryPort() {
+            
+            @Override
+            public void tryPort(int port) throws Exception {
+                startServer(port, loginService);
+            }
+        });
+        registerCategory(testuser, password);
+        
         Update mockUpdate = mock(Update.class);
         when(mockStorage.createUpdate(any(Category.class))).thenReturn(mockUpdate);
 
@@ -337,6 +380,8 @@
 
         URL url = new URL(endpoint + "/update-pojo");
         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+        conn.setRequestMethod("POST");
+        sendAuthorization(conn, testuser, password);
         conn.setDoOutput(true);
         conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
 
@@ -366,13 +411,30 @@
 
 
     @Test
-    public void testGetCount() throws IOException {
-
+    public void authorizedGetCount() throws Exception {
+        String[] roleNames = new String[] {
+                Roles.GET_COUNT,
+                Roles.REGISTER_CATEGORY
+        };
+        String testuser = "testuser";
+        String password = "testpassword";
+        final LoginService loginService = new TestLoginService(testuser, password, roleNames); 
+        port = FreePortFinder.findFreePort(new TryPort() {
+            
+            @Override
+            public void tryPort(int port) throws Exception {
+                startServer(port, loginService);
+            }
+        });
+        registerCategory(testuser, password);
+        
         when(mockStorage.getCount(category)).thenReturn(12345L);
         String endpoint = getEndpoint();
 
         URL url = new URL(endpoint + "/get-count");
         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+        conn.setRequestMethod("POST");
+        sendAuthorization(conn, testuser, password);
         conn.setDoOutput(true);
         conn.setDoInput(true);
         conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
@@ -391,11 +453,26 @@
     }
 
     @Test
-    public void testSaveFile() throws IOException {
+    public void authorizedSaveFile() throws Exception {
+        String[] roleNames = new String[] {
+                Roles.SAVE_FILE,
+        };
+        String testuser = "testuser";
+        String password = "testpassword";
+        final LoginService loginService = new TestLoginService(testuser, password, roleNames); 
+        port = FreePortFinder.findFreePort(new TryPort() {
+            
+            @Override
+            public void tryPort(int port) throws Exception {
+                startServer(port, loginService);
+            }
+        });
         String endpoint = getEndpoint();
 
         URL url = new URL(endpoint + "/save-file");
         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+        conn.setRequestMethod("POST");
+        sendAuthorization(conn, testuser, password);
         conn.setDoOutput(true);
         conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=fluff");
         conn.setRequestProperty("Transfer-Encoding", "chunked");
@@ -425,8 +502,21 @@
     }
 
     @Test
-    public void testLoadFile() throws IOException {
-
+    public void authorizedLoadFile() throws Exception {
+        String[] roleNames = new String[] {
+                Roles.LOAD_FILE,
+        };
+        String testuser = "testuser";
+        String password = "testpassword";
+        final LoginService loginService = new TestLoginService(testuser, password, roleNames); 
+        port = FreePortFinder.findFreePort(new TryPort() {
+            
+            @Override
+            public void tryPort(int port) throws Exception {
+                startServer(port, loginService);
+            }
+        });
+        
         byte[] data = "Hello World".getBytes();
         InputStream in = new ByteArrayInputStream(data);
         when(mockStorage.loadFile("fluff")).thenReturn(in);
@@ -435,6 +525,8 @@
         URL url = new URL(endpoint + "/load-file");
 
         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+        conn.setRequestMethod("POST");
+        sendAuthorization(conn, testuser, password);
         conn.setDoOutput(true);
         conn.setDoInput(true);
         OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
@@ -455,19 +547,33 @@
     }
 
     @Test
-    public void testPurge() throws IOException {
+    public void authorizedPurge() throws Exception {
+        String[] roleNames = new String[] {
+                Roles.PURGE,
+        };
+        String testuser = "testuser";
+        String password = "testpassword";
+        final LoginService loginService = new TestLoginService(testuser, password, roleNames); 
+        port = FreePortFinder.findFreePort(new TryPort() {
+            
+            @Override
+            public void tryPort(int port) throws Exception {
+                startServer(port, loginService);
+            }
+        });
         String endpoint = getEndpoint();
         URL url = new URL(endpoint + "/purge");
         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
         conn.setDoOutput(true);
         conn.setRequestMethod("POST");
+        sendAuthorization(conn, testuser, password);
         conn.getOutputStream().write("agentId=fluff".getBytes());
         int status = conn.getResponseCode();
         assertEquals(200, status);
         verify(mockStorage).purge("fluff");
     }
 
-    private void registerCategory() {
+    private void registerCategory(String username, String password) {
         try {
             String endpoint = getEndpoint();
             URL url = new URL(endpoint + "/register-category");
@@ -477,6 +583,7 @@
             conn.setDoOutput(true);
             conn.setDoInput(true);
             conn.setRequestMethod("POST");
+            sendAuthorization(conn, username, password);
             OutputStream out = conn.getOutputStream();
             Gson gson = new Gson();
             OutputStreamWriter writer = new OutputStreamWriter(out);
@@ -500,32 +607,69 @@
     }
 
     @Test
-    public void testBasicGenerateToken() throws IOException {
-        
-        verifyGenerateToken();
+    public void authorizedGenerateToken() throws Exception {
+        String[] roleNames = new String[] {
+                Roles.CMD_CHANNEL_GENERATE
+        };
+        String testuser = "testuser";
+        String password = "testpassword";
+        final LoginService loginService = new TestLoginService(testuser, password, roleNames); 
+        port = FreePortFinder.findFreePort(new TryPort() {
+            
+            @Override
+            public void tryPort(int port) throws Exception {
+                startServer(port, loginService);
+            }
+        });
+        verifyAuthorizedGenerateToken(testuser, password);
     }
 
     @Test
-    public void testGenerateTokenWithoutAuth() throws IOException {
+    public void unauthorizedGenerateToken() throws Exception {
+        String[] noRoles = new String[0];
+        String testuser = "testuser";
+        String password = "testpassword";
+        final LoginService loginService = new TestLoginService(testuser, password, noRoles); 
+        
+        port = FreePortFinder.findFreePort(new TryPort() {
+            
+            @Override
+            public void tryPort(int port) throws Exception {
+                startServer(port, loginService);
+            }
+        });
         
         String endpoint = getEndpoint();
         URL url = new URL(endpoint + "/generate-token");
 
         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
         conn.setRequestMethod("POST");
-        sendAuthorization(conn, "test-no-role", "testpasswd");
+        sendAuthorization(conn, testuser, password);
         conn.setDoOutput(true);
         conn.setDoInput(true);
         OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
         out.write("client-token=fluff");
         out.flush();
-        assertEquals(401, conn.getResponseCode());
+        assertEquals(403, conn.getResponseCode());
     }
 
     @Test
-    public void testBasicGenerateVerifyToken() throws IOException {
-        
-        byte[] token = verifyGenerateToken();
+    public void authorizedGenerateVerifyToken() throws Exception {
+        String[] roleNames = new String[] {
+                Roles.CMD_CHANNEL_GENERATE,
+                Roles.CMD_CHANNEL_VERIFY
+        };
+        String testuser = "testuser";
+        String password = "testpassword";
+        final LoginService loginService = new TestLoginService(testuser, password, roleNames); 
+        port = FreePortFinder.findFreePort(new TryPort() {
+            
+            @Override
+            public void tryPort(int port) throws Exception {
+                startServer(port, loginService);
+            }
+        });
+        byte[] token = verifyAuthorizedGenerateToken(testuser, password);
 
         String endpoint = getEndpoint();
         URL url = new URL(endpoint + "/verify-token");
@@ -533,7 +677,7 @@
         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
         conn.setRequestMethod("POST");
         conn.setRequestProperty("Content-type", "application/x-www-form-urlencoded");
-        sendAuthorization(conn, "test-cmd-channel", "testpasswd");
+        sendAuthorization(conn, testuser, password);
         conn.setDoOutput(true);
         conn.setDoInput(true);
         OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
@@ -543,9 +687,22 @@
     }
 
     @Test
-    public void testTokenTimeout() throws IOException, InterruptedException {
-        
-        byte[] token = verifyGenerateToken();
+    public void authorizedTokenTimeout() throws Exception {
+        String[] roleNames = new String[] {
+                Roles.CMD_CHANNEL_GENERATE,
+                Roles.CMD_CHANNEL_VERIFY
+        };
+        String testuser = "testuser";
+        String password = "testpassword";
+        final LoginService loginService = new TestLoginService(testuser, password, roleNames); 
+        port = FreePortFinder.findFreePort(new TryPort() {
+            
+            @Override
+            public void tryPort(int port) throws Exception {
+                startServer(port, loginService);
+            }
+        });
+        byte[] token = verifyAuthorizedGenerateToken(testuser, password);
 
         Thread.sleep(700); // Timeout is set to 500ms for tests, 700ms should be enough for everybody. ;-)
 
@@ -555,17 +712,30 @@
         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
         conn.setRequestMethod("POST");
         conn.setRequestProperty("Content-type", "application/x-www-form-urlencoded");
-        sendAuthorization(conn, "test-cmd-channel", "testpasswd");
+        sendAuthorization(conn, testuser, password);
         conn.setDoOutput(true);
         conn.setDoInput(true);
         OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
         out.write("client-token=fluff&token=" + URLEncoder.encode(Base64.encodeBase64String(token), "UTF-8"));
         out.flush();
-        assertEquals(401, conn.getResponseCode());
+        assertEquals(403, conn.getResponseCode());
     }
 
     @Test
-    public void testVerifyNonExistentToken() throws IOException {
+    public void authorizedVerifyNonExistentToken() throws Exception {
+        String[] roleNames = new String[] {
+                Roles.CMD_CHANNEL_VERIFY
+        };
+        String testuser = "testuser";
+        String password = "testpassword";
+        final LoginService loginService = new TestLoginService(testuser, password, roleNames); 
+        port = FreePortFinder.findFreePort(new TryPort() {
+            
+            @Override
+            public void tryPort(int port) throws Exception {
+                startServer(port, loginService);
+            }
+        });
         
         byte[] token = "fluff".getBytes();
 
@@ -575,22 +745,22 @@
         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
         conn.setRequestMethod("POST");
         conn.setRequestProperty("Content-type", "application/x-www-form-urlencoded");
-        sendAuthorization(conn, "test-cmd-channel", "testpasswd");
+        sendAuthorization(conn, testuser, password);
         conn.setDoOutput(true);
         conn.setDoInput(true);
         OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
         out.write("client-token=fluff&token=" + URLEncoder.encode(Base64.encodeBase64String(token), "UTF-8"));
         out.flush();
-        assertEquals(401, conn.getResponseCode());
+        assertEquals(403, conn.getResponseCode());
     }
 
-    private byte[] verifyGenerateToken() throws IOException {
+    private byte[] verifyAuthorizedGenerateToken(String username, String password) throws IOException {
         String endpoint = getEndpoint();
         URL url = new URL(endpoint + "/generate-token");
 
         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
         conn.setRequestMethod("POST");
-        sendAuthorization(conn, "test-cmd-channel", "testpasswd");
+        sendAuthorization(conn, username, password);
         conn.setDoOutput(true);
         conn.setDoInput(true);
         OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
@@ -610,5 +780,31 @@
         }
         return token;
     }
+    
+    private static class TestLoginService extends MappedLoginService {
+
+        private final String[] roleNames;
+        private final String username;
+        private final String password;
+
+        private TestLoginService(String username, String password,
+                String[] roleNames) {
+            this.username = username;
+            this.password = password;
+            this.roleNames = roleNames;
+        }
+
+        @Override
+        protected void loadUsers() throws IOException {
+            putUser(username, new Password(password),
+                    roleNames);
+        }
+
+        @Override
+        protected UserIdentity loadUser(String username) {
+            return new DefaultUserIdentity(null, null,
+                    roleNames);
+        }
+    }
 }
 
--- a/web/war/src/main/webapp/WEB-INF/web.xml	Fri May 03 12:39:38 2013 -0400
+++ b/web/war/src/main/webapp/WEB-INF/web.xml	Fri Apr 19 14:09:10 2013 +0200
@@ -60,9 +60,7 @@
       <url-pattern>/*</url-pattern>
     </web-resource-collection>
     <auth-constraint>
-      <role-name>thermostat-agent</role-name>
-      <role-name>thermostat-client</role-name>
-      <role-name>thermostat-cmd-channel</role-name>
+      <role-name>thermostat-realm</role-name>
     </auth-constraint>
   </security-constraint>
 
@@ -72,13 +70,7 @@
   </login-config>
 
   <security-role>
-    <role-name>thermostat-agent</role-name>
-  </security-role>
-  <security-role>
-    <role-name>thermostat-client</role-name>
-  </security-role>
-  <security-role>
-    <role-name>thermostat-cmd-channel</role-name>
+    <role-name>thermostat-realm</role-name>
   </security-role>
 
 </web-app>