changeset 805:1ba58e849ae8

Add auth and auth to webservice. Reviewed-by: jerboaa Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2012-November/004293.html
author Roman Kennke <rkennke@redhat.com>
date Wed, 28 Nov 2012 12:24:00 +0100
parents d6145521e208
children 68eabdccc9b3
files web/client/src/main/java/com/redhat/thermostat/web/client/WebStorage.java web/client/src/main/java/com/redhat/thermostat/web/client/WebStorageProvider.java web/cmd/src/main/java/com/redhat/thermostat/web/cmd/WebServiceLauncher.java web/common/src/main/java/com/redhat/thermostat/web/common/StorageWrapper.java web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java web/war/pom.xml web/war/src/main/webapp/WEB-INF/web.xml
diffstat 8 files changed, 158 insertions(+), 22 deletions(-) [+]
line wrap: on
line diff
--- a/web/client/src/main/java/com/redhat/thermostat/web/client/WebStorage.java	Wed Nov 21 21:29:49 2012 +0100
+++ b/web/client/src/main/java/com/redhat/thermostat/web/client/WebStorage.java	Wed Nov 28 12:24:00 2012 +0100
@@ -44,6 +44,8 @@
 import java.io.OutputStream;
 import java.io.Reader;
 import java.lang.reflect.Array;
+import java.net.MalformedURLException;
+import java.net.URL;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -56,7 +58,9 @@
 import org.apache.http.HttpResponse;
 import org.apache.http.NameValuePair;
 import org.apache.http.StatusLine;
-import org.apache.http.client.HttpClient;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.Credentials;
+import org.apache.http.auth.UsernamePasswordCredentials;
 import org.apache.http.client.entity.UrlEncodedFormEntity;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.conn.ClientConnectionManager;
@@ -164,6 +168,7 @@
         @Override
         public void connect() {
             try {
+                initAuthentication(httpClient);
                 ping();
                 connected = true;
                 fireChanged(ConnectionStatus.CONNECTED);
@@ -251,7 +256,9 @@
 
     private Map<Category, Integer> categoryIds;
     private Gson gson;
-    private HttpClient httpClient;
+    private DefaultHttpClient httpClient;
+    private String username;
+    private String password;
 
     public WebStorage() {
         categoryIds = new HashMap<>();
@@ -261,6 +268,16 @@
         httpClient = client;
     }
 
+    private void initAuthentication(DefaultHttpClient client) throws MalformedURLException {
+        if (username != null && password != null) {
+            URL endpointURL = new URL(endpoint);
+            // TODO: Maybe also limit to realm like 'Thermostat Realm' or such?
+            AuthScope scope = new AuthScope(endpointURL.getHost(), endpointURL.getPort());
+            Credentials creds = new UsernamePasswordCredentials(username, password);
+            client.getCredentialsProvider().setCredentials(scope, creds);
+        }
+    }
+
     private void ping() throws StorageException {
         post(endpoint + "/ping", (HttpEntity) null).close();
     }
@@ -458,4 +475,9 @@
         this.endpoint = endpoint;
     }
 
+    public void setAuthConfig(String username, String password) {
+        this.username = username;
+        this.password = password;
+    }
+
 }
--- a/web/client/src/main/java/com/redhat/thermostat/web/client/WebStorageProvider.java	Wed Nov 21 21:29:49 2012 +0100
+++ b/web/client/src/main/java/com/redhat/thermostat/web/client/WebStorageProvider.java	Wed Nov 28 12:24:00 2012 +0100
@@ -1,5 +1,6 @@
 package com.redhat.thermostat.web.client;
 
+import com.redhat.thermostat.common.cli.AuthenticationConfiguration;
 import com.redhat.thermostat.storage.config.StartupConfiguration;
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.core.StorageProvider;
@@ -12,6 +13,10 @@
     public Storage createStorage() {
         WebStorage storage = new WebStorage();
         storage.setEndpoint(config.getDBConnectionString());
+        if (config instanceof AuthenticationConfiguration) {
+            AuthenticationConfiguration authConf = (AuthenticationConfiguration) config;
+            storage.setAuthConfig(authConf.getUsername(), authConf.getPassword());
+        }
         return storage;
     }
 
--- a/web/cmd/src/main/java/com/redhat/thermostat/web/cmd/WebServiceLauncher.java	Wed Nov 21 21:29:49 2012 +0100
+++ b/web/cmd/src/main/java/com/redhat/thermostat/web/cmd/WebServiceLauncher.java	Wed Nov 28 12:24:00 2012 +0100
@@ -37,18 +37,26 @@
 
 package com.redhat.thermostat.web.cmd;
 
+import java.io.IOException;
 import java.util.List;
 
+import org.eclipse.jetty.security.ConstraintMapping;
+import org.eclipse.jetty.security.ConstraintSecurityHandler;
+import org.eclipse.jetty.security.DefaultUserIdentity;
+import org.eclipse.jetty.security.MappedLoginService;
 import org.eclipse.jetty.server.Connector;
 import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.UserIdentity;
 import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.servlet.ServletHandler;
 import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.servlet.ServletMapping;
+import org.eclipse.jetty.util.security.Constraint;
+import org.eclipse.jetty.util.security.Password;
+import org.eclipse.jetty.webapp.WebAppContext;
 
 import com.redhat.thermostat.common.config.InvalidConfigurationException;
+import com.redhat.thermostat.common.storage.MongoStorageProvider;
+import com.redhat.thermostat.web.server.IpPortPair;
 import com.redhat.thermostat.web.server.WebStorageEndPoint;
-import com.redhat.thermostat.web.server.IpPortPair;
 
 class WebServiceLauncher {
 
@@ -76,19 +84,54 @@
             connectors[i].setHost(pair.getIp());
         }
         server.setConnectors( connectors );
-        ServletHandler handler = new ServletHandler();
+
+        WebAppContext ctx = new WebAppContext();
+        ctx.setContextPath("/");
+        // This prevents useless classloading, which could fail in the face of OSGi.
+        ctx.setConfigurations(new org.eclipse.jetty.webapp.Configuration[0]);
+
         ServletHolder servletHolder = new ServletHolder("rest-storage-end-point", new WebStorageEndPoint());
         servletHolder.setInitParameter(WebStorageEndPoint.STORAGE_ENDPOINT, storageURL);
-        handler.setServlets(new ServletHolder[] { servletHolder });
-        ServletMapping mapping = new ServletMapping();
-        mapping.setPathSpec("/");
-        mapping.setServletName("rest-storage-end-point");
-        handler.setServletMappings(new ServletMapping[] { mapping });
-        server.setHandler(handler);
+        servletHolder.setInitParameter(WebStorageEndPoint.STORAGE_CLASS, MongoStorageProvider.class.getName());
+        ctx.addServlet(servletHolder, "/");
+
+        configureSecurity(ctx);
+
+        server.setHandler(ctx);
         server.start();
         server.join();
     }
 
+    private void configureSecurity(WebAppContext ctx) {
+        ConstraintSecurityHandler secHandler = new ConstraintSecurityHandler();
+        ConstraintMapping constraintMap = new ConstraintMapping();
+        Constraint constraint = new Constraint();
+        constraint.setAuthenticate(true);
+        constraint.setRoles(new String[] { "thermostat-client", "thermostat-agent" });
+        constraint.setName("Entire Application");
+        constraintMap.setPathSpec("/*");
+        constraintMap.setConstraint(constraint);
+        
+        secHandler.setRealmName("Thermostat Realm");
+        secHandler.setAuthMethod("BASIC");
+        secHandler.addConstraintMapping(constraintMap);
+        secHandler.addRole("thermostat-agent");
+        secHandler.addRole("thermostat-client");
+        secHandler.setLoginService(new MappedLoginService() {
+            
+            @Override
+            protected void loadUsers() throws IOException {
+                putUser("thermostat", new Password("thermostat"), new String[] { "thermostat-agent", "thermostat-client" });
+            }
+
+            @Override
+            protected UserIdentity loadUser(String username) {
+                return new DefaultUserIdentity(null, null, new String[] { "thermostat-agent", "thermostat-client" });
+            }
+        });
+        ctx.setSecurityHandler(secHandler);
+    }
+
     void stop() throws Exception {
         server.stop();
         server.join();
--- a/web/common/src/main/java/com/redhat/thermostat/web/common/StorageWrapper.java	Wed Nov 21 21:29:49 2012 +0100
+++ b/web/common/src/main/java/com/redhat/thermostat/web/common/StorageWrapper.java	Wed Nov 28 12:24:00 2012 +0100
@@ -37,9 +37,9 @@
 
 package com.redhat.thermostat.web.common;
 
+import com.redhat.thermostat.common.storage.MongoStorageProvider;
 import com.redhat.thermostat.storage.config.StartupConfiguration;
 import com.redhat.thermostat.storage.core.Storage;
-import com.redhat.thermostat.storage.core.StorageException;
 import com.redhat.thermostat.storage.core.StorageProvider;
 
 public class StorageWrapper {
@@ -64,7 +64,15 @@
             storage.getConnection().connect();
             return storage;
         } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
-            throw new StorageException (e);
+            // This fallback should infact not be used. But it gives us an automatic
+            // Import-Package in the OSGi descriptor, which actually *prevents* this same
+            // exception from happening (a recursive self-defeating catch-block) :-)
+            System.err.println("could not instantiate provider: " + storageClass + ", falling back to MongoStorage");
+            e.printStackTrace();
+            StorageProvider provider = new MongoStorageProvider();
+            provider.setConfig(conf);
+            storage = provider.createStorage();
+            return storage;
         }
     }
     public static void setStorage(Storage storage) {
--- a/web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java	Wed Nov 21 21:29:49 2012 +0100
+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java	Wed Nov 28 12:24:00 2012 +0100
@@ -82,6 +82,7 @@
 @SuppressWarnings("serial")
 public class WebStorageEndPoint extends HttpServlet {
 
+    private static final String ROLE_THERMOSTAT_AGENT = "thermostat-agent";
     private Storage storage;
     private Gson gson;
 
@@ -222,6 +223,10 @@
     }
 
     private void putPojo(HttpServletRequest req, HttpServletResponse resp) {
+        if (! req.isUserInRole(ROLE_THERMOSTAT_AGENT)) {
+            resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+            return;
+        }
         try {
             String insertParam = req.getParameter("insert");
             WebInsert insert = gson.fromJson(insertParam, WebInsert.class);
--- a/web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java	Wed Nov 21 21:29:49 2012 +0100
+++ b/web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java	Wed Nov 28 12:24:00 2012 +0100
@@ -54,15 +54,16 @@
 import java.io.OutputStreamWriter;
 import java.io.Reader;
 import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLEncoder;
 import java.util.HashMap;
 import java.util.Map;
 
-import javax.sound.midi.SysexMessage;
-
+import org.eclipse.jetty.security.DefaultUserIdentity;
+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;
@@ -71,6 +72,8 @@
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
+import sun.misc.BASE64Encoder;
+
 import com.google.gson.Gson;
 import com.redhat.thermostat.storage.core.Categories;
 import com.redhat.thermostat.storage.core.Category;
@@ -79,18 +82,18 @@
 import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.core.Persist;
 import com.redhat.thermostat.storage.core.Query;
+import com.redhat.thermostat.storage.core.Query.Criteria;
+import com.redhat.thermostat.storage.core.Query.SortDirection;
 import com.redhat.thermostat.storage.core.Remove;
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.core.Update;
-import com.redhat.thermostat.storage.core.Query.Criteria;
-import com.redhat.thermostat.storage.core.Query.SortDirection;
 import com.redhat.thermostat.storage.model.BasePojo;
 import com.redhat.thermostat.test.FreePortFinder;
 import com.redhat.thermostat.test.FreePortFinder.TryPort;
 import com.redhat.thermostat.test.MockQuery;
-import com.redhat.thermostat.web.common.WebQuery;
 import com.redhat.thermostat.web.common.StorageWrapper;
 import com.redhat.thermostat.web.common.WebInsert;
+import com.redhat.thermostat.web.common.WebQuery;
 import com.redhat.thermostat.web.common.WebRemove;
 import com.redhat.thermostat.web.common.WebUpdate;
 
@@ -167,7 +170,21 @@
 
     private void startServer(int port) throws Exception {
         server = new Server(port);
-        server.setHandler(new WebAppContext("src/main/webapp", "/"));
+        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" });
+            }
+            
+            @Override
+            protected UserIdentity loadUser(String username) {
+                return new DefaultUserIdentity(null, null, new String[] { "thermostat-agent" });
+            }
+        });
+        server.setHandler(ctx);
         server.start();
     }
 
@@ -271,6 +288,12 @@
 
         URL url = new URL(endpoint + "/put-pojo");
         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+        conn.setRequestMethod("POST");
+        BASE64Encoder enc = new BASE64Encoder();
+        String userpassword = "testname:testpasswd";
+        String encodedAuthorization = enc.encode( userpassword.getBytes() );
+        conn.setRequestProperty("Authorization", "Basic "+ encodedAuthorization);
+
         conn.setDoOutput(true);
         conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
         WebInsert insert = new WebInsert(categoryId, true, TestClass.class.getName());
@@ -487,6 +510,6 @@
     }
 
     private String getEndpoint() {
-        return "http://localhost:" + port + "/storage";
+        return "http://testname:testpasswd@localhost:" + port + "/storage";
     }
 }
--- a/web/war/pom.xml	Wed Nov 21 21:29:49 2012 +0100
+++ b/web/war/pom.xml	Wed Nov 28 12:24:00 2012 +0100
@@ -62,6 +62,12 @@
       <artifactId>thermostat-web-server</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>servlet-api</artifactId>
+      <version>${javax.servlet.version}</version>
+      <scope>provided</scope>
+    </dependency>
   </dependencies>
 
 </project>
--- a/web/war/src/main/webapp/WEB-INF/web.xml	Wed Nov 21 21:29:49 2012 +0100
+++ b/web/war/src/main/webapp/WEB-INF/web.xml	Wed Nov 28 12:24:00 2012 +0100
@@ -23,4 +23,28 @@
     <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-agent</role-name>
+      <role-name>thermostat-client</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-agent</role-name>
+  </security-role>
+  <security-role>
+    <role-name>thermostat-client</role-name>
+  </security-role>
+
 </web-app>