changeset 445:cf6662b69c07

HttpCsrfFilter - Store strategy
author Devel 1
date Mon, 31 Jul 2017 09:19:32 +0200
parents 6002d2c3f9d1
children a6697ffb881c
files stress-tester/src/main/java/com/passus/st/client/http/filter/HttpCsrfFilter.java stress-tester/src/main/java/com/passus/st/client/http/filter/HttpCsrfFilterExtractorTransformer.java stress-tester/src/main/java/com/passus/st/client/http/filter/HttpCsrfFilterInjectorTransformer.java stress-tester/src/main/java/com/passus/st/client/http/filter/HttpCsrfFilterStoreTransformer.java stress-tester/src/test/java/com/passus/st/client/http/filter/HttpCsrfFilterTest.java
diffstat 5 files changed, 152 insertions(+), 30 deletions(-) [+]
line wrap: on
line diff
--- a/stress-tester/src/main/java/com/passus/st/client/http/filter/HttpCsrfFilter.java	Fri Jul 28 15:54:52 2017 +0200
+++ b/stress-tester/src/main/java/com/passus/st/client/http/filter/HttpCsrfFilter.java	Mon Jul 31 09:19:32 2017 +0200
@@ -10,6 +10,7 @@
 import com.passus.config.schema.KeyNameVaryListNodeDefinition;
 import com.passus.config.schema.NodeDefinition;
 import com.passus.config.schema.NodeDefinitionCreator;
+import com.passus.config.schema.ValueNodeDefinition;
 import com.passus.data.ByteString;
 import com.passus.net.http.HttpCookie;
 import com.passus.net.http.HttpMessage;
@@ -112,38 +113,48 @@
 
     public static abstract class Store {
 
-        public abstract void save(ByteString token);
+        public abstract void save(ParametersBag session, ByteString token);
 
-        public abstract ByteString load();
+        public abstract ByteString load(ParametersBag session);
+
+        protected Object get(ParametersBag session) {
+            return session != null ? session.get(SESSION_KEY) : null;
+        }
+
+        protected void put(ParametersBag session, Object store) {
+            session.set(SESSION_KEY, store);
+        }
     }
 
     public static final class QueueStore extends Store {
 
-        private final Queue<ByteString> tokens = new LinkedList<>();
-
         @Override
-        public void save(ByteString token) {
+        public void save(ParametersBag session, ByteString token) {
+            Queue<ByteString> tokens = (Queue<ByteString>) get(session);
+            if (tokens == null) {
+                tokens = new LinkedList<>();
+                put(session, tokens);
+            }
             tokens.add(token);
         }
 
         @Override
-        public ByteString load() {
-            return tokens.poll();
+        public ByteString load(ParametersBag session) {
+            Queue<ByteString> tokens = (Queue<ByteString>) get(session);
+            return tokens == null ? null : tokens.poll();
         }
     }
 
     public static final class SingleTokenStore extends Store {
 
-        private ByteString token;
-
         @Override
-        public void save(ByteString token) {
-            this.token = token;
+        public void save(ParametersBag session, ByteString token) {
+            put(session, token);
         }
 
         @Override
-        public ByteString load() {
-            return token;
+        public ByteString load(ParametersBag session) {
+            return (ByteString) get(session);
         }
     }
 
@@ -157,6 +168,8 @@
 
     private final List<Injector> injectors = new ArrayList<>();
 
+    private Store tokenStore = new QueueStore();
+
     public HttpCsrfFilter() {
     }
 
@@ -203,10 +216,19 @@
         this.injectors.remove(injector);
     }
 
+    public Store getTokenStore() {
+        return tokenStore;
+    }
+
+    public void setTokenStore(Store tokenStore) {
+        this.tokenStore = tokenStore;
+    }
+
     @Override
     public void configure(Configuration cfg) {
         setExtractors((List<Extractor>) cfg.get("extract", Collections.EMPTY_LIST));
         setInjectors((List<Injector>) cfg.get("inject", Collections.EMPTY_LIST));
+        setTokenStore((Store) cfg.get("store", tokenStore));
     }
 
     @Override
@@ -214,10 +236,8 @@
         if (req != null) {
             ParametersBag session = context.scopes().getSession(req);
             if (session != null) {
-                Store store = (Store) session.get(SESSION_KEY);
-                if (store != null) {
-                    ByteString token = store.load();
-
+                ByteString token = tokenStore.load(session);
+                if (token != null) {
                     for (Injector injector : injectors) {
                         injector.inject(req, token);
                     }
@@ -242,14 +262,7 @@
             if (token != null) {
                 ParametersBag session = context.scopes().getSession(resp);
                 if (session != null) {
-                    Store store = (Store) session.get(SESSION_KEY);
-
-                    if (store == null) {
-                        store = new QueueStore();
-                        session.set(SESSION_KEY, store);
-                    }
-
-                    store.save(token);
+                    tokenStore.save(session, token);
                 }
             }
         }
@@ -275,9 +288,12 @@
                     .setNodeTransformer(new HttpCsrfFilterInjectorTransformer())
                     .add("header", valueDef().addValidator(HeaderNameValidator.INSTANCE));
 
+            ValueNodeDefinition storeDef = valueDef().setTransformer(new HttpCsrfFilterStoreTransformer());
+
             return mapDef(
                     tupleDef("extract", extractorsDef),
-                    tupleDef("inject", injectorsDef)
+                    tupleDef("inject", injectorsDef),
+                    tupleDef("store", storeDef).setRequired(false)
             );
         }
 
--- a/stress-tester/src/main/java/com/passus/st/client/http/filter/HttpCsrfFilterExtractorTransformer.java	Fri Jul 28 15:54:52 2017 +0200
+++ b/stress-tester/src/main/java/com/passus/st/client/http/filter/HttpCsrfFilterExtractorTransformer.java	Mon Jul 31 09:19:32 2017 +0200
@@ -19,7 +19,7 @@
  *
  * @author Mirosław Hawrot
  */
-public class HttpCsrfFilterExtractorTransformer implements NodeTransformer {
+class HttpCsrfFilterExtractorTransformer implements NodeTransformer {
 
     private Extractor createNameExtractor(CTupleNode tuple, Errors errors, Class<? extends Extractor> clazz) {
         if (validateType(tuple.getNode(), NodeType.VALUE, errors)) {
--- a/stress-tester/src/main/java/com/passus/st/client/http/filter/HttpCsrfFilterInjectorTransformer.java	Fri Jul 28 15:54:52 2017 +0200
+++ b/stress-tester/src/main/java/com/passus/st/client/http/filter/HttpCsrfFilterInjectorTransformer.java	Mon Jul 31 09:19:32 2017 +0200
@@ -18,7 +18,7 @@
  *
  * @author Mirosław Hawrot
  */
-public class HttpCsrfFilterInjectorTransformer implements NodeTransformer {
+class HttpCsrfFilterInjectorTransformer implements NodeTransformer {
 
     private Injector createNameExtractor(CTupleNode tuple, Errors errors, Class<? extends Injector> clazz) {
         if (validateType(tuple.getNode(), NodeType.VALUE, errors)) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stress-tester/src/main/java/com/passus/st/client/http/filter/HttpCsrfFilterStoreTransformer.java	Mon Jul 31 09:19:32 2017 +0200
@@ -0,0 +1,33 @@
+package com.passus.st.client.http.filter;
+
+import com.passus.commons.ConversionException;
+import com.passus.config.ValueTransformer;
+
+/**
+ *
+ * @author mikolaj.podbielski
+ */
+class HttpCsrfFilterStoreTransformer implements ValueTransformer {
+
+    @Override
+    public Object transform(Object obj) throws ConversionException {
+        if (obj instanceof String) {
+            String s = (String) obj;
+            switch (s) {
+                case "queue":
+                    return new HttpCsrfFilter.QueueStore();
+                case "single":
+                    return new HttpCsrfFilter.SingleTokenStore();
+                default:
+                    throw new ConversionException("Unknown token store type: " + s);
+            }
+        }
+        throw new ConversionException("Invalid token store type.");
+    }
+
+    @Override
+    public Object reverseTransform(Object obj) throws ConversionException {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+}
--- a/stress-tester/src/test/java/com/passus/st/client/http/filter/HttpCsrfFilterTest.java	Fri Jul 28 15:54:52 2017 +0200
+++ b/stress-tester/src/test/java/com/passus/st/client/http/filter/HttpCsrfFilterTest.java	Mon Jul 31 09:19:32 2017 +0200
@@ -1,11 +1,13 @@
 package com.passus.st.client.http.filter;
 
 import com.passus.config.validation.Errors;
+import com.passus.data.ByteString;
 import com.passus.net.http.HttpMessage;
 import com.passus.net.http.HttpRequest;
 import com.passus.net.http.HttpRequestBuilder;
 import com.passus.net.http.HttpResponse;
 import com.passus.net.http.HttpResponseBuilder;
+import com.passus.st.ParametersBag;
 import static com.passus.st.client.http.HttpConsts.TAG_SESSION_ID;
 import com.passus.st.client.http.HttpFlowContext;
 import com.passus.st.client.http.HttpScopes;
@@ -14,6 +16,9 @@
 import com.passus.st.client.http.filter.HttpCsrfFilter.HeaderExtractor;
 import com.passus.st.client.http.filter.HttpCsrfFilter.HeaderInjector;
 import com.passus.st.client.http.filter.HttpCsrfFilter.Injector;
+import com.passus.st.client.http.filter.HttpCsrfFilter.QueueStore;
+import com.passus.st.client.http.filter.HttpCsrfFilter.SingleTokenStore;
+import com.passus.st.client.http.filter.HttpCsrfFilter.Store;
 import com.passus.st.emitter.SessionInfo;
 import java.util.List;
 import static org.testng.AssertJUnit.assertEquals;
@@ -31,7 +36,69 @@
     }
 
     @Test
-    public void testFilter_CookieExtractorHeaderInjector() throws Exception {
+    public void testCookieExtractor() {
+        CookieExtractor extractor = new CookieExtractor("x_csrf_token");
+        HttpResponse resp = HttpResponseBuilder.ok()
+                .cookie("x_csrf_token", "value1")
+                .build();
+
+        ByteString value = extractor.extract(resp);
+        assertEquals("value1", value.toString());
+    }
+
+    @Test
+    public void testHeaderExtractor() {
+        HeaderExtractor extractor = new HeaderExtractor("x-csrf-token");
+        HttpResponse resp = HttpResponseBuilder.ok()
+                .header("x-csrf-token", "value1")
+                .build();
+
+        ByteString value = extractor.extract(resp);
+        assertEquals("value1", value.toString());
+    }
+
+    @Test
+    public void testHeaderInjector() {
+        HeaderInjector injector = new HeaderInjector("x-csrf-token");
+        HttpRequest req = HttpRequestBuilder.get("http://test/test1")
+                .header("x-csrf-token", "oldValue")
+                .build();
+
+        injector.inject(req, ByteString.create("newValue"));
+        assertEquals("newValue", req.getHeaders().get("x-csrf-token").toString());
+    }
+
+    @Test
+    public void testQueueStore() {
+        QueueStore store = new QueueStore();
+        ParametersBag session = new ParametersBag();
+
+        assertTrue(store.load(session) == null);
+
+        store.save(session, ByteString.create("token1"));
+        store.save(session, ByteString.create("token2"));
+        assertEquals("token1", store.load(session).toString());
+        assertEquals("token2", store.load(session).toString());
+        assertTrue(store.load(session) == null);
+    }
+
+    @Test
+    public void testSingleTokenStore() {
+        SingleTokenStore store = new SingleTokenStore();
+        ParametersBag session = new ParametersBag();
+
+        assertTrue(store.load(session) == null);
+
+        store.save(session, ByteString.create("token1"));
+        assertEquals("token1", store.load(session).toString());
+        assertEquals("token1", store.load(session).toString());
+
+        store.save(session, ByteString.create("token2"));
+        assertEquals("token2", store.load(session).toString());
+    }
+
+    @Test
+    public void testFilter_CookieExtractorHeaderInjectorQueueStore() throws Exception {
         HttpRequest req1 = HttpRequestBuilder.get("http://test/test1")
                 .header("x-csrf-token", "token")
                 .build();
@@ -58,6 +125,7 @@
         HttpCsrfFilter filter = new HttpCsrfFilter();
         filter.addExtractor(cookieExtractor);
         filter.addInjector(headerInjector);
+        filter.setTokenStore(new QueueStore());
 
         filter.filterOutbound(req1, null, context);
         filter.filterInbound(null, resp1, context);
@@ -75,7 +143,8 @@
                 + "        header: \"csrf-header\"\n"
                 + "        cookie: \"csrf-cookie\"\n"
                 + "      inject:\n"
-                + "        header: \"csrf-header-inject\"\n";
+                + "        header: \"csrf-header-inject\"\n"
+                + "      store: single\n";
 
         Errors errors = new Errors();
         List<HttpFilter> filters = HttpFiltersConfigurator.getFilters(filterConfig, errors);
@@ -85,6 +154,10 @@
         assertTrue(filters.get(0) instanceof HttpCsrfFilter);
 
         HttpCsrfFilter filter = (HttpCsrfFilter) filters.get(0);
+
+        Store tokenStore = filter.getTokenStore();
+        assertTrue(tokenStore instanceof SingleTokenStore);
+
         List<Extractor> extractors = filter.getExtractors();
         List<Injector> injectors = filter.getInjectors();