You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@deltaspike.apache.org by gp...@apache.org on 2014/04/11 11:38:06 UTC

git commit: DELTASPIKE-564 optional double submit prevention

Repository: deltaspike
Updated Branches:
  refs/heads/master 4399428cd -> 697db84aa


DELTASPIKE-564 optional double submit prevention


Project: http://git-wip-us.apache.org/repos/asf/deltaspike/repo
Commit: http://git-wip-us.apache.org/repos/asf/deltaspike/commit/697db84a
Tree: http://git-wip-us.apache.org/repos/asf/deltaspike/tree/697db84a
Diff: http://git-wip-us.apache.org/repos/asf/deltaspike/diff/697db84a

Branch: refs/heads/master
Commit: 697db84aaf7a460f6d9d596e7ffa17830e61fcba
Parents: 4399428
Author: gpetracek <gp...@apache.org>
Authored: Fri Apr 11 11:25:08 2014 +0200
Committer: gpetracek <gp...@apache.org>
Committed: Fri Apr 11 11:34:13 2014 +0200

----------------------------------------------------------------------
 .../jsf/api/config/JsfModuleConfig.java         |  5 ++
 .../token/PostRequestTokenComponent.java        | 57 ++++++++++++
 .../token/RequestTokenHtmlRenderer.java         | 84 +++++++++++++++++
 .../token/DoubleSubmitAwarePhaseListener.java   | 95 ++++++++++++++++++++
 .../jsf/impl/token/PostRequestTokenManager.java | 70 +++++++++++++++
 .../jsf/impl/token/PostRequestTokenMarker.java  | 27 ++++++
 .../resources/META-INF/deltaspike.taglib.xml    |  7 ++
 7 files changed, 345 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/deltaspike/blob/697db84a/deltaspike/modules/jsf/api/src/main/java/org/apache/deltaspike/jsf/api/config/JsfModuleConfig.java
----------------------------------------------------------------------
diff --git a/deltaspike/modules/jsf/api/src/main/java/org/apache/deltaspike/jsf/api/config/JsfModuleConfig.java b/deltaspike/modules/jsf/api/src/main/java/org/apache/deltaspike/jsf/api/config/JsfModuleConfig.java
index e8077d7..c6436a2 100644
--- a/deltaspike/modules/jsf/api/src/main/java/org/apache/deltaspike/jsf/api/config/JsfModuleConfig.java
+++ b/deltaspike/modules/jsf/api/src/main/java/org/apache/deltaspike/jsf/api/config/JsfModuleConfig.java
@@ -128,6 +128,11 @@ public class JsfModuleConfig implements DeltaSpikeConfig
         return Default.class;
     }
 
+    public boolean isAllowPostRequestWithoutDoubleSubmitPrevention()
+    {
+        return true;
+    }
+
     protected boolean isDelegatedWindowHandlingEnabled()
     {
         if (ClassUtils.tryToLoadClassForName(CLIENT_WINDOW_CLASS_NAME) == null)

http://git-wip-us.apache.org/repos/asf/deltaspike/blob/697db84a/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/component/token/PostRequestTokenComponent.java
----------------------------------------------------------------------
diff --git a/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/component/token/PostRequestTokenComponent.java b/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/component/token/PostRequestTokenComponent.java
new file mode 100644
index 0000000..0a8bba7
--- /dev/null
+++ b/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/component/token/PostRequestTokenComponent.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.deltaspike.jsf.impl.component.token;
+
+import org.apache.deltaspike.jsf.impl.token.PostRequestTokenMarker;
+
+import javax.faces.component.FacesComponent;
+import javax.faces.component.UIInput;
+
+
+/**
+ * Component for rendering the post-request-token
+ */
+@FacesComponent(PostRequestTokenComponent.COMPONENT_TYPE)
+public class PostRequestTokenComponent extends UIInput
+{
+    public static final String COMPONENT_TYPE = "org.apache.deltaspike.PostRequestTokenHolder";
+
+    private transient String markedId;
+
+    @Override
+    public String getId()
+    {
+        if (this.markedId == null)
+        {
+            String originalId = super.getId();
+
+            if (originalId.contains(PostRequestTokenMarker.POST_REQUEST_TOKEN_KEY))
+            {
+                this.markedId = originalId;
+            }
+            else
+            {
+                this.markedId = originalId + "_" + PostRequestTokenMarker.POST_REQUEST_TOKEN_KEY;
+            }
+        }
+        return this.markedId;
+    }
+
+    //don't use #restoreState - we couldn't support stateless views,...
+}

http://git-wip-us.apache.org/repos/asf/deltaspike/blob/697db84a/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/component/token/RequestTokenHtmlRenderer.java
----------------------------------------------------------------------
diff --git a/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/component/token/RequestTokenHtmlRenderer.java b/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/component/token/RequestTokenHtmlRenderer.java
new file mode 100644
index 0000000..90ce38f
--- /dev/null
+++ b/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/component/token/RequestTokenHtmlRenderer.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.deltaspike.jsf.impl.component.token;
+
+import org.apache.deltaspike.core.api.provider.BeanProvider;
+import org.apache.deltaspike.jsf.impl.token.PostRequestTokenManager;
+
+import javax.faces.component.UIComponent;
+import javax.faces.context.FacesContext;
+import javax.faces.context.ResponseWriter;
+import javax.faces.render.FacesRenderer;
+import javax.faces.render.Renderer;
+import java.io.IOException;
+
+@FacesRenderer(
+    componentFamily = PostRequestTokenComponent.COMPONENT_FAMILY,
+    rendererType = PostRequestTokenComponent.COMPONENT_TYPE)
+public class RequestTokenHtmlRenderer extends Renderer
+{
+    private static final String INPUT_ELEMENT = "input";
+    private static final String TYPE_ATTRIBUTE = "type";
+    private static final String INPUT_TYPE_HIDDEN = "hidden";
+
+    private static final String ID_ATTRIBUTE = "id";
+    private static final String NAME_ATTRIBUTE = "name";
+    private static final String VALUE_ATTRIBUTE = "value";
+
+    private volatile PostRequestTokenManager postRequestTokenManager;
+
+    @Override
+    public void encodeBegin(FacesContext facesContext, UIComponent component) throws IOException
+    {
+        ResponseWriter writer = facesContext.getResponseWriter();
+
+        writer.startElement(INPUT_ELEMENT, component);
+        writer.writeAttribute(TYPE_ATTRIBUTE, INPUT_TYPE_HIDDEN, null);
+
+        String clientId = component.getClientId(facesContext);
+        writer.writeAttribute(ID_ATTRIBUTE, clientId, null);
+        writer.writeAttribute(NAME_ATTRIBUTE, clientId, null);
+
+        String currentPostRequestToken = getPostRequestTokenManager().getCurrentToken();
+        if (currentPostRequestToken != null)
+        {
+            writer.writeAttribute(VALUE_ATTRIBUTE, currentPostRequestToken, VALUE_ATTRIBUTE);
+        }
+
+        writer.endElement(INPUT_ELEMENT);
+    }
+
+    //don't use #decode - we couldn't support DSP for immediate actions
+
+    private PostRequestTokenManager getPostRequestTokenManager()
+    {
+        if (this.postRequestTokenManager == null)
+        {
+            synchronized (this)
+            {
+                if (this.postRequestTokenManager == null)
+                {
+                    this.postRequestTokenManager = BeanProvider.getContextualReference(PostRequestTokenManager.class);
+                }
+            }
+        }
+
+        return this.postRequestTokenManager;
+    }
+}

http://git-wip-us.apache.org/repos/asf/deltaspike/blob/697db84a/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/token/DoubleSubmitAwarePhaseListener.java
----------------------------------------------------------------------
diff --git a/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/token/DoubleSubmitAwarePhaseListener.java b/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/token/DoubleSubmitAwarePhaseListener.java
new file mode 100644
index 0000000..0eab5a6
--- /dev/null
+++ b/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/token/DoubleSubmitAwarePhaseListener.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.deltaspike.jsf.impl.token;
+
+import org.apache.deltaspike.core.spi.activation.Deactivatable;
+import org.apache.deltaspike.jsf.api.listener.phase.JsfPhaseListener;
+
+import javax.faces.context.FacesContext;
+import javax.faces.event.PhaseEvent;
+import javax.faces.event.PhaseId;
+import javax.faces.event.PhaseListener;
+import javax.inject.Inject;
+import java.util.Map;
+
+//ignore jsf-ajax requests since they have to be queued according to the spec.
+//ignore get-requests since they >shouldn't< change the state (we couldn't support them at all)
+//post-requests don't get pipelined -> no need to sync. them per session
+//browser-window-handling is done implicitly (PostRequestTokenManager is window-scoped)
+@JsfPhaseListener(ordinal = 9000)
+public class DoubleSubmitAwarePhaseListener implements PhaseListener, Deactivatable
+{
+    private static final long serialVersionUID = -4247051429332418226L;
+
+    @Inject
+    private PostRequestTokenManager postRequestTokenManager;
+
+    @Override
+    public void afterPhase(PhaseEvent event)
+    {
+        FacesContext facesContext = event.getFacesContext();
+
+        //only check full POST requests
+        if (facesContext.isPostback() && !facesContext.getPartialViewContext().isAjaxRequest())
+        {
+            String receivedPostRequestToken = facesContext.getExternalContext()
+                .getRequestParameterMap().get(PostRequestTokenMarker.POST_REQUEST_TOKEN_KEY);
+
+            if (receivedPostRequestToken == null)
+            {
+                receivedPostRequestToken = findPostRequestTokenWithPrefix(facesContext);
+            }
+
+            if (!this.postRequestTokenManager.isValidRequest(receivedPostRequestToken))
+            {
+                facesContext.renderResponse();
+            }
+        }
+    }
+
+    @Override
+    public void beforePhase(PhaseEvent event)
+    {
+        //refresh the token in case of GET-requests to avoid that the token is re-used on the next page
+        if (!event.getFacesContext().isPostback())
+        {
+            this.postRequestTokenManager.createNewToken();
+        }
+    }
+
+    @Override
+    public PhaseId getPhaseId()
+    {
+        return PhaseId.RESTORE_VIEW;
+    }
+
+    protected String findPostRequestTokenWithPrefix(FacesContext facesContext)
+    {
+        for (Map.Entry<String, String> parameterEntry :
+            facesContext.getExternalContext().getRequestParameterMap().entrySet())
+        {
+            if (parameterEntry.getKey().endsWith(PostRequestTokenMarker.POST_REQUEST_TOKEN_WITH_PREFIX_KEY) ||
+                parameterEntry.getKey().endsWith(PostRequestTokenMarker.POST_REQUEST_TOKEN_WITH_MANUAL_PREFIX_KEY))
+            {
+                return parameterEntry.getValue();
+            }
+        }
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/deltaspike/blob/697db84a/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/token/PostRequestTokenManager.java
----------------------------------------------------------------------
diff --git a/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/token/PostRequestTokenManager.java b/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/token/PostRequestTokenManager.java
new file mode 100644
index 0000000..d9f9e9b
--- /dev/null
+++ b/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/token/PostRequestTokenManager.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.deltaspike.jsf.impl.token;
+
+import org.apache.deltaspike.core.api.scope.WindowScoped;
+import org.apache.deltaspike.jsf.api.config.JsfModuleConfig;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import java.io.Serializable;
+import java.util.UUID;
+
+@WindowScoped
+@Named("dsPostRequestToken")
+public class PostRequestTokenManager implements Serializable
+{
+    private static final long serialVersionUID = 5387627547198129897L;
+
+    private volatile String currentToken;
+
+    private boolean allowPostRequestWithoutDoubleSubmitPrevention = true;
+
+    protected PostRequestTokenManager()
+    {
+    }
+
+    @Inject
+    public PostRequestTokenManager(JsfModuleConfig config)
+    {
+        this.allowPostRequestWithoutDoubleSubmitPrevention = config.isAllowPostRequestWithoutDoubleSubmitPrevention();
+    }
+
+    public void createNewToken()
+    {
+        this.currentToken = UUID.randomUUID().toString().replace("-", "");
+    }
+
+    public synchronized boolean isValidRequest(String token)
+    {
+        if (token == null)
+        {
+            return this.allowPostRequestWithoutDoubleSubmitPrevention;
+        }
+        String previousToken = this.currentToken;
+        createNewToken();
+
+        return token.equals(previousToken);
+    }
+
+    public String getCurrentToken()
+    {
+        return this.currentToken;
+    }
+}

http://git-wip-us.apache.org/repos/asf/deltaspike/blob/697db84a/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/token/PostRequestTokenMarker.java
----------------------------------------------------------------------
diff --git a/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/token/PostRequestTokenMarker.java b/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/token/PostRequestTokenMarker.java
new file mode 100644
index 0000000..4eb3109
--- /dev/null
+++ b/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/token/PostRequestTokenMarker.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.deltaspike.jsf.impl.token;
+
+public interface PostRequestTokenMarker
+{
+    String POST_REQUEST_TOKEN_KEY = "dsprt";
+
+    String POST_REQUEST_TOKEN_WITH_PREFIX_KEY = ":" + POST_REQUEST_TOKEN_KEY;
+    String POST_REQUEST_TOKEN_WITH_MANUAL_PREFIX_KEY = "_" + POST_REQUEST_TOKEN_KEY;
+}

http://git-wip-us.apache.org/repos/asf/deltaspike/blob/697db84a/deltaspike/modules/jsf/impl/src/main/resources/META-INF/deltaspike.taglib.xml
----------------------------------------------------------------------
diff --git a/deltaspike/modules/jsf/impl/src/main/resources/META-INF/deltaspike.taglib.xml b/deltaspike/modules/jsf/impl/src/main/resources/META-INF/deltaspike.taglib.xml
index b1cfc44..68cfe58 100644
--- a/deltaspike/modules/jsf/impl/src/main/resources/META-INF/deltaspike.taglib.xml
+++ b/deltaspike/modules/jsf/impl/src/main/resources/META-INF/deltaspike.taglib.xml
@@ -37,4 +37,11 @@
             <renderer-type>org.apache.deltaspike.DisableClientWindow</renderer-type>
         </component>
     </tag>
+    <tag>
+        <tag-name>preventDoubleSubmit</tag-name>
+        <component>
+            <component-type>org.apache.deltaspike.PostRequestTokenHolder</component-type>
+            <renderer-type>org.apache.deltaspike.PostRequestTokenHolder</renderer-type>
+        </component>
+    </tag>
 </facelet-taglib>