Skip to content

Commit 5a9fd65

Browse files
committed
fix for GRAILS-5921 "Enhance withForm to allow for forms on multiple tabs"
1 parent e4013a7 commit 5a9fd65

File tree

3 files changed

+65
-8
lines changed

3 files changed

+65
-8
lines changed

grails-plugin-controllers/src/main/groovy/org/codehaus/groovy/grails/web/metaclass/WithFormMethod.groovy

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,11 @@ class WithFormMethod {
8989
final request = webRequest.getCurrentRequest()
9090
SynchronizerTokensHolder tokensHolderInSession = request.getSession(false)?.getAttribute(SynchronizerTokensHolder.HOLDER)
9191
String urlInRequest = webRequest.params[SynchronizerTokensHolder.TOKEN_URI]
92+
String tokenInRequest = webRequest.params[SynchronizerTokensHolder.TOKEN_KEY]
9293

93-
tokensHolderInSession.resetToken(urlInRequest)
94+
if (urlInRequest && tokenInRequest) {
95+
tokensHolderInSession.resetToken(urlInRequest, tokenInRequest)
96+
}
9497
if (tokensHolderInSession.isEmpty()) request.getSession(false)?.removeAttribute(SynchronizerTokensHolder.HOLDER)
9598
}
9699
}

grails-test-suite-uber/src/test/groovy/org/codehaus/groovy/grails/web/metaclass/WithFormMethodTests.groovy

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,4 +242,47 @@ class WithFormMethodTests extends GroovyTestCase {
242242
assertEquals "bar", result2.foo
243243
}
244244

245+
void testHandleSubmitOfTwoFormsWithSameURL() {
246+
def withForm = new WithFormMethod()
247+
def url1 = "http://grails.org/submit"
248+
def url2 = "http://grails.org/submit"
249+
250+
SynchronizerTokensHolder tokensHolder = new SynchronizerTokensHolder()
251+
def token1 = tokensHolder.generateToken(url1)
252+
def token2 = tokensHolder.generateToken(url2)
253+
254+
def request1 = GrailsWebUtil.bindMockWebRequest()
255+
request1.session.setAttribute(SynchronizerTokensHolder.HOLDER,tokensHolder)
256+
request1.currentRequest.addParameter(SynchronizerTokensHolder.TOKEN_URI,url1)
257+
request1.currentRequest.addParameter(SynchronizerTokensHolder.TOKEN_KEY,token1)
258+
259+
def result1 = withForm.withForm(request1) {
260+
return [foo:"bar"]
261+
}.invalidToken {
262+
throw new GrailsRuntimeException("invalid token")
263+
}
264+
265+
assertEquals "bar", result1.foo
266+
267+
def request2 = GrailsWebUtil.bindMockWebRequest()
268+
request2.session.setAttribute(SynchronizerTokensHolder.HOLDER,tokensHolder)
269+
request2.currentRequest.addParameter(SynchronizerTokensHolder.TOKEN_URI,url2)
270+
request2.currentRequest.addParameter(SynchronizerTokensHolder.TOKEN_KEY,token2)
271+
272+
def result2 = withForm.withForm(request2) {
273+
return [foo:"bar"]
274+
}.invalidToken {
275+
throw new GrailsRuntimeException("invalid token")
276+
}
277+
278+
assertEquals "bar", result2.foo
279+
280+
shouldFail(GrailsRuntimeException) {
281+
withForm.withForm(request2) {
282+
return [foo:"bar"]
283+
}.invalidToken {
284+
throw new GrailsRuntimeException("invalid token")
285+
}
286+
}
287+
}
245288
}

grails-web/src/main/groovy/org/codehaus/groovy/grails/web/servlet/mvc/SynchronizerTokensHolder.groovy

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package org.codehaus.groovy.grails.web.servlet.mvc
1616

1717
import javax.servlet.http.HttpSession
18+
import java.util.concurrent.CopyOnWriteArraySet
1819

1920
/**
2021
* A token used to handle double-submits.
@@ -29,28 +30,38 @@ class SynchronizerTokensHolder implements Serializable {
2930
public static final String TOKEN_KEY = "org.codehaus.groovy.grails.SYNCHRONIZER_TOKEN"
3031
public static final String TOKEN_URI = "org.codehaus.groovy.grails.SYNCHRONIZER_URI"
3132

32-
Map<String, UUID> currentTokens;
33+
Map<String, Set<UUID>> currentTokens= [:].withDefault { new CopyOnWriteArraySet<UUID>() };
3334

3435
SynchronizerTokensHolder() {
35-
// generateToken(url)
36-
currentTokens = new HashMap<String, UUID>();
3736
}
3837

3938
boolean isValid(String url, String token) {
40-
currentTokens[url]?.equals(UUID.fromString(token))
39+
final uuid = UUID.fromString(token)
40+
currentTokens[url]?.contains(uuid)
4141
}
4242

4343
String generateToken(String url) {
44-
currentTokens[url] = UUID.randomUUID()
45-
return currentTokens[url]
44+
final uuid = UUID.randomUUID()
45+
currentTokens[url].add(uuid)
46+
return uuid
4647
}
4748

4849
void resetToken(String url) {
4950
currentTokens.remove(url)
5051
}
5152

53+
void resetToken(String url, String token) {
54+
if (url && token) {
55+
final set = currentTokens[url]
56+
set.remove(UUID.fromString(token))
57+
if (set.isEmpty()) {
58+
currentTokens.remove(url)
59+
}
60+
}
61+
}
62+
5263
boolean isEmpty() {
53-
return currentTokens.isEmpty()
64+
return currentTokens.isEmpty() || currentTokens.every { String url, Set<UUID> uuids -> uuids.isEmpty() }
5465
}
5566

5667
static SynchronizerTokensHolder store(HttpSession session) {

0 commit comments

Comments
 (0)