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 <[email protected]> Authored: Fri Apr 11 11:25:08 2014 +0200 Committer: gpetracek <[email protected]> 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>
