diff --git a/.github/workflows/github-actions-build.yml b/.github/workflows/github-actions-build.yml index 0dc721a12..05d03519e 100644 --- a/.github/workflows/github-actions-build.yml +++ b/.github/workflows/github-actions-build.yml @@ -48,7 +48,7 @@ jobs: echo "Sonar secure variables NOT available" else echo "Sonar secure variables ARE available" - mvn -B sonar:sonar -Dsonar.projectKey="bordertech-wcomponents" -Dsonar.organization="bordertech-github" -Dsonar.host.url="https://sonarcloud.io" + mvn -B org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey="bordertech-wcomponents" -Dsonar.organization="bordertech-github" -Dsonar.host.url="https://sonarcloud.io" fi env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 32ec31917..a59cd4c1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ ### API Changes ### Enhancements + +* To improve the robustness of the session token parameter (wc_t), which is used to prevent CSRF attacks, the following changes have been made: + * The session token is no longer included on any GET URLs and only posted in the body for POSTS. + * Modified the session token interceptors to only accept a session token on a POST and throw an exception if provided on a GET. + * Modified Targetable components to use the new createTargetUrl method in WebUtilites that centralises the logic for + creating the URLs for Targetable components and excludes the session token. + * Moved the adding of the hidden parameters onto the AJAX url from the XSL into the WApplicationRenderer so the session + token can be excluded. + ### Bug Fixes ## 1.5.37 diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WAudio.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WAudio.java index e60e3d732..68fcc8efe 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WAudio.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WAudio.java @@ -2,6 +2,7 @@ import com.github.bordertech.wcomponents.util.Util; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; @@ -324,29 +325,11 @@ public String[] getAudioUrls() { } String[] urls = new String[audio.length]; - - Environment env = getEnvironment(); - Map parameters = env.getHiddenParameters(); - parameters.put(Environment.TARGET_ID, getTargetId()); - - if (Util.empty(getCacheKey())) { - // Add some randomness to the URL to prevent caching - String random = WebUtilities.generateRandom(); - parameters.put(Environment.UNIQUE_RANDOM_PARAM, random); - } else { - // Remove step counter as not required for cached content - parameters.remove(Environment.STEP_VARIABLE); - parameters.remove(Environment.SESSION_TOKEN_VARIABLE); - // Add the cache key - parameters.put(Environment.CONTENT_CACHE_KEY, getCacheKey()); - } - - // this variable needs to be set in the portlet environment. - String url = env.getWServletPath(); - + String cacheKey = getCacheKey(); + Map parameters = new HashMap<>(); for (int i = 0; i < urls.length; i++) { parameters.put(AUDIO_INDEX_REQUEST_PARAM_KEY, String.valueOf(i)); - urls[i] = WebUtilities.getPath(url, parameters, true); + urls[i] = WebUtilities.createTargetUrl(this, cacheKey, parameters); } return urls; @@ -439,7 +422,6 @@ public boolean isRenderControls() { return getComponentModel().renderControls; } - /** * Sets whether the browser should render the default controls. The default is true. * @param renderControls if true then the controls are rendered diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WContent.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WContent.java index 2167a6b06..6c43003bf 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WContent.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WContent.java @@ -1,7 +1,6 @@ package com.github.bordertech.wcomponents; import com.github.bordertech.wcomponents.util.Util; -import java.util.Map; /** *

@@ -176,41 +175,19 @@ public String getUrl() { String mode = DisplayMode.PROMPT_TO_SAVE.equals(getDisplayMode()) ? "attach" : "inline"; + String url; // Check for a "static" resource if (content instanceof InternalResource) { - String url = ((InternalResource) content).getTargetUrl(); - // This magic parameter is a work-around to the loading indicator becoming - // "stuck" in certain browsers. - // It is also used by the static resource handler to set the correct headers - url = url + "&" + URL_CONTENT_MODE_PARAMETER_KEY + "=" + mode; - return url; - } - - Environment env = getEnvironment(); - Map parameters = env.getHiddenParameters(); - parameters.put(Environment.TARGET_ID, getTargetId()); - - if (Util.empty(getCacheKey())) { - // Add some randomness to the URL to prevent caching - String random = WebUtilities.generateRandom(); - parameters.put(Environment.UNIQUE_RANDOM_PARAM, random); + url = ((InternalResource) content).getTargetUrl(); } else { - // Remove step counter as not required for cached content - parameters.remove(Environment.STEP_VARIABLE); - parameters.remove(Environment.SESSION_TOKEN_VARIABLE); - // Add the cache key - parameters.put(Environment.CONTENT_CACHE_KEY, getCacheKey()); + url = WebUtilities.createTargetUrl(this, getCacheKey()); } - // This magic parameter is a work-around to the loading indicator becoming - // "stuck" in certain browsers. It is only read by the theme. - parameters.put(URL_CONTENT_MODE_PARAMETER_KEY, mode); - - // The targetable path needs to be configured for the portal environment. - String url = env.getWServletPath(); + // This magic parameter is a work-around to the loading indicator becoming "stuck" in certain browsers. + // It is also used by the static resource handler to set the correct headers + url = url + "&" + URL_CONTENT_MODE_PARAMETER_KEY + "=" + mode; - // Note the last parameter. In javascript we don't want to encode "&". - return WebUtilities.getPath(url, parameters, true); + return url; } /** diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WImage.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WImage.java index 2774887d5..386559d97 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WImage.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WImage.java @@ -2,7 +2,6 @@ import com.github.bordertech.wcomponents.util.Util; import java.awt.Dimension; -import java.util.Map; /** *

@@ -99,26 +98,7 @@ public String getTargetUrl() { return ((InternalResource) image).getTargetUrl(); } - Environment env = getEnvironment(); - Map parameters = env.getHiddenParameters(); - parameters.put(Environment.TARGET_ID, getTargetId()); - - if (Util.empty(getCacheKey())) { - // Add some randomness to the URL to prevent caching - String random = WebUtilities.generateRandom(); - parameters.put(Environment.UNIQUE_RANDOM_PARAM, random); - } else { - // Remove step counter as not required for cached content - parameters.remove(Environment.STEP_VARIABLE); - parameters.remove(Environment.SESSION_TOKEN_VARIABLE); - // Add the cache key - parameters.put(Environment.CONTENT_CACHE_KEY, getCacheKey()); - } - - // this variable needs to be set in the portlet environment. - String url = env.getWServletPath(); - - return WebUtilities.getPath(url, parameters, true); + return WebUtilities.createTargetUrl(this, getCacheKey()); } /** diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WMultiFileWidget.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WMultiFileWidget.java index c8319b6ad..6e22570d9 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WMultiFileWidget.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WMultiFileWidget.java @@ -14,6 +14,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -810,30 +811,10 @@ public String getFileUrl(final String fileId) { return null; } - Environment env = getEnvironment(); - Map parameters = env.getHiddenParameters(); - parameters.put(Environment.TARGET_ID, getTargetId()); - - if (Util.empty(file.getFileCacheKey())) { - // Add some randomness to the URL to prevent caching - String random = WebUtilities.generateRandom(); - parameters.put(Environment.UNIQUE_RANDOM_PARAM, random); - } else { - // Remove step counter as not required for cached content - parameters.remove(Environment.STEP_VARIABLE); - parameters.remove(Environment.SESSION_TOKEN_VARIABLE); - // Add the cache key - parameters.put(Environment.CONTENT_CACHE_KEY, file.getFileCacheKey()); - } - // File id + Map parameters = new HashMap<>(); parameters.put(FILE_UPLOAD_ID_KEY, fileId); - - // The targetable path needs to be configured for the portal environment. - String url = env.getWServletPath(); - - // Note the last parameter. In javascript we don't want to encode "&". - return WebUtilities.getPath(url, parameters, true); + return WebUtilities.createTargetUrl(this, file.getFileCacheKey(), parameters); } /** @@ -854,33 +835,12 @@ public String getFileThumbnailUrl(final String fileId) { return ((InternalResource) thumbnail).getTargetUrl(); } - Environment env = getEnvironment(); - Map parameters = env.getHiddenParameters(); - parameters.put(Environment.TARGET_ID, getTargetId()); - - if (Util.empty(file.getThumbnailCacheKey())) { - // Add some randomness to the URL to prevent caching - String random = WebUtilities.generateRandom(); - parameters.put(Environment.UNIQUE_RANDOM_PARAM, random); - } else { - // Remove step counter as not required for cached content - parameters.remove(Environment.STEP_VARIABLE); - parameters.remove(Environment.SESSION_TOKEN_VARIABLE); - // Add the cache key - parameters.put(Environment.CONTENT_CACHE_KEY, file.getThumbnailCacheKey()); - } - + Map parameters = new HashMap<>(); // File id parameters.put(FILE_UPLOAD_ID_KEY, fileId); - // Thumbnail flag parameters.put(FILE_UPLOAD_THUMB_NAIL_KEY, "Y"); - - // The targetable path needs to be configured for the portal environment. - String url = env.getWServletPath(); - - // Note the last parameter. In javascript we don't want to encode "&". - return WebUtilities.getPath(url, parameters, true); + return WebUtilities.createTargetUrl(this, file.getThumbnailCacheKey(), parameters); } /** diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WTree.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WTree.java index fc5b15248..33f9c4376 100644 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WTree.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WTree.java @@ -454,32 +454,10 @@ public String getItemImageUrl(final TreeItemImage item, final String itemId) { } // Build targetted url - Environment env = getEnvironment(); - Map parameters = env.getHiddenParameters(); - parameters.put(Environment.TARGET_ID, getTargetId()); - - String cacheKey = item.getImageCacheKey(); - - if (Util.empty(cacheKey)) { - // Add some randomness to the URL to prevent caching - String random = WebUtilities.generateRandom(); - parameters.put(Environment.UNIQUE_RANDOM_PARAM, random); - } else { - // Remove step counter as not required for cached content - parameters.remove(Environment.STEP_VARIABLE); - parameters.remove(Environment.SESSION_TOKEN_VARIABLE); - // Add the cache key - parameters.put(Environment.CONTENT_CACHE_KEY, cacheKey); - } - + Map parameters = new HashMap<>(); // Item id parameters.put(ITEM_REQUEST_KEY, itemId); - - // The targetable path needs to be configured for the portal environment. - url = env.getWServletPath(); - - // Note the last parameter. In javascript we don't want to encode "&". - return WebUtilities.getPath(url, parameters, true); + return WebUtilities.createTargetUrl(this, item.getImageCacheKey(), parameters); } /** diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WVideo.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WVideo.java index 38f908a0c..54a2db45a 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WVideo.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WVideo.java @@ -2,10 +2,10 @@ import com.github.bordertech.wcomponents.util.Util; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -446,14 +446,11 @@ public String[] getVideoUrls() { } String[] urls = new String[video.length]; - - // this variable needs to be set in the portlet environment. - String url = getEnvironment().getWServletPath(); - Map parameters = getBaseParameterMap(); - + String cacheKey = getCacheKey(); + Map parameters = new HashMap<>(); for (int i = 0; i < urls.length; i++) { parameters.put(VIDEO_INDEX_REQUEST_PARAM_KEY, String.valueOf(i)); - urls[i] = WebUtilities.getPath(url, parameters, true); + urls[i] = WebUtilities.createTargetUrl(this, cacheKey, parameters); } return urls; @@ -474,14 +471,11 @@ public String[] getTrackUrls() { } String[] urls = new String[tracks.length]; - - // this variable needs to be set in the portlet environment. - String url = getEnvironment().getWServletPath(); - Map parameters = getBaseParameterMap(); - + String cacheKey = getCacheKey(); + Map parameters = new HashMap<>(); for (int i = 0; i < urls.length; i++) { parameters.put(TRACK_INDEX_REQUEST_PARAM_KEY, String.valueOf(i)); - urls[i] = WebUtilities.getPath(url, parameters, true); + urls[i] = WebUtilities.createTargetUrl(this, cacheKey, parameters); } return urls; @@ -501,36 +495,9 @@ public String getPosterUrl() { return null; } - // this variable needs to be set in the portlet environment. - String url = getEnvironment().getWServletPath(); - Map parameters = getBaseParameterMap(); + Map parameters = new HashMap<>(); parameters.put(POSTER_REQUEST_PARAM_KEY, "x"); - return WebUtilities.getPath(url, parameters, true); - } - - /** - * Retrieves the base parameter map for serving content (videos + tracks). - * - * @return the base map for serving content. - */ - private Map getBaseParameterMap() { - Environment env = getEnvironment(); - Map parameters = env.getHiddenParameters(); - parameters.put(Environment.TARGET_ID, getTargetId()); - - if (Util.empty(getCacheKey())) { - // Add some randomness to the URL to prevent caching - String random = WebUtilities.generateRandom(); - parameters.put(Environment.UNIQUE_RANDOM_PARAM, random); - } else { - // Remove step counter as not required for cached content - parameters.remove(Environment.STEP_VARIABLE); - parameters.remove(Environment.SESSION_TOKEN_VARIABLE); - // Add the cache key - parameters.put(Environment.CONTENT_CACHE_KEY, getCacheKey()); - } - - return parameters; + return WebUtilities.createTargetUrl(this, getCacheKey(), parameters); } /** @@ -559,7 +526,6 @@ public boolean isVisible() { public void handleRequest(final Request request) { super.handleRequest(request); - String targ = request.getParameter(Environment.TARGET_ID); boolean contentReqested = (targ != null && targ.equals(getTargetId())); diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WWindow.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WWindow.java index c78c7d28b..309db3883 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WWindow.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WWindow.java @@ -351,6 +351,8 @@ public String getUrl() { parameters.put(WWINDOW_REQUEST_PARAM_KEY, getId()); // Override the step count with WWindow step parameters.put(Environment.STEP_VARIABLE, String.valueOf(getStep())); + // Remove session token as this should not be exposed on GET URLs (CSRF Rules) + parameters.remove(Environment.SESSION_TOKEN_VARIABLE); String url = env.getWServletPath(); diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WebUtilities.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WebUtilities.java index 512501bbe..be49eda44 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WebUtilities.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/WebUtilities.java @@ -428,6 +428,55 @@ public static String doubleDecodeBrackets(final String input) { return DOUBLE_DECODE_BRACKETS.translate(input); } + /** + * Create the URL for a targetable component. + * + * @param target the targetable component + * @param cacheKey the cacheKey or otherwise null + * @return the URL for the content of a targetable component + */ + public static String createTargetUrl(final Targetable target, final String cacheKey) { + return createTargetUrl(target, cacheKey, null); + } + + /** + * Create the URL for a targetable component with additional parameters. + * + * @param target the targetable component + * @param cacheKey the cacheKey or otherwise null + * @param additionalParams the additional parameters to include on url or otherwise null + * @return the URL for the content of a targetable component + */ + public static String createTargetUrl(final Targetable target, final String cacheKey, final Map additionalParams) { + + Environment env = target.getEnvironment(); + + Map parameters = env.getHiddenParameters(); + // Remove session token as this should not be exposed on GET URLs (CSRF Rules) + parameters.remove(Environment.SESSION_TOKEN_VARIABLE); + + // Add the target id + parameters.put(Environment.TARGET_ID, target.getTargetId()); + + if (Util.empty(cacheKey)) { + // Add some randomness to the URL to prevent caching + parameters.put(Environment.UNIQUE_RANDOM_PARAM, WebUtilities.generateRandom()); + } else { + // Add the cache key + parameters.put(Environment.CONTENT_CACHE_KEY, cacheKey); + // Remove step counter as not required for cached content + parameters.remove(Environment.STEP_VARIABLE); + } + + // Add additional parameters + if (additionalParams != null) { + parameters.putAll(additionalParams); + } + + // Build URL + return getPath(env.getWServletPath(), parameters, true); + } + /** * Adds GET parameters to a path. * @@ -449,6 +498,12 @@ public static String getPath(final String url, final Map paramet */ public static String getPath(final String url, final Map parameters, final boolean javascript) { + + // Check URL provided + if (url == null) { + throw new IllegalArgumentException("URL must be provided."); + } + // Have we already got some parameters? int index = url.indexOf('?'); boolean hasVars = false; diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/container/SessionTokenAjaxInterceptor.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/container/SessionTokenAjaxInterceptor.java index 36730e987..d0bcbc9c3 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/container/SessionTokenAjaxInterceptor.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/container/SessionTokenAjaxInterceptor.java @@ -5,6 +5,8 @@ import com.github.bordertech.wcomponents.UIContext; import com.github.bordertech.wcomponents.UIContextHolder; import com.github.bordertech.wcomponents.util.Util; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; /** * This session token interceptor makes sure the ajax request being processed is for the correct session. @@ -17,6 +19,11 @@ */ public class SessionTokenAjaxInterceptor extends InterceptorComponent { + /** + * The logger instance for this class. + */ + private static final Log LOG = LogFactory.getLog(SessionTokenAjaxInterceptor.class); + /** * Override to check whether the session token variable in the incoming request matches what we expect. * @@ -38,14 +45,19 @@ public void serviceRequest(final Request request) { // Get the session token from the AJAX request String got = request.getParameter(Environment.SESSION_TOKEN_VARIABLE); - // Check tokens match (both must be provided) - if (Util.equals(expected, got)) { + // Session token should not be provided on a GET URL (CSRF Rules) + if (got != null && "GET".equals(request.getMethod())) { + throw new IllegalStateException("A session token should not be provided on a GET"); + } + + // Check processing a GET or tokens must match + if ("GET".equals(request.getMethod()) || (got != null && Util.equals(expected, got))) { // Process AJAX request getBackingComponent().serviceRequest(request); } else { // Invalid token on AJAX request - throw new SessionTokenException("Wrong session token detected for AJAX request. Expected token [" - + expected + "] but got token [" + got + "]."); + LOG.debug("Wrong session token detected for AJAX request. Expected token [" + expected + "] but got token [" + got + "]."); + throw new SessionTokenException("Wrong session token detected for AJAX request."); } } diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/container/SessionTokenContentInterceptor.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/container/SessionTokenContentInterceptor.java index 6d35fc0a2..d12b4776f 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/container/SessionTokenContentInterceptor.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/container/SessionTokenContentInterceptor.java @@ -4,16 +4,9 @@ import com.github.bordertech.wcomponents.Request; import com.github.bordertech.wcomponents.UIContext; import com.github.bordertech.wcomponents.UIContextHolder; -import com.github.bordertech.wcomponents.WImage; -import com.github.bordertech.wcomponents.util.StepCountUtil; -import com.github.bordertech.wcomponents.util.Util; /** - * This session token interceptor makes sure the content request being processed is for the correct session. - *

- * Similar to {@link SessionTokenInterceptor} but caters for setting error codes for content requests such as - * {@link WImage} when a token error is detected. - *

+ * This session token interceptor makes sure the session token on content requests are handled correctly for CSRF. * * @author Jonathan Austin * @since 1.0.0 @@ -21,14 +14,14 @@ public class SessionTokenContentInterceptor extends InterceptorComponent { /** - * Override to check whether the session token variable in the incoming request matches what we expect. + * Override to check whether the session token is handled correctly for CSRF. * * @param request the request being serviced. */ @Override public void serviceRequest(final Request request) { - // Get the expected session token + // Get the current session token UIContext uic = UIContextHolder.getCurrent(); String expected = uic.getEnvironment().getSessionToken(); @@ -38,19 +31,19 @@ public void serviceRequest(final Request request) { + " Can be due to the session timing out."); } - // Get the session token from the content request - String got = request.getParameter(Environment.SESSION_TOKEN_VARIABLE); + // Content requests should only be a GET (CSRF Rules) + if (!"GET".equals(request.getMethod())) { + throw new IllegalStateException("Content request should only be a GET"); + } - // Check tokens match (both must be provided) or check if cached content (no session token on request) - if (Util.equals(expected, got) || (got == null && StepCountUtil.isCachedContentRequest(request))) { - // Process content request - getBackingComponent().serviceRequest(request); - } else { - // Invalid token on content request - throw new SessionTokenException("Wrong session token detected for content request. Expected token [" - + expected + "] but got token [" + got + "]."); + // Check no session token on the content request (CSRF Rules) + String got = request.getParameter(Environment.SESSION_TOKEN_VARIABLE); + if (got != null) { + throw new IllegalStateException("A session token should not be provided on a GET"); } + getBackingComponent().serviceRequest(request); + } } diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/container/SessionTokenInterceptor.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/container/SessionTokenInterceptor.java index 20430b516..737b2a613 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/container/SessionTokenInterceptor.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/container/SessionTokenInterceptor.java @@ -6,12 +6,13 @@ import com.github.bordertech.wcomponents.UIContextHolder; import com.github.bordertech.wcomponents.util.Util; import java.util.UUID; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; /** * This session token interceptor makes sure the request being processed is for the correct session. *

- * As the token is a UUID, it will be much harder for CSRF attacks. No request processing will occur without the correct - * UUID. + * As the token is a UUID, it will be much harder for CSRF attacks. No request processing will occur without the correct UUID. *

* * @author Jonathan Austin @@ -19,6 +20,11 @@ */ public class SessionTokenInterceptor extends InterceptorComponent { + /** + * The logger instance for this class. + */ + private static final Log LOG = LogFactory.getLog(SessionTokenInterceptor.class); + /** * Override to check whether the session token variable in the incoming request matches what we expect. * @@ -26,27 +32,31 @@ public class SessionTokenInterceptor extends InterceptorComponent { */ @Override public void serviceRequest(final Request request) { - // Get the expected session token + + // Get the expected session token (could be null for new session) UIContext uic = UIContextHolder.getCurrent(); String expected = uic.getEnvironment().getSessionToken(); // Get the session token from the request String got = request.getParameter(Environment.SESSION_TOKEN_VARIABLE); - // Check tokens match (Both null if new session) - // or processing a GET and no token - if (Util.equals(expected, got) || (got == null && "GET".equals(request.getMethod()))) { + // Session token should not be provided on a GET URL (CSRF Rules) + if (got != null && "GET".equals(request.getMethod())) { + throw new IllegalStateException("A session token should not be provided on a GET"); + } + + // Check processing a GET or tokens must match + if ("GET".equals(request.getMethod()) || (got != null && Util.equals(expected, got))) { // Process request getBackingComponent().serviceRequest(request); - } else { // Invalid token - String msg; - if (expected == null && got != null) { - msg = "Session for token [" + got + "] is no longer valid or timed out."; - } else { - msg = "Wrong session token detected for servlet request. Expected token [" + expected - + "] but got token [" + got + "]."; - } - throw new SessionTokenException(msg); + } else if (expected == null && got != null) { + // Expired token + LOG.debug("Session for token [" + got + "] is no longer valid or timed out."); + throw new SessionTokenException("Session for token is no longer valid or timed out."); + } else { + // Wrong token + LOG.debug("Wrong session token detected for servlet request. Expected token [" + expected + "] but got token [" + got + "]."); + throw new SessionTokenException("Wrong session token detected for servlet request."); } } diff --git a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/render/webxml/WApplicationRenderer.java b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/render/webxml/WApplicationRenderer.java index 082c812af..3fe4030bb 100755 --- a/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/render/webxml/WApplicationRenderer.java +++ b/wcomponents-core/src/main/java/com/github/bordertech/wcomponents/render/webxml/WApplicationRenderer.java @@ -1,10 +1,12 @@ package com.github.bordertech.wcomponents.render.webxml; +import com.github.bordertech.wcomponents.Environment; import com.github.bordertech.wcomponents.Renderer; import com.github.bordertech.wcomponents.UIContext; import com.github.bordertech.wcomponents.UIContextHolder; import com.github.bordertech.wcomponents.WApplication; import com.github.bordertech.wcomponents.WComponent; +import com.github.bordertech.wcomponents.WebUtilities; import com.github.bordertech.wcomponents.XmlStringBuilder; import com.github.bordertech.wcomponents.servlet.WebXmlRenderContext; import com.github.bordertech.wcomponents.util.TrackingUtil; @@ -45,11 +47,20 @@ public void doRender(final WComponent component, final WebXmlRenderContext rende LOG.warn("WApplication component should be the top level component."); } + // Build AJAX url (add hidden parameters that was previously added by XSL) + String ajaxUrl = uic.getEnvironment().getWServletPath(); + if (ajaxUrl != null) { + Map params = uic.getEnvironment().getHiddenParameters(); + // Dont add session token on URL (CSRF Rules) + params.remove(Environment.SESSION_TOKEN_VARIABLE); + ajaxUrl = WebUtilities.getPath(ajaxUrl, params, true); + } + xml.appendTagOpen("ui:application"); xml.appendAttribute("id", component.getId()); xml.appendOptionalAttribute("class", component.getHtmlClass()); xml.appendUrlAttribute("applicationUrl", uic.getEnvironment().getPostPath()); - xml.appendUrlAttribute("ajaxUrl", uic.getEnvironment().getWServletPath()); + xml.appendUrlAttribute("ajaxUrl", ajaxUrl); xml.appendOptionalAttribute("unsavedChanges", application.hasUnsavedChanges(), "true"); xml.appendOptionalAttribute("title", application.getTitle()); xml.appendOptionalAttribute("defaultFocusId", uic.isFocusRequired() && !Util.empty(focusId), diff --git a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/MockWEnvironment.java b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/MockWEnvironment.java index 123dffbc0..b42328efd 100755 --- a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/MockWEnvironment.java +++ b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/MockWEnvironment.java @@ -1,6 +1,6 @@ package com.github.bordertech.wcomponents; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; /** @@ -14,7 +14,7 @@ public class MockWEnvironment extends AbstractEnvironment { /** * The hidden parameters map. */ - private Map hiddenParameters = new HashMap<>(); + private Map hiddenParameters = new LinkedHashMap<>(); /** * Sets the post path. Overriden in order to make method public, as it's useful for unit testing. @@ -33,7 +33,8 @@ public void setPostPath(final String postPath) { */ @Override public Map getHiddenParameters() { - return hiddenParameters; + // Simulate behaviour to create new map each time + return new LinkedHashMap<>(hiddenParameters); } /** diff --git a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/SerializationPerformance_Test.java b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/SerializationPerformance_Test.java index ceea8ba06..edf1a790f 100755 --- a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/SerializationPerformance_Test.java +++ b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/SerializationPerformance_Test.java @@ -226,7 +226,6 @@ private void sendRequest(final WComponent comp, final UIContext uic) { PrintWriter writer = new PrintWriter(new NullWriter()); uic.setEnvironment(new WServlet.WServletEnvironment("", "http://localhost", "")); uic.setUI(comp); - InterceptorComponent root = ServletUtil.createInterceptorChain(new MockHttpServletRequest()); root.attachUI(comp); @@ -235,6 +234,7 @@ private void sendRequest(final WComponent comp, final UIContext uic) { setActiveContext(uic); MockRequest request = new MockRequest(); + request.setMethod("GET"); try { root.serviceRequest(request); diff --git a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/WebUtilities_Test.java b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/WebUtilities_Test.java index d4b8fd57d..80e9dde3e 100755 --- a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/WebUtilities_Test.java +++ b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/WebUtilities_Test.java @@ -7,11 +7,13 @@ import com.github.bordertech.wcomponents.util.mock.MockRequest; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import org.junit.Assert; import org.junit.AfterClass; +import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; @@ -118,124 +120,6 @@ public void testGetTop() { Assert.assertEquals("Incorrect top component returned for top", root, WebUtilities.getTop(root)); } - // @Test - // public void testGetWComponentPath() - // { - // // Simple test, one root element. - // WContainer root = new WContainer(); - // UIContext uic = new UIContextImpl(); - // uic.setUI(root); - // - // List path = WebUtilities.getWComponentPath(root, root.getId(), false); - // List expected = Arrays.asList(new WComponentPathElement[] - // { - // new WComponentPathElement(root) - // }); - // Assert.assertEquals("Incorrect path", expected, path); - // - // // Add a static child - // WContainer staticChild = new WContainer(); - // root.add(staticChild); - // - // path = WebUtilities.getWComponentPath(root, staticChild.getId(), false); - // expected = Arrays.asList(new WComponentPathElement[] - // { - // new WComponentPathElement(root), - // new WComponentPathElement(staticChild) - // }); - // Assert.assertEquals("Incorrect path", expected, path); - // - // // Add a dynamic child - // root.setLocked(true); - // setActiveContext(uic); - // WComponent dynamicChild = new DefaultWComponent(); - // staticChild.add( dynamicChild); - // - // path = WebUtilities.getWComponentPath(root, dynamicChild.getId(), false); - // expected = Arrays.asList(new WComponentPathElement[] - // { - // new WComponentPathElement(root), - // new WComponentPathElement(staticChild), - // new WComponentPathElement(dynamicChild), - // }); - // Assert.assertEquals("Incorrect path", expected, path); - // - // // Test against another context with strict - should not find dynamic child - // String dynamicChildId = dynamicChild.getId(); - // UIContext otherUic = new UIContextImpl(); - // otherUic.setUI(root); - // setActiveContext(otherUic); - // - // path = WebUtilities.getWComponentPath(root, dynamicChildId, false); - // Assert.assertNull("Path should not have been found", path); - // - // // Test against another context with tolerant - should return up to the static child - // path = WebUtilities.getWComponentPath(root, dynamicChildId, true); - // expected = Arrays.asList(new WComponentPathElement[] - // { - // new WComponentPathElement(root), - // new WComponentPathElement(staticChild) - // }); - // Assert.assertEquals("Incorrect path", expected, path); - // } - // - // @Test - // public void testGetWComponentPathWithRepeater() - // { - // WContainer root = new WContainer(); - // UIContext uic = new UIContextImpl(); - // uic.setUI(root); - // WRepeater repeater = new WRepeater(); - // WComponent repeatedComponent = new WText(); - // List data = new ArrayList(Arrays.asList(new String[] { "a", "b", "c" })); - // - // repeater.setRepeatedComponent(repeatedComponent); - // root.add(repeater); - // - // setActiveContext(uic); - // repeater.setData(data); - // List contexts = repeater.getRowContexts(); - // - // for (int i = 0; i < data.size(); i++) - // { - // UIContext rowContext = contexts.get(i); - // String repeatedComponentId = getComponentId(repeatedComponent, rowContext); - // - // List path = WebUtilities.getWComponentPath(root, repeatedComponentId, false); - // List expected = Arrays.asList(new WComponentPathElement[] - // { - // new WComponentPathElement(root), - // new WComponentPathElement(repeater, i), - // new WComponentPathElement(repeater.getRepeatRoot()), - // new WComponentPathElement(repeatedComponent) - // }); - // - // Assert.assertEquals("Incorrect path for row " + i, expected, path); - // } - // - // // Test when a row is removed from the repeater - // UIContext rowContext = contexts.get(contexts.size() - 1); - // data.remove(data.size() - 1); - // - // // Strict should return null - // UIContextHolder.pushContext(rowContext); - // String repeatedComponentId = getComponentId(repeatedComponent, rowContext); - // - // List path = WebUtilities.getWComponentPath(root, repeatedComponentId, false); - // Assert.assertNull("Path should not have been found in strict mode after row removal", path); - // - // // Tolerant should return up to the repeater - // path = WebUtilities.getWComponentPath(root, repeatedComponentId, true); - // List expected = Arrays.asList(new WComponentPathElement[] - // { - // new WComponentPathElement(root), - // new WComponentPathElement(repeater), - // new WComponentPathElement(repeater.getRepeatRoot()), - // new WComponentPathElement(repeatedComponent) - // }); - // - // Assert.assertEquals("Incorrect tolerant path after row removal", expected, path); - // } @Test public void testFindClosestContext() { WContainer root = new WContainer(); @@ -465,36 +349,53 @@ public void testDecode() { encoded)); } + @Test(expected = IllegalArgumentException.class) + public void testGetPathNullURL() { + // Should not allow null base URL + WebUtilities.getPath(null, Collections.emptyMap()); + } + @Test - public void testGetPath() { - // Simple case - String url = "/foo"; - String expected = "/foo"; - Assert.assertEquals("Incorrect path returned for " + url, expected, WebUtilities. - getPath(url, null)); + public void testGetPathSimple() { + String baseUrl = "/foo"; + String url = WebUtilities.getPath(baseUrl, null); + Assert.assertEquals("Incorrect path returned for base URL", baseUrl, url); + } - // Simple case with one param + @Test + public void testGetPathWithParameter() { + // Simple case with adding one param + String baseUrl = "/foo"; Map params = new HashMap<>(); params.put("a", "b"); + String url = WebUtilities.getPath(baseUrl, params); + String expected = "/foo?a=b"; + Assert.assertEquals("Incorrect path returned for base URL with one parameter", expected, url); + } - url = "/foo"; - expected = "/foo?a=b"; - Assert.assertEquals("Incorrect path returned for " + url + " with a=b", expected, - WebUtilities.getPath(url, params)); - + @Test + public void testGetPathWithExistingParameter() { // Case with existing params and two in the map - params = new HashMap<>(); + String baseUrl = "/foo?a=b"; + Map params = new LinkedHashMap<>(); params.put("c", "d"); params.put("e", "f"); + String url = WebUtilities.getPath(baseUrl, params); + String expected = "/foo?a=b&c=d&e=f"; + Assert.assertEquals("Incorrect path returned for base URL with existing parameters", expected, url); + } - url = "/foo?a=b"; - expected = "/foo?a=b&c=d&e=f"; - - assertURLEquals(expected, WebUtilities.getPath(url, params), "&"); - + @Test + public void testGetPathWithAsJavascriptURL() { + // Case with existing params and two in the map + String baseUrl = "/foo?a=b"; + Map params = new HashMap<>(); + params.put("c", "d"); + params.put("e", "f"); // As a javascript url - expected = "/foo?a=b&c=d&e=f"; - assertURLEquals(expected, WebUtilities.getPath(url, params, true), "&"); + String url = WebUtilities.getPath(baseUrl, params, true); + String expected = "/foo?a=b&c=d&e=f"; + Assert.assertEquals("Incorrect path returned for URL for javascript", expected, url); } @Test @@ -594,26 +495,6 @@ public void testRenderToHtmlWithXML() { Assert.assertEquals("Invalid html output with XML", TransformXMLTestHelper.EXPECTED, output); } -// @Test -// public void testContainsBrackets() { -// Assert.assertTrue("Contains a open bracket", WebUtilities.containsBrackets("{")); -// Assert.assertTrue("Contains a closed bracket", WebUtilities.containsBrackets("}")); -// Assert.assertFalse("Contains an encoded open bracket", WebUtilities.containsBrackets("{")); -// Assert.assertFalse("Contains an encoded closed bracket", WebUtilities.containsBrackets("}")); -// Assert.assertFalse("Contains a double encoded open bracket", WebUtilities.containsBrackets("&#123;")); -// Assert.assertFalse("Contains a double encoded closed bracket", WebUtilities.containsBrackets("&#125;")); -// } - -// @Test -// public void testContainsEncodeBrackets() { -// Assert.assertTrue("Contains an encoded open bracket", WebUtilities.containsEncodedBrackets("{")); -// Assert.assertTrue("Contains an encoded closed bracket", WebUtilities.containsEncodedBrackets("}")); -// Assert.assertFalse("Contains a double encoded open bracket", WebUtilities.containsEncodedBrackets("&#123;")); -// Assert.assertFalse("Contains a double encoded closed bracket", WebUtilities.containsEncodedBrackets("&#125;")); -// Assert.assertFalse("Contains a open bracket", WebUtilities.containsEncodedBrackets("{")); -// Assert.assertFalse("Contains a closed bracket", WebUtilities.containsEncodedBrackets("}")); -// } - @Test public void testEncodeBrackets() { String in = "{}<{}>"; @@ -677,8 +558,110 @@ public void testDoubleDecodeBracketsWithNoMatches() { Assert.assertEquals("Double decode brackets not correct", out, WebUtilities.doubleDecodeBrackets(in)); } + @Test + public void testCreateTargetUrl() { + + String baseUrl = "/path"; + + // Setup context + UIContext uic = createUIContext(); + MockWEnvironment env = new MockWEnvironment(); + env.setPostPath(baseUrl); + uic.setEnvironment(env); + setActiveContext(uic); + + // Target URL with no hidden or additional parameters + Targetable target = new MyTargetable(); + HashMap expectedParams = new LinkedHashMap<>(); + expectedParams.put("wc_target", "TARGET"); + expectedParams.put("no-cache", null); + String url = WebUtilities.createTargetUrl(target, null); + assertCreatedURLCorrect("Target URL with no hidden or additional parameters. ", url, baseUrl, expectedParams, "&"); + + // Target URL with hidden parameters + // Setup hidden parameters + HashMap hiddenParams = new LinkedHashMap<>(); + hiddenParams.put(Environment.SESSION_TOKEN_VARIABLE, "session"); + hiddenParams.put(Environment.STEP_VARIABLE, "1"); + env.setHiddenParameters(hiddenParams); + uic.setEnvironment(env); + // Expected params + expectedParams = new LinkedHashMap<>(); + expectedParams.put("wc_s", "1"); + expectedParams.put("wc_target", "TARGET"); + expectedParams.put("no-cache", null); + url = WebUtilities.createTargetUrl(target, null); + assertCreatedURLCorrect("Target URL with hidden parameter. ", url, baseUrl, expectedParams, "&"); + + // Target URL with hidden parameters and additional + // Setup additional params + HashMap additionalParams = new LinkedHashMap<>(); + additionalParams = new HashMap<>(); + additionalParams.put("c", "d"); + additionalParams.put("e", "f"); + // Expected params + expectedParams = new LinkedHashMap<>(); + expectedParams.put("wc_s", "1"); + expectedParams.put("wc_target", "TARGET"); + expectedParams.put("no-cache", null); + expectedParams.putAll(additionalParams); + url = WebUtilities.createTargetUrl(target, null, additionalParams); + assertCreatedURLCorrect("Target URL with hidden parameters and additional. ", url, baseUrl, expectedParams, "&"); + } + + @Test + public void testCreateTargetUrlWithCache() { + + String baseUrl = "/path"; + String cacheKey = "CACHE"; + + // Setup context + UIContext uic = createUIContext(); + MockWEnvironment env = new MockWEnvironment(); + env.setPostPath(baseUrl); + uic.setEnvironment(env); + setActiveContext(uic); + + // Target URL with no hidden or additional parameters + Targetable target = new MyTargetable(); + HashMap expectedParams = new LinkedHashMap<>(); + expectedParams.put("wc_target", "TARGET"); + expectedParams.put("contentCacheKey", cacheKey); + String url = WebUtilities.createTargetUrl(target, cacheKey); + assertCreatedURLCorrect("Target URL with no hidden or additional parameters and CACHE. ", url, baseUrl, expectedParams, "&"); + + // Target URL with hidden parameters + // Setup hidden parameters + HashMap hiddenParams = new LinkedHashMap<>(); + hiddenParams.put(Environment.SESSION_TOKEN_VARIABLE, "session"); + hiddenParams.put(Environment.STEP_VARIABLE, "1"); + env.setHiddenParameters(hiddenParams); + uic.setEnvironment(env); + // Expected params + expectedParams = new LinkedHashMap<>(); + expectedParams.put("wc_target", "TARGET"); + expectedParams.put("contentCacheKey", cacheKey); + url = WebUtilities.createTargetUrl(target, cacheKey); + assertCreatedURLCorrect("Target URL with hidden parameter and CACHE. ", url, baseUrl, expectedParams, "&"); + + // Target URL with hidden parameters and additional + // Setup additional params + HashMap additionalParams = new LinkedHashMap<>(); + additionalParams = new HashMap<>(); + additionalParams.put("c", "d"); + additionalParams.put("e", "f"); + // Expected params + expectedParams = new LinkedHashMap<>(); + expectedParams.put("wc_target", "TARGET"); + expectedParams.put("contentCacheKey", cacheKey); + expectedParams.putAll(additionalParams); + url = WebUtilities.createTargetUrl(target, cacheKey, additionalParams); + assertCreatedURLCorrect("Target URL with hidden parameters and additional and CACHE. ", url, baseUrl, expectedParams, "&"); + } + /** * Generates a range of characters. + * * @param from The first character in the range (must be > 0). * @param to The last character in the range (must be >= from). * @return A string containing the character range. @@ -691,99 +674,48 @@ private static String characterRange(final int from, final int to) { return result.toString(); } -// /** -// * Set up and execute the updateBeanValue method with the given parameter. -// * If the parameter is null then the default updateBeanValue(component) method will be invoked. -// * -// * @param visibleOnly the parameter to pass to WebUtilities.updateBeanValue(component, visibleOnly). -// */ -// private void runUpdateBeanValue(final Boolean visibleOnly) { -// final String directChild = "directChild"; -// final String grandChild = "grandChild"; -// final String invisibleGrandChild = "invisibleGrandChild"; -// final String childOfInvisibleContainer = "childOfInvisibleContainer"; -// -// Map beanMap = new HashMap<>(); -// beanMap.put(directChild, null); -// beanMap.put(grandChild, null); -// beanMap.put(invisibleGrandChild, null); -// beanMap.put(childOfInvisibleContainer, null); -// -// WContainer root = new WContainer(); -// root.setBean(beanMap); -// WTextField childTextField = new WTextField(); -// childTextField.setBeanProperty(directChild); -// root.add(childTextField); -// -// WContainer childContainer = new WContainer(); -// root.add(childContainer); -// WTextField grandChildTextField = new WTextField(); -// grandChildTextField.setBeanProperty(grandChild); -// childContainer.add(grandChildTextField); -// -// WTextField invisibleGrandChildTextField = new WTextField(); -// invisibleGrandChildTextField.setBeanProperty(invisibleGrandChild); -// invisibleGrandChildTextField.setVisible(false); -// childContainer.add(invisibleGrandChildTextField); -// -// WContainer invisibleContainer = new WContainer(); -// invisibleContainer.setVisible(false); -// root.add(invisibleContainer); -// WTextField childOfInivisbleContainerTextField = new WTextField(); -// childOfInivisbleContainerTextField.setBeanProperty(childOfInvisibleContainer); -// invisibleContainer.add(childOfInivisbleContainerTextField); -// -// root.setLocked(true); -// setActiveContext(createUIContext()); -// -// childTextField.setData(directChild); -// grandChildTextField.setData(grandChild); -// invisibleGrandChildTextField.setData(invisibleGrandChild); -// childOfInivisbleContainerTextField.setData(childOfInvisibleContainer); -// -// if (visibleOnly == null) { -// WebUtilities.updateBeanValue(root); -// } else { -// WebUtilities.updateBeanValue(root, visibleOnly); -// } -// -// Assert.assertEquals("updateBeanValue failed to update directChild with visibleOnly=[" + visibleOnly + "]", directChild, beanMap.get(directChild)); -// Assert.assertEquals("updateBeanValue failed to update grandChild with visibleOnly=[" + visibleOnly + "]", grandChild, beanMap.get(grandChild)); -// Assert.assertEquals("updateBeanValue updated an incorrect value for invisibleGrandChild with visibleOnly=[" + visibleOnly + "]", BooleanUtils.isNotFalse(visibleOnly) ? null : invisibleGrandChild, beanMap.get(invisibleGrandChild)); -// Assert.assertEquals("updateBeanValue updated an incorrect value for childOfInvisibleContainer with visibleOnly=[" + visibleOnly + "]", BooleanUtils.isNotFalse(visibleOnly) ? null : childOfInvisibleContainer, beanMap.get(childOfInvisibleContainer)); -// } /** * Compare the URLS. The parameters of the URL must be equal but they do not have to be in the same order. * - * @param actual the actual value - * @param expected the expected value + * @param msgPrefix message prefix for assert messages + * @param actualUrl the actual value + * @param expectedBase the expected value + * @param expectedParams the expected parameters * @param separator the separator */ - private void assertURLEquals(final String actual, final String expected, final String separator) { + private void assertCreatedURLCorrect(final String msgPrefix, final String actualUrl, final String expectedBase, final Map expectedParams, final String separator) { // compare the path section of urls (string compare) - int paramStartIndex = actual.indexOf('?'); - - // if the path elements of the url are not equal bail out now. - Assert.assertTrue("The path elements of the URLs are not equal", - expected.startsWith(actual.substring(0, paramStartIndex))); - - // now compare the parameters of each URL - String expectedURLParams = expected.substring(paramStartIndex + 1); - String actualURLParams = actual.substring(paramStartIndex + 1); - - String[] expectedParams = expectedURLParams.split(separator); - String[] actualParams = actualURLParams.split(separator); + int paramStartIndex = actualUrl.indexOf('?'); - int params = expectedParams.length; + String actualURLBase = actualUrl.substring(0, paramStartIndex); + String actualURLParams = actualUrl.substring(paramStartIndex + 1); - Assert.assertEquals("The number of parameters in URLs are not equal", params, - actualParams.length); - - List expectedParamArray = Arrays.asList(expectedParams); - List actualParamArray = Arrays.asList(actualParams); + // if the path elements of the url are not equal bail out now. + Assert.assertEquals(msgPrefix + "The path elements of the URLs are not equal", expectedBase, actualURLBase); + + // Extract actual parameters + Map actualParams = new LinkedHashMap<>(); + String[] actualParamsSplit = actualURLParams.split(separator); + for (String actual : actualParamsSplit) { + String split[] = actual.split("="); + actualParams.put(split[0], split[1]); + } - Assert.assertTrue("The parameters contained in the URLs are not equal", - actualParamArray.containsAll(expectedParamArray)); + // Check parameter keys + Assert.assertEquals(msgPrefix + "Expected parameter keys not on URL", expectedParams.keySet(), actualParams.keySet()); + + // Check parameter values + for (Map.Entry entry : expectedParams.entrySet()) { + String key = entry.getKey(); + String actualValue = actualParams.get(key); + String expectedValue = entry.getValue(); + if (expectedValue == null) { + // Null value used to indicate value is random and cannot be checked but is at least present + Assert.assertNotNull(msgPrefix + "Parameter [" + key + "] has no value", actualValue); + } else { + Assert.assertEquals(msgPrefix + "Parameter [" + key + "] has incorrect value", expectedValue, actualValue); + } + } } /** @@ -802,4 +734,13 @@ private static String getComponentId(final WComponent component, final UIContext UIContextHolder.popContext(); } } + + private static class MyTargetable extends AbstractWComponent implements Targetable { + + @Override + public String getTargetId() { + return "TARGET"; + } + + } } diff --git a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/container/SessionTokenAjaxInterceptor_Test.java b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/container/SessionTokenAjaxInterceptor_Test.java index 0ccbbc5d7..8803ba58c 100644 --- a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/container/SessionTokenAjaxInterceptor_Test.java +++ b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/container/SessionTokenAjaxInterceptor_Test.java @@ -17,63 +17,77 @@ */ public class SessionTokenAjaxInterceptor_Test extends AbstractWComponentTestCase { + private static final String VALID_TOKEN = "X"; + private static final String INVALID_TOKEN = "Y"; + @Before public void setupUIC() { + // Set up user context and session token + MockWEnvironment env = new MockWEnvironment(); + env.setSessionToken(VALID_TOKEN); UIContext uic = createUIContext(); - uic.setEnvironment(new MockWEnvironment()); + uic.setEnvironment(env); setActiveContext(uic); } @Test(expected = SessionTokenException.class) public void testServiceRequestNoTokenOnUIC() { - SessionTokenAjaxInterceptor interceptor = new SessionTokenAjaxInterceptor(); - interceptor.serviceRequest(new MockRequest()); + // Clear session token on UIC + UIContextHolder.getCurrent().getEnvironment().setSessionToken(null); + // Should not process with a UIC with no session token + new SessionTokenAjaxInterceptor().serviceRequest(new MockRequest()); } @Test - public void testServiceRequestCorrectToken() { + public void testServiceRequestWithPOSTandCorrectToken() { // Setup interceptor SessionTokenAjaxInterceptor interceptor = new SessionTokenAjaxInterceptor(); MyBackingComponent component = new MyBackingComponent(); interceptor.attachUI(component); - // Setup session token - UIContext uic = UIContextHolder.getCurrent(); - uic.getEnvironment().setSessionToken("X"); // Setup request MockRequest request = new MockRequest(); - request.setParameter(Environment.SESSION_TOKEN_VARIABLE, "X"); - // Process request + request.setParameter(Environment.SESSION_TOKEN_VARIABLE, VALID_TOKEN); + // Should process with POST request and valid token interceptor.serviceRequest(request); - Assert.assertTrue("Action phase should have occurred for corret token", component.handleRequestCalled); + Assert.assertTrue("Action phase should have occurred for POST and correct token", component.handleRequestCalled); } @Test(expected = SessionTokenException.class) - public void testServiceRequestInvalidToken() { - // Setup interceptor - SessionTokenAjaxInterceptor interceptor = new SessionTokenAjaxInterceptor(); - MyBackingComponent component = new MyBackingComponent(); - interceptor.attachUI(component); - // Setup session token - UIContext uic = UIContextHolder.getCurrent(); - uic.getEnvironment().setSessionToken("X"); + public void testServiceRequestWithPOSTandInvalidToken() { // Setup invalid request MockRequest request = new MockRequest(); - request.setParameter(Environment.SESSION_TOKEN_VARIABLE, "Y"); - // Process request - interceptor.serviceRequest(request); + request.setParameter(Environment.SESSION_TOKEN_VARIABLE, INVALID_TOKEN); + // Should not process with a POST request and invalid token + new SessionTokenAjaxInterceptor().serviceRequest(request); } @Test(expected = SessionTokenException.class) - public void testServiceRequestNoTokenOnRequest() { - // Setup interceptor + public void testServiceRequestWithPOSTandNoTokenOnRequest() { + // Should not process with a POST request and no token + new SessionTokenAjaxInterceptor().serviceRequest(new MockRequest()); + } + + @Test(expected = IllegalStateException.class) + public void testServiceRequestWithGETandToken() { + // Setup GET request with token + MockRequest request = new MockRequest(); + request.setMethod("GET"); + request.setParameter(Environment.SESSION_TOKEN_VARIABLE, VALID_TOKEN); + // Should not allow GET request with token parameter + new SessionTokenAjaxInterceptor().serviceRequest(request); + } + + @Test + public void testServiceRequestWithGETandNoToken() { SessionTokenAjaxInterceptor interceptor = new SessionTokenAjaxInterceptor(); MyBackingComponent component = new MyBackingComponent(); interceptor.attachUI(component); - // Setup session token - UIContext uic = UIContextHolder.getCurrent(); - uic.getEnvironment().setSessionToken("X"); - // Process request - interceptor.serviceRequest(new MockRequest()); + // Setup GET request with no token + MockRequest request = new MockRequest(); + request.setMethod("GET"); + // Should allow GET request with no token parameter + interceptor.serviceRequest(request); + Assert.assertTrue("Action phase should have occurred for GET request with no token", component.handleRequestCalled); } /** diff --git a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/container/SessionTokenContentInterceptor_Test.java b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/container/SessionTokenContentInterceptor_Test.java index 2c62105ac..b9425d353 100644 --- a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/container/SessionTokenContentInterceptor_Test.java +++ b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/container/SessionTokenContentInterceptor_Test.java @@ -1,7 +1,6 @@ package com.github.bordertech.wcomponents.container; import com.github.bordertech.wcomponents.AbstractWComponentTestCase; -import com.github.bordertech.wcomponents.ContentEscape; import com.github.bordertech.wcomponents.Environment; import com.github.bordertech.wcomponents.MockWEnvironment; import com.github.bordertech.wcomponents.Request; @@ -18,82 +17,54 @@ */ public class SessionTokenContentInterceptor_Test extends AbstractWComponentTestCase { + private static final String VALID_TOKEN = "X"; + @Before public void setupUIC() { + // Set up user context and session token + Environment env = new MockWEnvironment(); + env.setSessionToken(VALID_TOKEN); UIContext uic = createUIContext(); - uic.setEnvironment(new MockWEnvironment()); + uic.setEnvironment(env); setActiveContext(uic); } @Test(expected = SessionTokenException.class) public void testServiceRequestNoTokenOnUIC() { - SessionTokenContentInterceptor interceptor = new SessionTokenContentInterceptor(); - interceptor.serviceRequest(new MockRequest()); + // Clear token on Context + UIContextHolder.getCurrent().getEnvironment().setSessionToken(null); + // Should not process if UIC has no session token + new SessionTokenContentInterceptor().serviceRequest(new MockRequest()); } - @Test - public void testServiceRequestCorrectToken() { - // Setup interceptor - SessionTokenContentInterceptor interceptor = new SessionTokenContentInterceptor(); - MyBackingContent component = new MyBackingContent(); - interceptor.attachUI(component); - // Setup session token - UIContext uic = UIContextHolder.getCurrent(); - uic.getEnvironment().setSessionToken("X"); - // Setup request - MockRequest request = new MockRequest(); - request.setParameter(Environment.SESSION_TOKEN_VARIABLE, "X"); - // Process request - interceptor.serviceRequest(request); - Assert.assertTrue("Action phase should have occurred for corret token", component.handleRequestCalled); + @Test(expected = IllegalStateException.class) + public void testServiceRequestWithPOST() { + // Should not process with a POST request + new SessionTokenContentInterceptor().serviceRequest(new MockRequest()); } - @Test(expected = SessionTokenException.class) - public void testServiceRequestInvalidToken() { - // Setup interceptor - SessionTokenContentInterceptor interceptor = new SessionTokenContentInterceptor(); - MyBackingContent component = new MyBackingContent(); - interceptor.attachUI(component); - // Setup session token - UIContext uic = UIContextHolder.getCurrent(); - uic.getEnvironment().setSessionToken("X"); - // Setup invalid request + @Test(expected = IllegalStateException.class) + public void testServiceRequestWithGETandToken() { + // Setup GET request with token MockRequest request = new MockRequest(); - request.setParameter(Environment.SESSION_TOKEN_VARIABLE, "Y"); - // Process request - interceptor.serviceRequest(request); - } - - @Test(expected = SessionTokenException.class) - public void testServiceRequestNoTokenOnRequest() { - // Setup interceptor - SessionTokenContentInterceptor interceptor = new SessionTokenContentInterceptor(); - MyBackingContent component = new MyBackingContent(); - interceptor.attachUI(component); - // Setup session token - UIContext uic = UIContextHolder.getCurrent(); - uic.getEnvironment().setSessionToken("X"); - // Process request - interceptor.serviceRequest(new MockRequest()); + request.setMethod("GET"); + request.setParameter(Environment.SESSION_TOKEN_VARIABLE, VALID_TOKEN); + // Should not process with a GET request with a token + new SessionTokenContentInterceptor().serviceRequest(request); } - @Test(expected = ContentEscape.class) - public void testServiceRequestNoTokenWIthCachedContent() { + @Test + public void testServiceRequestWithGETandNoToken() { // Setup interceptor SessionTokenContentInterceptor interceptor = new SessionTokenContentInterceptor(); MyBackingContent component = new MyBackingContent(); interceptor.attachUI(component); - // Setup session token - UIContext uic = UIContextHolder.getCurrent(); - uic.getEnvironment().setSessionToken("X"); - uic.setUI(component); - // Setup request - TargetID makes the WContent trigger the ContentEscape + // Setup request MockRequest request = new MockRequest(); - request.setParameter(Environment.TARGET_ID, component.getId()); - // Set cached content - component.setCacheKey("mykey"); - // Process request + request.setMethod("GET"); + // Should process with a GET request and no token interceptor.serviceRequest(request); + Assert.assertTrue("Action phase should have occurred for GET request and no token", component.handleRequestCalled); } /** diff --git a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/container/SessionTokenInterceptor_Test.java b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/container/SessionTokenInterceptor_Test.java index 2328a4a9a..5c9efdd89 100755 --- a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/container/SessionTokenInterceptor_Test.java +++ b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/container/SessionTokenInterceptor_Test.java @@ -6,10 +6,10 @@ import com.github.bordertech.wcomponents.Request; import com.github.bordertech.wcomponents.UIContext; import com.github.bordertech.wcomponents.UIContextHolder; -import com.github.bordertech.wcomponents.UIContextImpl; import com.github.bordertech.wcomponents.WApplication; import com.github.bordertech.wcomponents.util.mock.MockRequest; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; /** @@ -20,90 +20,112 @@ */ public class SessionTokenInterceptor_Test extends AbstractWComponentTestCase { - @Test - public void testServiceRequestCorrectToken() { + private static final String VALID_TOKEN = "X"; + private static final String INVALID_TOKEN = "Y"; + @Before + public void setupUIC() { + // Set up user context and session token + MockWEnvironment env = new MockWEnvironment(); + env.setSessionToken(VALID_TOKEN); + UIContext uic = createUIContext(); + uic.setEnvironment(env); + setActiveContext(uic); + } + + @Test + public void testServiceRequestWithPOSTandCorrectToken() { // Setup interceptor - SessionTokenInterceptor interceptor = setupInterceptor(); - MyBackingComponent component = (MyBackingComponent) interceptor.getBackingComponent(); - UIContext uic = UIContextHolder.getCurrent(); + SessionTokenInterceptor interceptor = new SessionTokenInterceptor(); + MyBackingComponent component = new MyBackingComponent(); + interceptor.attachUI(component); + // Setup request with valid token MockRequest request = new MockRequest(); - - // Setup matching tokens on session and request - uic.getEnvironment().setSessionToken("X"); - request.setParameter(Environment.SESSION_TOKEN_VARIABLE, "X"); - - // Process request + request.setParameter(Environment.SESSION_TOKEN_VARIABLE, VALID_TOKEN); + // Should process POST request with correct token interceptor.serviceRequest(request); Assert.assertTrue("Action phase should have occurred for corret token", component.handleRequestCalled); } - @Test - public void testServiceRequestIncorrectToken() { - // Setup interceptor - SessionTokenInterceptor interceptor = setupInterceptor(); - MyBackingComponent component = (MyBackingComponent) interceptor.getBackingComponent(); - UIContext uic = UIContextHolder.getCurrent(); + @Test(expected = SessionTokenException.class) + public void testServiceRequestWithPOSTandIncorrectToken() { + // Setup request with invalid token MockRequest request = new MockRequest(); - - // Setup tokens that dont match on session and request - uic.getEnvironment().setSessionToken("X"); - request.setParameter(Environment.SESSION_TOKEN_VARIABLE, "Y"); - - try { - // Process request - interceptor.serviceRequest(request); - Assert.fail("Should have thrown an excpetion for incorrect token"); - } catch (SessionTokenException e) { - Assert.assertFalse("Action phase should not have occurred for token error", component.handleRequestCalled); - } + request.setParameter(Environment.SESSION_TOKEN_VARIABLE, INVALID_TOKEN); + // Should not process POST request with incorrect token + new SessionTokenInterceptor().serviceRequest(request); } - @Test + @Test(expected = SessionTokenException.class) public void testSessionTimeout() { - // Setup interceptor - SessionTokenInterceptor interceptor = setupInterceptor(); - MyBackingComponent component = (MyBackingComponent) interceptor.getBackingComponent(); - UIContext uic = UIContextHolder.getCurrent(); + // Clear session token on UIC (simulate new session from timeout) + UIContextHolder.getCurrent().getEnvironment().setSessionToken(null); + // Simulate request parameter from previous session (new session has null token) MockRequest request = new MockRequest(); + request.setParameter(Environment.SESSION_TOKEN_VARIABLE, VALID_TOKEN); + // Should not process a POST request with a token and null session token + new SessionTokenInterceptor().serviceRequest(request); + } - // Simulate request parameter from previous session (new session has null token) - request.setParameter(Environment.SESSION_TOKEN_VARIABLE, "X"); - try { - // Process request - interceptor.serviceRequest(request); - Assert.fail("Should have thrown an excpetion for incorrect token"); - } catch (SessionTokenException e) { - Assert.assertFalse("Action phase should not have occurred for session timeout", component.handleRequestCalled); - Assert.assertEquals("Step count should not have been incremented for session timeout", 0, uic.getEnvironment().getStep()); - } + @Test(expected = SessionTokenException.class) + public void testNewSessionWithPOSTRequestWithToken() { + // Clear session token on UIC (simulate new session) + UIContextHolder.getCurrent().getEnvironment().setSessionToken(null); + // Setup POST request with a token + MockRequest request = new MockRequest(); + request.setParameter(Environment.SESSION_TOKEN_VARIABLE, VALID_TOKEN); + // Should not process a POST request with a token and a new session + new SessionTokenInterceptor().serviceRequest(request); + } + + @Test(expected = SessionTokenException.class) + public void testNewSessionWithPOSTRequestWithNoToken() { + // Clear session token on UIC (simulate new session) + UIContextHolder.getCurrent().getEnvironment().setSessionToken(null); + // Should not process a POST request with no token and a new session + new SessionTokenInterceptor().serviceRequest(new MockRequest()); } @Test - public void testNewSession() { + public void testNewSessionWithGETRequest() { // Setup interceptor - SessionTokenInterceptor interceptor = setupInterceptor(); + SessionTokenInterceptor interceptor = new SessionTokenInterceptor(); + MyBackingComponent component = new MyBackingComponent(); + interceptor.attachUI(component); UIContext uic = UIContextHolder.getCurrent(); - - // Check no session token (ie new session) - Assert.assertNull("Session token should be null for new session", uic.getEnvironment().getSessionToken()); - - // Test default state (ie no params and new session) + // Clear session token on UIC (simulate new session) + uic.getEnvironment().setSessionToken(null); + // Setup GET Request with no token MockRequest request = new MockRequest(); + request.setMethod("GET"); interceptor.serviceRequest(request); interceptor.preparePaint(request); + Assert.assertTrue("Action phase should have occurred for new session", component.handleRequestCalled); Assert.assertNotNull("Session token should be set for new session", uic.getEnvironment().getSessionToken()); } - private SessionTokenInterceptor setupInterceptor() { - MyBackingComponent component = new MyBackingComponent(); + @Test(expected = IllegalStateException.class) + public void testServiceRequestWithGETandToken() { + // Setup GET request with a token + MockRequest request = new MockRequest(); + request.setMethod("GET"); + request.setParameter(Environment.SESSION_TOKEN_VARIABLE, VALID_TOKEN); + // Should not process a GET request with a token + new SessionTokenInterceptor().serviceRequest(request); + } + + @Test + public void testServiceRequestWithGETandNoToken() { + // Setup interceptor SessionTokenInterceptor interceptor = new SessionTokenInterceptor(); - interceptor.setBackingComponent(component); - UIContext uic = new UIContextImpl(); - uic.setUI(component); - uic.setEnvironment(new MockWEnvironment()); - setActiveContext(uic); - return interceptor; + MyBackingComponent component = new MyBackingComponent(); + interceptor.attachUI(component); + // Setup GET request with no token + MockRequest request = new MockRequest(); + request.setMethod("GET"); + // Should process GET request with no token + interceptor.serviceRequest(request); + Assert.assertTrue("Action phase should have occurred for new session", component.handleRequestCalled); } /** diff --git a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/render/webxml/WApplicationRenderer_Test.java b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/render/webxml/WApplicationRenderer_Test.java index a7fbf90e0..75f265317 100755 --- a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/render/webxml/WApplicationRenderer_Test.java +++ b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/render/webxml/WApplicationRenderer_Test.java @@ -1,5 +1,6 @@ package com.github.bordertech.wcomponents.render.webxml; +import com.github.bordertech.wcomponents.Environment; import com.github.bordertech.wcomponents.MockWEnvironment; import com.github.bordertech.wcomponents.UIContext; import com.github.bordertech.wcomponents.WApplication; @@ -12,10 +13,11 @@ import com.github.bordertech.wcomponents.util.ConfigurationProperties; import java.io.IOException; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; -import org.junit.Assert; import org.apache.commons.configuration.Configuration; import org.custommonkey.xmlunit.exceptions.XpathException; +import org.junit.Assert; import org.junit.Test; import org.xml.sax.SAXException; @@ -103,6 +105,30 @@ public void testBasicRenderedFormat() throws XpathException, IOException, SAXExc assertXpathNotExists("//ui:application/@defaultFocusId", application); } + @Test + public void testAjaxUrlWithParameters() throws XpathException, IOException, SAXException { + // Basic component (no optional fields) + MockWEnvironment environment = new MockWEnvironment(); + WApplication application = new WApplication(); + environment.setPostPath("WApplicationRendererTest.postPath"); + HashMap hiddenParams = new LinkedHashMap<>(); + hiddenParams.put("A", "B"); + hiddenParams.put("X", "Y"); + // This should be ignored and not added to the AJAX url + hiddenParams.put(Environment.SESSION_TOKEN_VARIABLE, "SESSION"); + environment.setHiddenParameters(hiddenParams); + + String expectedUrl = "WApplicationRendererTest.postPath?A=B&X=Y"; + + UIContext uic = createUIContext(); + uic.setEnvironment(environment); + uic.setUI(application); + setActiveContext(uic); + + assertSchemaMatch(application); + assertXpathEvaluatesTo(expectedUrl, "//ui:application/@ajaxUrl", application); + } + @Test public void testRenderedFormatWithFocussedComponent() throws XpathException, IOException, SAXException { @@ -180,8 +206,10 @@ public void testXssEscaping() throws IOException, SAXException, XpathException { application.setTitle(getMaliciousAttribute("ui:application")); assertSafeContent(application); - uic.getEnvironment().getHiddenParameters().put(getMaliciousAttribute("ui:param"), "dummy"); - uic.getEnvironment().getHiddenParameters().put("dummy", getMaliciousAttribute("ui:param")); + HashMap hiddenParams = new LinkedHashMap<>(); + hiddenParams.put(getMaliciousAttribute("ui:param"), "dummy"); + hiddenParams.put("dummy", getMaliciousAttribute("ui:param")); + environment.setHiddenParameters(hiddenParams); assertSafeContent(application); } diff --git a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/render/webxml/WebXmlRenderingPerformance_Test.java b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/render/webxml/WebXmlRenderingPerformance_Test.java index bd2fb7182..c39aa128d 100755 --- a/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/render/webxml/WebXmlRenderingPerformance_Test.java +++ b/wcomponents-core/src/test/java/com/github/bordertech/wcomponents/render/webxml/WebXmlRenderingPerformance_Test.java @@ -28,8 +28,8 @@ import org.junit.experimental.categories.Category; /** - * Tests to check the performance of WComponent XML rendering. This test does not check that the XML output is correct - - * see the tests for each Renderer. + * Tests to check the performance of WComponent XML rendering. This test does not check that the XML output is correct - see the tests for each + * Renderer. * * @author Yiannis Paschalidis * @since 1.0.0 @@ -236,6 +236,7 @@ private void sendRequest(final WComponent comp, final UIContext uic) { setActiveContext(uic); MockRequest request = new MockRequest(); + request.setMethod("GET"); try { root.serviceRequest(request); diff --git a/wcomponents-theme/src/main/js/wc/ajax/Trigger.js b/wcomponents-theme/src/main/js/wc/ajax/Trigger.js index fa0252d75..e376f3412 100755 --- a/wcomponents-theme/src/main/js/wc/ajax/Trigger.js +++ b/wcomponents-theme/src/main/js/wc/ajax/Trigger.js @@ -704,6 +704,9 @@ function(tag, event, serialize, Widget, getAncestorOrSelf, ajax, formUpdateManag result = addToQueryString(result, serialize.serialize(region.getElementsByTagName(TAG.SELECT))); result = addToQueryString(result, serialize.serialize(region.getElementsByTagName(TAG.TEXTAREA))); result = addToQueryString(result, serialize.serialize(stateContainer.getElementsByTagName(TAG.INPUT))); + if (instance.method !== instance.METHODS.GET) { + result = addToQueryString(result, serialize.serialize(document.getElementsByName("wc_t"))); + } } else { formUpdateManager.update(form); result = serialize.serialize(form); diff --git a/wcomponents-theme/src/main/js/wc/ui/multiFileUploader.js b/wcomponents-theme/src/main/js/wc/ui/multiFileUploader.js index 1d664a884..4d77086b0 100755 --- a/wcomponents-theme/src/main/js/wc/ui/multiFileUploader.js +++ b/wcomponents-theme/src/main/js/wc/ui/multiFileUploader.js @@ -893,6 +893,7 @@ function (attribute, prefetch, event, initialise, uid, Trigger, has, clearSelect */ function sendFile(uri, uploadName, fileId, file, callback) { var request, xhr, formData = new FormData(), + token = document.getElementById("wc_t"), onProgress = progressEventFactory(fileId), onError = errorHandlerFactory(fileId), onAbort = abortHandlerFactory(fileId); @@ -905,6 +906,10 @@ function (attribute, prefetch, event, initialise, uid, Trigger, has, clearSelect * The name, however, is a readonly property of blob and while we may appear to have overridden the value we probably haven't. */ formData.append(uploadName, file, file.name); + // Add session token + if (token) { + formData.append("wc_t", token.value); + } request = { url: uri, diff --git a/wcomponents-theme/src/test/intern/wc.date.parser.test.js b/wcomponents-theme/src/test/intern/wc.date.parser.test.js index bcd28e4e2..c4b2a31a1 100755 --- a/wcomponents-theme/src/test/intern/wc.date.parser.test.js +++ b/wcomponents-theme/src/test/intern/wc.date.parser.test.js @@ -114,19 +114,19 @@ define(["intern!object", "intern/chai!assert", "intern/resources/test.utils!"], }, testParserStndExpandYear: function() { var parser = getParser(standardMasks, false, false), - result = parser.parse("281025"); + result = parser.parse("281040"); assert.strictEqual(result.length, 1); assert.strictEqual(result[0].day, 28); assert.strictEqual(result[0].month, 10); - assert.strictEqual(result[0].year, 2025); + assert.strictEqual(result[0].year, 2040); }, testParserStndExpandYearPast: function() { var parser = getParser(standardMasks, true, false), - result = parser.parse("281025"); + result = parser.parse("281045"); assert.strictEqual(result.length, 1); assert.strictEqual(result[0].day, 28); assert.strictEqual(result[0].month, 10); - assert.strictEqual(result[0].year, 1925); + assert.strictEqual(result[0].year, 1945); }, testParserStndMonthAbbr: function() { var parser = getParser(standardMasks, false, false), diff --git a/wcomponents-xslt/src/main/xslt/wc.ui.application.xsl b/wcomponents-xslt/src/main/xslt/wc.ui.application.xsl index 0bb337c78..97133ed63 100644 --- a/wcomponents-xslt/src/main/xslt/wc.ui.application.xsl +++ b/wcomponents-xslt/src/main/xslt/wc.ui.application.xsl @@ -1,8 +1,8 @@ + xmlns:ui="https://github.com/bordertech/wcomponents/namespace/ui/v1.0" + xmlns:html="http://www.w3.org/1999/xhtml" version="2.0" + exclude-result-prefixes="xsl ui html"> @@ -18,17 +18,6 @@
- - - - & - - - ? - - - - @@ -40,6 +29,9 @@ hidden + + +