Skip to content

Instantly share code, notes, and snippets.

@apetro
Last active August 29, 2015 14:01
Show Gist options
  • Save apetro/e56984a85f23d492c9c0 to your computer and use it in GitHub Desktop.
Save apetro/e56984a85f23d492c9c0 to your computer and use it in GitHub Desktop.
CVE-2014-3417 CONFIG permission issue writeup

CVE-2014-3417 Illicit access to Config mode

Description:

Whereas the CONFIG permission is intended to restrict who can configure, via Config mode, a given portlet publication, this restriction is reflected only superficially in the UI layer and any user, including the guest user, with SUBSCRIBE access to the portlet being configured can enter Config mode for that portlet. This renders the CONFIG permission ineffective.

The information exposed and potential for nefarious injection of markup and behavior varies greatly depending upon what individual portlets with CONFIG mode are in use, potentially including custom-developed portlets. In general, though, CONFIG mode is administrative configuration intended to be available only to trusted portal administrators and delegated administrators, and not to unprivileged users or to the anonymous public via the guest user.

This problem is exacerbated by potential use of portlet preferences in specific portlets to store credentials coupled with CONFIG UIs over those preferences that expose their present values.

Remediation:

Patch is available.

Options

0. Un-publish SQL Query portlets

Immediately un-publish all instances of the ships-with-uPortal SQL Query portlet. The CONFIG mode of the SQL Query portlet allows arbitrary queries against the portal database, which will include portlet preferences, which may include credentials or other personal data. This is probably a particularly serious way to compromise a uPortal environment.

This option doesn't remediate the entire vulnerability, but it zaps the most serious vector for the vulnerability.

1. Un-declare custom config portlet-mode

One blunt instrument is to un-declare the CONFIG (or Config or config, etc.) custom mode from the portlet.xml deployment descriptors of the deployed portlets (and restart the portal so this takes effect). This will prevent the portlet container from recognizing and serving up the Config mode of the portlets so configured. That blocks the vulnerability at the cost of degraded administrative functionality. Of course you can always configure the portlets via entity files instead of via the Config mode UI anyway.

Config mode declarations in portlet.xml look like this:

 <supports>
   <mime-type>text/html</mime-type>
    <portlet-mode>view</portlet-mode>
    <portlet-mode>config</portlet-mode>
 </supports>

with the part about <portlet-mode>config</portlet-mode> being the important bit for this issue.

(live example: http://goo.gl/Lqqee0 ).

If you have no portlets with such <portlet-mode> supports declarations declaring config mode support, you are not affected by this vulnerability (but should still patch since you might adopt portlets in the future using config mode.)

2. Block with a Filter?

Another blunt instrument solution would be to add a Java Servlet Filter or the like intercepting all attempts to enter CONFIG mode and blocking these. This would degrade functionality for administrators but block exploits of the vulnerability until patched.

3. Apply the patch to restrict access to Config mode as intended

The deeper fix is to start actually enforcing CONFIG permission in the portlet container on attempt to use CONFIG mode. The developed patch does this.

4. Upgrade to a fixed uPortal version

uPortal 4.0.13.1 includes the fix for this vulnerability such that CONFIG permissions are properly enforced.

uPortal 4.0.14 and 4.1 when released will also include this fix.

Patch application

The vulnerability was introduced by a changeset that inadvertently orphaned the bit of code that enforced the CONFIG permission.

The provided patch works by restoring that enforcement to the critical path for portlet execution.

To apply the patch,

###. 0. Backup

Back up your custom uPortal source code and deployed artifacts, of course, before making any changes.

1a. Replace PortletRendererImmpl.java

Replace org/jasig/portal/portlet/rendering/PortletRendererImpl.java with the fixed version of the file.

OR

1b. Fix PortletRendererImpl.java

Merge in the changes to PortletRendererImpl if you have local modifications you wish to retain.

AND

2. Re-build from source

Re-build your customized uPortal from source so as to to include the resulting modified PortletRendererImpl .class file in your deployable artifacts

AND

3. Deploy

Stop Tomcat; Re-deploy your customized uPortal so that the deployed artifacts include the fixed PortletRendererImpl .class file; Start Tomcat. so that the change takes effect.

Risk assessments and characterization

Cf. OWASP Application Threat Modeling.

Type:

This is an instance of the Access Control Enforced by Presentation Layer category of vulnerability in OWASP parlance.

RISK model: High

  • Impact: High: Where this vulnerability is present, it may expose privileged credentials in e.g. WebProxyPortlet configurations. Exposed usages of the SqlQuery portlet type are at very serious risk. Exposed Advanced variant of the SimpleContentPortlet probably make JavaScript and markup injection attacks feasible.
  • Possibility: High: Most uPortal deployments will have exposed SimpleContent and WebProxy portlets with CONFIG mode.
  • Ease: High: Easy to exploit through trivial URL manipulation apparent on review of publicly available source code.

DREAD model: 8/10

  • Damage potential: Read stored system credentials, JavaScript injection, database access, user attribute interception... : 8
  • Reproducibility: Reproducible in all recent uPortal versions: 10
  • Exploitability: Often exploitable anonymously or as unprivileged user: 8
  • Affected users: Affects most uPortal adopters : 8
  • Discoverability: Apparent on review of publicly available source code, but config prompts are not typically surfaced in end-user-facing UIs to give folks the idea to try this: 5

(8+10+8+8+5)/5 = 39/5 = 8 (out of 10).

How to get help with this fix

Acknowledgements

Andrew Petro / the My UW-Madison team discovered this vulnerability. Andrew Petro coded the initial fix shipping in uPortal 4.0.13.1 and applied to the active maintenance branches.

From 0bd81ff225d4786340a990acb2f0a6c7068fd1ff Mon Sep 17 00:00:00 2001
From: Andrew Petro <apetro@wisc.edu>
Date: Wed, 7 May 2014 10:42:43 -0500
Subject: [PATCH] Enforce CONFIG permission.
---
.../portal/portlet/rendering/IPortletRenderer.java | 10 ++++
.../portlet/rendering/PortletRendererImpl.java | 62 ++++++++++++++++++----
2 files changed, 63 insertions(+), 9 deletions(-)
diff --git a/uportal-war/src/main/java/org/jasig/portal/portlet/rendering/IPortletRenderer.java b/uportal-war/src/main/java/org/jasig/portal/portlet/rendering/IPortletRenderer.java
index 28af532..f337b13 100644
--- a/uportal-war/src/main/java/org/jasig/portal/portlet/rendering/IPortletRenderer.java
+++ b/uportal-war/src/main/java/org/jasig/portal/portlet/rendering/IPortletRenderer.java
@@ -107,6 +107,8 @@ public interface IPortletRenderer {
* @param portletWindowId Portlet to target with the action
* @param httpServletRequest The portal's request
* @param httpServletResponse The portal's response (nothing will be written to the response)
+ * @throws org.jasig.portal.AuthorizationException if the requesting user lacks permission to invoke the requested
+ * portlet window's portlet mode
*/
public long doAction(IPortletWindowId portletWindowId, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse);
@@ -116,6 +118,8 @@ public interface IPortletRenderer {
* @param portletWindowId Portlet to target with the action
* @param httpServletRequest The portal's request
* @param httpServletResponse The portal's response (nothing will be written to the response)
+ * @throws org.jasig.portal.AuthorizationException if the requesting user lacks permission to invoke the requested
+ * portlet window's portlet mode
*/
public long doEvent(IPortletWindowId portletWindowId, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Event event);
@@ -126,6 +130,8 @@ public interface IPortletRenderer {
* @param httpServletRequest The portal's request
* @param httpServletResponse The portal's response (nothing will be written to the response)
* @param portletOutputHandler The output handler to write to
+ * @throws org.jasig.portal.AuthorizationException if the requesting user lacks permission to invoke the requested
+ * portlet mode
*/
public PortletRenderResult doRenderMarkup(IPortletWindowId portletWindowId, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, PortletOutputHandler portletOutputHandler) throws IOException;
@@ -136,6 +142,8 @@ public interface IPortletRenderer {
* @param httpServletRequest The portal's request
* @param httpServletResponse The portal's response (nothing will be written to the response)
* @param portletOutputHandler The output handler to write to
+ * @throws org.jasig.portal.AuthorizationException if the requesting user does not have permission to access the
+ * portlet window's current mode.
*/
public PortletRenderResult doRenderHeader(IPortletWindowId portletWindowId, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, PortletOutputHandler portletOutputHandler) throws IOException;
@@ -147,6 +155,8 @@ public interface IPortletRenderer {
* @param httpServletResponse The portal's response (nothing will be written to the response)
* @param portletOutputHandler The output handler to write to
* @return The execution time for serving the resource
+ * @throws org.jasig.portal.AuthorizationException if the requesting user lacks permission to invoke the requested
+ * portlet window's portlet mode
*/
public long doServeResource(IPortletWindowId portletWindowId, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, PortletResourceOutputHandler portletOutputHandler) throws IOException;
diff --git a/uportal-war/src/main/java/org/jasig/portal/portlet/rendering/PortletRendererImpl.java b/uportal-war/src/main/java/org/jasig/portal/portlet/rendering/PortletRendererImpl.java
index 976b6ef..61b7bba 100644
--- a/uportal-war/src/main/java/org/jasig/portal/portlet/rendering/PortletRendererImpl.java
+++ b/uportal-war/src/main/java/org/jasig/portal/portlet/rendering/PortletRendererImpl.java
@@ -31,6 +31,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.Validate;
import org.apache.pluto.container.PortletContainer;
import org.apache.pluto.container.PortletContainerException;
import org.jasig.portal.AuthorizationException;
@@ -135,6 +136,8 @@ public class PortletRendererImpl implements IPortletRenderer {
this.portletCacheControlService.purgeCachedPortletData(portletWindowId, httpServletRequest);
final IPortletWindow portletWindow = this.portletWindowRegistry.getPortletWindow(httpServletRequest, portletWindowId);
+
+ enforceConfigPermission(httpServletRequest, portletWindow);
httpServletRequest = this.setupPortletRequest(httpServletRequest);
httpServletResponse = new PortletHttpServletResponseWrapper(httpServletResponse, portletWindow);
@@ -179,6 +182,9 @@ public class PortletRendererImpl implements IPortletRenderer {
final IPortletWindow portletWindow = this.portletWindowRegistry.getPortletWindow(httpServletRequest, portletWindowId);
httpServletRequest = this.setupPortletRequest(httpServletRequest);
+
+ enforceConfigPermission(httpServletRequest, portletWindow);
+
httpServletResponse = new PortletHttpServletResponseWrapper(httpServletResponse, portletWindow);
//Execute the action,
@@ -209,7 +215,9 @@ public class PortletRendererImpl implements IPortletRenderer {
public PortletRenderResult doRenderHeader(IPortletWindowId portletWindowId,
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, PortletOutputHandler portletOutputHandler) throws IOException {
-
+
+ // enforce CONFIG mode restriction through its enforcement in doRender().
+
return doRender(portletWindowId,
httpServletRequest,
httpServletResponse,
@@ -227,7 +235,9 @@ public class PortletRendererImpl implements IPortletRenderer {
@Override
public PortletRenderResult doRenderMarkup(IPortletWindowId portletWindowId, HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, PortletOutputHandler portletOutputHandler) throws IOException {
-
+
+ // enforce CONFIG mode access restriction through its enforcement in the doRender() impl.
+
return doRender(portletWindowId,
httpServletRequest,
httpServletResponse,
@@ -362,6 +372,8 @@ public class PortletRendererImpl implements IPortletRenderer {
final IPortletWindow portletWindow = this.portletWindowRegistry.getPortletWindow(httpServletRequest, portletWindowId);
+ enforceConfigPermission(httpServletRequest, portletWindow);
+
/*
* If the portlet is rendering in EXCLUSIVE WindowState ignore the provided PortletOutputHandler and
* write directly to the response.
@@ -462,6 +474,8 @@ public class PortletRendererImpl implements IPortletRenderer {
logger.debug("Replaying cached content for Render {} request to {}", renderPart, portletWindow);
+ enforceConfigPermission(httpServletRequest, portletWindow);
+
final long renderStartTime = System.nanoTime();
final CachedPortletData<PortletRenderResult> cachedPortletData = cacheState.getCachedPortletData();
@@ -527,6 +541,8 @@ public class PortletRendererImpl implements IPortletRenderer {
final IPortletWindow portletWindow = this.portletWindowRegistry.getPortletWindow(httpServletRequest, portletWindowId);
+ enforceConfigPermission(httpServletRequest, portletWindow);
+
if (cacheState.isUseBrowserData()) {
return doResourceReplayBrowserContent(portletWindow, httpServletRequest, cacheState, portletOutputHandler);
}
@@ -610,6 +626,8 @@ public class PortletRendererImpl implements IPortletRenderer {
protected long doResourceReplayBrowserContent(IPortletWindow portletWindow, HttpServletRequest httpServletRequest,
CacheState<CachedPortletResourceData<Long>, Long> cacheState,
PortletResourceOutputHandler portletOutputHandler) {
+
+ enforceConfigPermission(httpServletRequest, portletWindow);
logger.debug("Sending 304 for resource request to {}", portletWindow);
@@ -641,6 +659,8 @@ public class PortletRendererImpl implements IPortletRenderer {
protected Long doResourceReplayCachedContent(IPortletWindow portletWindow,
HttpServletRequest httpServletRequest, CacheState<CachedPortletResourceData<Long>, Long> cacheState,
PortletResourceOutputHandler portletOutputHandler, long baseExecutionTime) throws IOException {
+
+ enforceConfigPermission(httpServletRequest, portletWindow);
logger.debug("Replaying cached content for resource request to {}", portletWindow);
@@ -681,6 +701,9 @@ public class PortletRendererImpl implements IPortletRenderer {
*/
@Override
public void doReset(IPortletWindowId portletWindowId, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
+
+ // Don't enforce config mode restriction because this is going to snap the portlet window into VIEW mode anyway.
+
final IPortletWindow portletWindow = this.portletWindowRegistry.getPortletWindow(httpServletRequest, portletWindowId);
if(portletWindow != null) {
portletWindow.setPortletMode(PortletMode.VIEW);
@@ -723,22 +746,43 @@ public class PortletRendererImpl implements IPortletRenderer {
return portletHttpServletRequestWrapper;
}
-
- protected void setupPortletWindow(HttpServletRequest httpServletRequest, IPortletWindow portletWindow, IPortletUrlBuilder portletUrl) {
- final PortletMode portletMode = portletUrl.getPortletMode();
+
+ /**
+ * Enforces CONFIG mode access control. If you don't have CONFIG, and the PortletWindow specifies CONFIG, throws
+ * IllegalAccessException. Otherwise does nothing.
+ *
+ * @param httpServletRequest the non-null current HttpServletRequest (for determining the requesting user)
+ * @param portletWindow a non-null portlet window that might be in CONFIG mode
+ * @throws org.jasig.portal.AuthorizationException if user is not permitted to access window specifying CONFIG mode
+ * @throws java.lang.IllegalArgumentException if the request or window are null
+ */
+ protected void enforceConfigPermission(final HttpServletRequest httpServletRequest,
+ final IPortletWindow portletWindow) {
+
+ Validate.notNull(httpServletRequest, "Servlet request must not be null to determine a remote user.");
+ Validate.notNull(portletWindow, "Portlet window must not be null to determine its mode.");
+
+ final PortletMode portletMode = portletWindow.getPortletMode();
if (portletMode != null) {
if (IPortletRenderer.CONFIG.equals(portletMode)) {
final IPerson person = this.personManager.getPerson(httpServletRequest);
-
+
final EntityIdentifier ei = person.getEntityIdentifier();
final AuthorizationService authorizationService = AuthorizationService.instance();
final IAuthorizationPrincipal ap = authorizationService.newPrincipal(ei.getKey(), ei.getType());
-
+
final IPortletEntity portletEntity = portletWindow.getPortletEntity();
final IPortletDefinition portletDefinition = portletEntity.getPortletDefinition();
-
+
if (!ap.canConfigure(portletDefinition.getPortletDefinitionId().getStringId())) {
- throw new AuthorizationException(person.getUserName() + " does not have permission to render '" + portletDefinition.getFName() + "' in " + portletMode + " PortletMode");
+ logger.error("User {} attempted to use portlet {} in {} mode " +
+ "but lacks permission to use that mode. " +
+ "THIS MAY BE AN ATTEMPT TO EXPLOIT A HISTORICAL SECURITY FLAW. " +
+ "YOU SHOULD PROBABLY FIGURE OUT WHO THIS USER IS AND" +
+ " WHY THEY ARE TRYING TO ACCESS UNAUTHORIZED MODES ILLICITLY. NAUGHTY!",
+ person, portletDefinition.getFName(), portletMode);
+ throw new AuthorizationException(person.getUserName() + " does not have permission to use '" +
+ portletDefinition.getFName() + "' in " + portletMode + " PortletMode");
}
}
}
--
1.8.4.2
From aaa94724f31d5a7cc98ad4e090f4e051f3017055 Mon Sep 17 00:00:00 2001
From: Andrew Petro <apetro@wisc.edu>
Date: Fri, 16 May 2014 09:55:06 -0500
Subject: [PATCH] Enforce CONFIG permission.
---
.../portal/portlet/rendering/IPortletRenderer.java | 10 ++++
.../portlet/rendering/PortletRendererImpl.java | 60 ++++++++++++++++++----
2 files changed, 61 insertions(+), 9 deletions(-)
diff --git a/uportal-war/src/main/java/org/jasig/portal/portlet/rendering/IPortletRenderer.java b/uportal-war/src/main/java/org/jasig/portal/portlet/rendering/IPortletRenderer.java
index 28af532..f337b13 100644
--- a/uportal-war/src/main/java/org/jasig/portal/portlet/rendering/IPortletRenderer.java
+++ b/uportal-war/src/main/java/org/jasig/portal/portlet/rendering/IPortletRenderer.java
@@ -107,6 +107,8 @@ public interface IPortletRenderer {
* @param portletWindowId Portlet to target with the action
* @param httpServletRequest The portal's request
* @param httpServletResponse The portal's response (nothing will be written to the response)
+ * @throws org.jasig.portal.AuthorizationException if the requesting user lacks permission to invoke the requested
+ * portlet window's portlet mode
*/
public long doAction(IPortletWindowId portletWindowId, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse);
@@ -116,6 +118,8 @@ public interface IPortletRenderer {
* @param portletWindowId Portlet to target with the action
* @param httpServletRequest The portal's request
* @param httpServletResponse The portal's response (nothing will be written to the response)
+ * @throws org.jasig.portal.AuthorizationException if the requesting user lacks permission to invoke the requested
+ * portlet window's portlet mode
*/
public long doEvent(IPortletWindowId portletWindowId, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Event event);
@@ -126,6 +130,8 @@ public interface IPortletRenderer {
* @param httpServletRequest The portal's request
* @param httpServletResponse The portal's response (nothing will be written to the response)
* @param portletOutputHandler The output handler to write to
+ * @throws org.jasig.portal.AuthorizationException if the requesting user lacks permission to invoke the requested
+ * portlet mode
*/
public PortletRenderResult doRenderMarkup(IPortletWindowId portletWindowId, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, PortletOutputHandler portletOutputHandler) throws IOException;
@@ -136,6 +142,8 @@ public interface IPortletRenderer {
* @param httpServletRequest The portal's request
* @param httpServletResponse The portal's response (nothing will be written to the response)
* @param portletOutputHandler The output handler to write to
+ * @throws org.jasig.portal.AuthorizationException if the requesting user does not have permission to access the
+ * portlet window's current mode.
*/
public PortletRenderResult doRenderHeader(IPortletWindowId portletWindowId, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, PortletOutputHandler portletOutputHandler) throws IOException;
@@ -147,6 +155,8 @@ public interface IPortletRenderer {
* @param httpServletResponse The portal's response (nothing will be written to the response)
* @param portletOutputHandler The output handler to write to
* @return The execution time for serving the resource
+ * @throws org.jasig.portal.AuthorizationException if the requesting user lacks permission to invoke the requested
+ * portlet window's portlet mode
*/
public long doServeResource(IPortletWindowId portletWindowId, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, PortletResourceOutputHandler portletOutputHandler) throws IOException;
diff --git a/uportal-war/src/main/java/org/jasig/portal/portlet/rendering/PortletRendererImpl.java b/uportal-war/src/main/java/org/jasig/portal/portlet/rendering/PortletRendererImpl.java
index 3038964..075fa24 100644
--- a/uportal-war/src/main/java/org/jasig/portal/portlet/rendering/PortletRendererImpl.java
+++ b/uportal-war/src/main/java/org/jasig/portal/portlet/rendering/PortletRendererImpl.java
@@ -34,6 +34,7 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.apache.commons.lang.Validate;
import org.apache.pluto.container.PortletContainer;
import org.apache.pluto.container.PortletContainerException;
import org.jasig.portal.AuthorizationException;
@@ -136,6 +137,9 @@ public class PortletRendererImpl implements IPortletRenderer {
this.portletCacheControlService.purgeCachedPortletData(portletWindowId, httpServletRequest);
final IPortletWindow portletWindow = this.portletWindowRegistry.getPortletWindow(httpServletRequest, portletWindowId);
+
+ enforceConfigPermission(httpServletRequest, portletWindow);
+
httpServletRequest = this.setupPortletRequest(httpServletRequest);
httpServletResponse = PortletHttpServletResponseWrapper.create(httpServletResponse, portletWindow);
@@ -183,6 +187,8 @@ public class PortletRendererImpl implements IPortletRenderer {
httpServletRequest = this.setupPortletRequest(httpServletRequest);
httpServletResponse = PortletHttpServletResponseWrapper.create(httpServletResponse, portletWindow);
+
+ enforceConfigPermission(httpServletRequest, portletWindow);
//Execute the action,
if (this.logger.isDebugEnabled()) {
@@ -214,7 +220,9 @@ public class PortletRendererImpl implements IPortletRenderer {
public PortletRenderResult doRenderHeader(IPortletWindowId portletWindowId,
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, PortletOutputHandler portletOutputHandler) throws IOException {
-
+
+ // enforce CONFIG mode restriction through its enforcement in doRender().
+
return doRender(portletWindowId,
httpServletRequest,
httpServletResponse,
@@ -231,7 +239,9 @@ public class PortletRendererImpl implements IPortletRenderer {
@Override
public PortletRenderResult doRenderMarkup(IPortletWindowId portletWindowId, HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, PortletOutputHandler portletOutputHandler) throws IOException {
-
+
+ // enforce CONFIG mode access restriction through its enforcement in the doRender() impl.
+
return doRender(portletWindowId,
httpServletRequest,
httpServletResponse,
@@ -366,6 +376,8 @@ public class PortletRendererImpl implements IPortletRenderer {
final IPortletWindow portletWindow = this.portletWindowRegistry.getPortletWindow(httpServletRequest, portletWindowId);
+ enforceConfigPermission(httpServletRequest, portletWindow);
+
/*
* If the portlet is rendering in EXCLUSIVE WindowState ignore the provided PortletOutputHandler and
* write directly to the response.
@@ -469,6 +481,9 @@ public class PortletRendererImpl implements IPortletRenderer {
if (logger.isDebugEnabled()) {
logger.debug("Replaying cached content for Render " + renderPart + " request to " + portletWindow);
}
+
+ enforceConfigPermission(httpServletRequest, portletWindow);
+
final long renderStartTime = System.nanoTime();
@@ -535,6 +550,8 @@ public class PortletRendererImpl implements IPortletRenderer {
final IPortletWindow portletWindow = this.portletWindowRegistry.getPortletWindow(httpServletRequest, portletWindowId);
+ enforceConfigPermission(httpServletRequest, portletWindow);
+
if (cacheState.isUseBrowserData()) {
return doResourceReplayBrowserContent(portletWindow, httpServletRequest, cacheState, portletOutputHandler);
}
@@ -620,6 +637,8 @@ public class PortletRendererImpl implements IPortletRenderer {
protected long doResourceReplayBrowserContent(IPortletWindow portletWindow, HttpServletRequest httpServletRequest,
CacheState<CachedPortletResourceData<Long>, Long> cacheState,
PortletResourceOutputHandler portletOutputHandler) {
+
+ enforceConfigPermission(httpServletRequest, portletWindow);
if (logger.isDebugEnabled()) {
logger.debug("Sending 304 for resource request to " + portletWindow);
@@ -653,6 +672,8 @@ public class PortletRendererImpl implements IPortletRenderer {
protected Long doResourceReplayCachedContent(IPortletWindow portletWindow,
HttpServletRequest httpServletRequest, CacheState<CachedPortletResourceData<Long>, Long> cacheState,
PortletResourceOutputHandler portletOutputHandler, long baseExecutionTime) throws IOException {
+
+ enforceConfigPermission(httpServletRequest, portletWindow);
if (logger.isDebugEnabled()) {
logger.debug("Replaying cached content for resource request to " + portletWindow);
@@ -695,6 +716,8 @@ public class PortletRendererImpl implements IPortletRenderer {
*/
@Override
public void doReset(IPortletWindowId portletWindowId, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
+
+ // Don't enforce config mode restriction because this is going to snap the portlet window into VIEW mode anyway.
final IPortletWindow portletWindow = this.portletWindowRegistry.getPortletWindow(httpServletRequest, portletWindowId);
if(portletWindow != null) {
portletWindow.setPortletMode(PortletMode.VIEW);
@@ -737,22 +760,41 @@ public class PortletRendererImpl implements IPortletRenderer {
return portletHttpServletRequestWrapper;
}
-
- protected void setupPortletWindow(HttpServletRequest httpServletRequest, IPortletWindow portletWindow, IPortletUrlBuilder portletUrl) {
- final PortletMode portletMode = portletUrl.getPortletMode();
+
+ /**
+ * Enforces CONFIG mode access control. If you don't have CONFIG, and the PortletWindow specifies CONFIG, throws
+ * IllegalAccessException. Otherwise does nothing.
+ *
+ * @param httpServletRequest the non-null current HttpServletRequest (for determining the requesting user)
+ * @param portletWindow a non-null portlet window that might be in CONFIG mode
+ * @throws org.jasig.portal.AuthorizationException if user is not permitted to access window specifying CONFIG mode
+ * @throws java.lang.IllegalArgumentException if the request or window are null
+ */
+ protected void enforceConfigPermission(final HttpServletRequest httpServletRequest,
+ final IPortletWindow portletWindow) {
+
+ Validate.notNull(httpServletRequest, "Servlet request must not be null to determine a remote user.");
+ Validate.notNull(portletWindow, "Portlet window must not be null to determine its mode.");
+
+ final PortletMode portletMode = portletWindow.getPortletMode();
if (portletMode != null) {
if (IPortletRenderer.CONFIG.equals(portletMode)) {
final IPerson person = this.personManager.getPerson(httpServletRequest);
-
final EntityIdentifier ei = person.getEntityIdentifier();
final AuthorizationService authorizationService = AuthorizationService.instance();
final IAuthorizationPrincipal ap = authorizationService.newPrincipal(ei.getKey(), ei.getType());
-
final IPortletEntity portletEntity = portletWindow.getPortletEntity();
final IPortletDefinition portletDefinition = portletEntity.getPortletDefinition();
-
if (!ap.canConfigure(portletDefinition.getPortletDefinitionId().getStringId())) {
- throw new AuthorizationException(person.getUserName() + " does not have permission to render '" + portletDefinition.getFName() + "' in " + portletMode + " PortletMode");
+ logger.error("User " + person +
+ " attempted to use portlet " + portletDefinition.getFName() +
+ " in " + portletMode + " mode " +
+ "but lacks permission to use that mode. " +
+ "THIS MAY BE AN ATTEMPT TO EXPLOIT A HISTORICAL SECURITY FLAW. " +
+ "YOU SHOULD PROBABLY FIGURE OUT WHO THIS USER IS AND" +
+ " WHY THEY ARE TRYING TO ACCESS UNAUTHORIZED MODES ILLICITLY. NAUGHTY!");
+ throw new AuthorizationException(person.getUserName() + " does not have permission to use '" +
+ portletDefinition.getFName() + "' in " + portletMode + " PortletMode");
}
}
}
--
1.8.4.2
/**
* Licensed to Jasig under one or more contributor license
* agreements. See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* Jasig 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.jasig.portal.portlet.rendering;
import java.io.IOException;
import java.io.Writer;
import javax.portlet.CacheControl;
import javax.portlet.Event;
import javax.portlet.PortletException;
import javax.portlet.PortletMode;
import javax.portlet.PortletRequest;
import javax.portlet.PortletSession;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.lang.Validate;
import org.apache.pluto.container.PortletContainer;
import org.apache.pluto.container.PortletContainerException;
import org.jasig.portal.AuthorizationException;
import org.jasig.portal.EntityIdentifier;
import org.jasig.portal.api.portlet.PortletDelegationLocator;
import org.jasig.portal.events.IPortletExecutionEventFactory;
import org.jasig.portal.portlet.PortletDispatchException;
import org.jasig.portal.portlet.container.cache.CacheControlImpl;
import org.jasig.portal.portlet.container.cache.CacheState;
import org.jasig.portal.portlet.container.cache.CachedPortletData;
import org.jasig.portal.portlet.container.cache.CachedPortletResourceData;
import org.jasig.portal.portlet.container.cache.CachingPortletOutputHandler;
import org.jasig.portal.portlet.container.cache.CachingPortletResourceOutputHandler;
import org.jasig.portal.portlet.container.cache.HeaderSettingCacheControl;
import org.jasig.portal.portlet.container.cache.IPortletCacheControlService;
import org.jasig.portal.portlet.container.cache.PortletCachingHeaderUtils;
import org.jasig.portal.portlet.container.services.AdministrativeRequestListenerController;
import org.jasig.portal.portlet.om.IPortletDefinition;
import org.jasig.portal.portlet.om.IPortletEntity;
import org.jasig.portal.portlet.om.IPortletWindow;
import org.jasig.portal.portlet.om.IPortletWindowId;
import org.jasig.portal.portlet.registry.IPortletWindowRegistry;
import org.jasig.portal.portlet.session.PortletSessionAdministrativeRequestListener;
import org.jasig.portal.security.IAuthorizationPrincipal;
import org.jasig.portal.security.IPerson;
import org.jasig.portal.security.IPersonManager;
import org.jasig.portal.services.AuthorizationService;
import org.jasig.portal.url.IPortalRequestInfo;
import org.jasig.portal.url.IPortletUrlBuilder;
import org.jasig.portal.url.IUrlSyntaxProvider;
import org.jasig.portal.url.ParameterMap;
import org.jasig.portal.utils.web.PortletHttpServletRequestWrapper;
import org.jasig.portal.utils.web.PortletHttpServletResponseWrapper;
import org.jasig.portal.utils.web.PortletMimeHttpServletResponseWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* Executes methods on portlets using Pluto
*
* @author Eric Dalquist
* @version $Revision$
*/
@Service
public class PortletRendererImpl implements IPortletRenderer {
protected final Log logger = LogFactory.getLog(this.getClass());
private IPersonManager personManager;
private IPortletWindowRegistry portletWindowRegistry;
private PortletContainer portletContainer;
private PortletDelegationLocator portletDelegationLocator;
private IPortletCacheControlService portletCacheControlService;
private IPortletExecutionEventFactory portalEventFactory;
private IUrlSyntaxProvider urlSyntaxProvider;
@Autowired
public void setUrlSyntaxProvider(IUrlSyntaxProvider urlSyntaxProvider) {
this.urlSyntaxProvider = urlSyntaxProvider;
}
@Autowired
public void setPortalEventFactory(IPortletExecutionEventFactory portalEventFactory) {
this.portalEventFactory = portalEventFactory;
}
@Autowired
public void setPersonManager(IPersonManager personManager) {
this.personManager = personManager;
}
@Autowired
public void setPortletWindowRegistry(IPortletWindowRegistry portletWindowRegistry) {
this.portletWindowRegistry = portletWindowRegistry;
}
@Autowired
public void setPortletContainer(PortletContainer portletContainer) {
this.portletContainer = portletContainer;
}
@Autowired
public void setPortletDelegationLocator(PortletDelegationLocator portletDelegationLocator) {
this.portletDelegationLocator = portletDelegationLocator;
}
/**
* @param portletCacheControlService the portletCacheControlService to set
*/
@Autowired
public void setPortletCacheControlService(
IPortletCacheControlService portletCacheControlService) {
this.portletCacheControlService = portletCacheControlService;
}
/*
* PLT 22.1 If the content of a portlet is cached and the portlet is target of request
* with an action-type semantic (e.g. an action or event call), the portlet container should discard the cache and
* invoke the corresponding request handling methods of the portlet like processAction,or processEvent.
*
* (non-Javadoc)
* @see org.jasig.portal.channels.portlet.IPortletRenderer#doAction(org.jasig.portal.portlet.om.IPortletWindowId, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
public long doAction(IPortletWindowId portletWindowId, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
this.portletCacheControlService.purgeCachedPortletData(portletWindowId, httpServletRequest);
final IPortletWindow portletWindow = this.portletWindowRegistry.getPortletWindow(httpServletRequest, portletWindowId);
enforceConfigPermission(httpServletRequest, portletWindow);
httpServletRequest = this.setupPortletRequest(httpServletRequest);
httpServletResponse = PortletHttpServletResponseWrapper.create(httpServletResponse, portletWindow);
//Execute the action,
if (this.logger.isDebugEnabled()) {
this.logger.debug("Executing portlet action for window '" + portletWindow + "'");
}
final long start = System.nanoTime();
try {
this.portletContainer.doAction(portletWindow.getPlutoPortletWindow(), httpServletRequest, httpServletResponse);
}
catch (PortletException pe) {
throw new PortletDispatchException("The portlet window '" + portletWindow + "' threw an exception while executing action.", portletWindow, pe);
}
catch (PortletContainerException pce) {
throw new PortletDispatchException("The portlet container threw an exception while executing action on portlet window '" + portletWindow + "'.", portletWindow, pce);
}
catch (IOException ioe) {
throw new PortletDispatchException("The portlet window '" + portletWindow + "' threw an exception while executing action.", portletWindow, ioe);
}
final long executionTime = System.nanoTime() - start;
this.portalEventFactory.publishPortletActionExecutionEvent(httpServletRequest, this, portletWindowId, executionTime);
return executionTime;
}
/*
* PLT 22.1 If the content of a portlet is cached and the portlet is target of request
* with an action-type semantic (e.g. an action or event call), the portlet container should discard the cache and
* invoke the corresponding request handling methods of the portlet like processAction,or processEvent.
*
* (non-Javadoc)
* @see org.jasig.portal.portlet.rendering.IPortletRenderer#doEvent(org.jasig.portal.portlet.om.IPortletWindowId, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.portlet.Event)
*/
@Override
public long doEvent(IPortletWindowId portletWindowId, HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, Event event) {
this.portletCacheControlService.purgeCachedPortletData(portletWindowId, httpServletRequest);
final IPortletWindow portletWindow = this.portletWindowRegistry.getPortletWindow(httpServletRequest, portletWindowId);
httpServletRequest = this.setupPortletRequest(httpServletRequest);
httpServletResponse = PortletHttpServletResponseWrapper.create(httpServletResponse, portletWindow);
enforceConfigPermission(httpServletRequest, portletWindow);
//Execute the action,
if (this.logger.isDebugEnabled()) {
this.logger.debug("Executing portlet event for window '" + portletWindow + "'");
}
final long start = System.nanoTime();
try {
this.portletContainer.doEvent(portletWindow.getPlutoPortletWindow(), httpServletRequest, httpServletResponse, event);
}
catch (PortletException pe) {
throw new PortletDispatchException("The portlet window '" + portletWindow + "' threw an exception while executing event.", portletWindow, pe);
}
catch (PortletContainerException pce) {
throw new PortletDispatchException("The portlet container threw an exception while executing event on portlet window '" + portletWindow + "'.", portletWindow, pce);
}
catch (IOException ioe) {
throw new PortletDispatchException("The portlet window '" + portletWindow + "' threw an exception while executing event.", portletWindow, ioe);
}
final long executionTime = System.nanoTime() - start;
this.portalEventFactory.publishPortletEventExecutionEvent(httpServletRequest, this, portletWindowId, executionTime, event.getQName());
return executionTime;
}
@Override
public PortletRenderResult doRenderHeader(IPortletWindowId portletWindowId,
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, PortletOutputHandler portletOutputHandler) throws IOException {
// enforce CONFIG mode restriction through its enforcement in doRender().
return doRender(portletWindowId,
httpServletRequest,
httpServletResponse,
portletOutputHandler,
RenderPart.HEADERS);
}
/**
* Interacts with the {@link IPortletCacheControlService} to determine if the markup should come from cache or not.
* If cached data doesn't exist or is expired, this delegates to {@link #doRenderMarkupInternal(IPortletWindowId, HttpServletRequest, HttpServletResponse, Writer)}.
* @throws IOException
*/
@Override
public PortletRenderResult doRenderMarkup(IPortletWindowId portletWindowId, HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, PortletOutputHandler portletOutputHandler) throws IOException {
// enforce CONFIG mode access restriction through its enforcement in the doRender() impl.
return doRender(portletWindowId,
httpServletRequest,
httpServletResponse,
portletOutputHandler,
RenderPart.MARKUP);
}
/**
* Describes the part of the render request and defines the part specific behaviors
*/
protected enum RenderPart {
HEADERS(PortletRequest.RENDER_HEADERS) {
@Override
public CacheState<CachedPortletData<PortletRenderResult>, PortletRenderResult> getCacheState(
IPortletCacheControlService portletCacheControlService, HttpServletRequest request,
IPortletWindowId portletWindowId) {
return portletCacheControlService.getPortletRenderHeaderState(request, portletWindowId);
}
@Override
public void cachePortletOutput(IPortletCacheControlService portletCacheControlService,
IPortletWindowId portletWindowId, HttpServletRequest request,
CacheState<CachedPortletData<PortletRenderResult>, PortletRenderResult> cacheState, CachedPortletData<PortletRenderResult> cachedPortletData) {
portletCacheControlService.cachePortletRenderHeaderOutput(portletWindowId,
request,
cacheState,
cachedPortletData);
}
@Override
public void publishRenderExecutionEvent(IPortletExecutionEventFactory portalEventFactory, PortletRendererImpl source,
HttpServletRequest request, IPortletWindowId portletWindowId, long executionTime,
boolean targeted, boolean cached) {
portalEventFactory.publishPortletRenderHeaderExecutionEvent(request,
source,
portletWindowId,
executionTime,
targeted,
cached);
}
},
MARKUP(PortletRequest.RENDER_MARKUP) {
@Override
public CacheState<CachedPortletData<PortletRenderResult>, PortletRenderResult> getCacheState(
IPortletCacheControlService portletCacheControlService, HttpServletRequest request,
IPortletWindowId portletWindowId) {
return portletCacheControlService.getPortletRenderState(request, portletWindowId);
}
@Override
public void cachePortletOutput(IPortletCacheControlService portletCacheControlService,
IPortletWindowId portletWindowId, HttpServletRequest request,
CacheState<CachedPortletData<PortletRenderResult>, PortletRenderResult> cacheState, CachedPortletData<PortletRenderResult> cachedPortletData) {
portletCacheControlService.cachePortletRenderOutput(portletWindowId,
request,
cacheState,
cachedPortletData);
}
@Override
public void publishRenderExecutionEvent(IPortletExecutionEventFactory portalEventFactory, PortletRendererImpl source,
HttpServletRequest request, IPortletWindowId portletWindowId, long executionTime,
boolean targeted, boolean cached) {
portalEventFactory.publishPortletRenderExecutionEvent(request,
source,
portletWindowId,
executionTime,
targeted,
cached);
}
};
private final String renderPart;
private RenderPart(String renderPart) {
this.renderPart = renderPart;
}
/**
* Get the cache state for the request
*/
public abstract CacheState<CachedPortletData<PortletRenderResult>, PortletRenderResult> getCacheState(
IPortletCacheControlService portletCacheControlService, HttpServletRequest request,
IPortletWindowId portletWindowId);
/**
* Cache the portlet output
*/
public abstract void cachePortletOutput(IPortletCacheControlService portletCacheControlService,
IPortletWindowId portletWindowId, HttpServletRequest request,
CacheState<CachedPortletData<PortletRenderResult>, PortletRenderResult> cacheState, CachedPortletData<PortletRenderResult> cachedPortletData);
/**
* Public portlet event
*/
public abstract void publishRenderExecutionEvent(IPortletExecutionEventFactory portalEventFactory,
PortletRendererImpl source, HttpServletRequest request, IPortletWindowId portletWindowId, long executionTime,
boolean targeted, boolean cached);
/**
* @return The {@link PortletRequest#RENDER_PART} name
*/
public final String getRenderPart() {
return renderPart;
}
/**
* Determine the {@link RenderPart} from the {@link PortletRequest#RENDER_PART}
*/
public static RenderPart getRenderPart(String renderPart) {
if (PortletRequest.RENDER_HEADERS.equals(renderPart)) {
return HEADERS;
}
if (PortletRequest.RENDER_MARKUP.equals(renderPart)) {
return MARKUP;
}
throw new IllegalArgumentException("Unknown " + PortletRequest.RENDER_PART + " specified: " + renderPart);
}
}
protected PortletRenderResult doRender(IPortletWindowId portletWindowId, HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, PortletOutputHandler portletOutputHandler, RenderPart renderPart)
throws IOException {
final CacheState<CachedPortletData<PortletRenderResult>, PortletRenderResult> cacheState = renderPart
.getCacheState(this.portletCacheControlService, httpServletRequest, portletWindowId);
final IPortletWindow portletWindow = this.portletWindowRegistry.getPortletWindow(httpServletRequest, portletWindowId);
enforceConfigPermission(httpServletRequest, portletWindow);
/*
* If the portlet is rendering in EXCLUSIVE WindowState ignore the provided PortletOutputHandler and
* write directly to the response.
*
* THIS IS VERY BAD AND SHOULD BE DEPRECATED ALONG WITH EXCLUSIVE WINDOW STATE
*/
if (EXCLUSIVE.equals(portletWindow.getWindowState())) {
portletOutputHandler = new ResourcePortletOutputHandler(httpServletResponse);
}
if (cacheState.isUseCachedData()) {
return doRenderReplayCachedContent(portletWindow, httpServletRequest, cacheState, portletOutputHandler, renderPart, 0);
}
final int cacheSizeThreshold = this.portletCacheControlService.getCacheSizeThreshold();
final CachingPortletOutputHandler cachingPortletOutputHandler = new CachingPortletOutputHandler(portletOutputHandler, cacheSizeThreshold);
final CacheControl cacheControl = cacheState.getCacheControl();
//Setup the request and response
httpServletRequest = this.setupPortletRequest(httpServletRequest);
httpServletResponse = PortletMimeHttpServletResponseWrapper.create(httpServletResponse, portletWindow, portletOutputHandler, cacheControl);
httpServletRequest.setAttribute(ATTRIBUTE__PORTLET_CACHE_CONTROL, cacheControl);
httpServletRequest.setAttribute(ATTRIBUTE__PORTLET_OUTPUT_HANDLER, cachingPortletOutputHandler);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Rendering portlet body for window '" + portletWindow + "'");
}
final long renderStartTime = System.nanoTime();
try {
httpServletRequest.setAttribute(PortletRequest.RENDER_PART, renderPart.getRenderPart());
this.portletContainer.doRender(portletWindow.getPlutoPortletWindow(), httpServletRequest, httpServletResponse);
}
catch (PortletException pe) {
throw new PortletDispatchException("The portlet window '" + portletWindow + "' threw an exception while executing renderMarkup.", portletWindow, pe);
}
catch (PortletContainerException pce) {
throw new PortletDispatchException("The portlet container threw an exception while executing renderMarkup on portlet window '" + portletWindow + "'.", portletWindow, pce);
}
catch (IOException ioe) {
throw new PortletDispatchException("The portlet window '" + portletWindow + "' threw an exception while executing renderMarkup.", portletWindow, ioe);
}
final long executionTime = System.nanoTime() - renderStartTime;
//See if the portlet signaled to use the cached content
final boolean useCachedContent = cacheControl.useCachedContent();
if (useCachedContent) {
final CachedPortletData<PortletRenderResult> cachedPortletData = cacheState.getCachedPortletData();
if (cachedPortletData == null) {
throw new PortletDispatchException(
"The portlet window '" + portletWindow + "' indicated via CacheControl#useCachedContent " +
"that the portal should render cached content, however there is no cached content to return. " +
"This is a portlet bug.",
portletWindow);
}
//Update the expiration time and re-store in the cache
cachedPortletData.updateExpirationTime(cacheControl.getExpirationTime());
renderPart.cachePortletOutput(portletCacheControlService, portletWindowId, httpServletRequest, cacheState, cachedPortletData);
return doRenderReplayCachedContent(portletWindow, httpServletRequest, cacheState, portletOutputHandler, renderPart, executionTime);
}
publishRenderEvent(portletWindow, httpServletRequest, renderPart, executionTime, false);
//Build the render result
final PortletRenderResult portletRenderResult = constructPortletRenderResult(httpServletRequest, executionTime);
//Check if the portlet's output should be cached
if (cacheState != null) {
boolean shouldCache = this.portletCacheControlService.shouldOutputBeCached(cacheControl);
if (shouldCache) {
final CachedPortletData<PortletRenderResult> cachedPortletData = cachingPortletOutputHandler
.getCachedPortletData(portletRenderResult, cacheControl);
if (cachedPortletData != null) {
renderPart.cachePortletOutput(portletCacheControlService,
portletWindowId,
httpServletRequest,
cacheState,
cachedPortletData);
}
}
}
return portletRenderResult;
}
/**
* Replay the cached content inside the {@link CachedPortletData} as the response to a doRender.
*/
protected PortletRenderResult doRenderReplayCachedContent(IPortletWindow portletWindow,
HttpServletRequest httpServletRequest, CacheState<CachedPortletData<PortletRenderResult>, PortletRenderResult> cacheState,
PortletOutputHandler portletOutputHandler, RenderPart renderPart, long baseExecutionTime) throws IOException {
if (logger.isDebugEnabled()) {
logger.debug("Replaying cached content for Render " + renderPart + " request to " + portletWindow);
}
enforceConfigPermission(httpServletRequest, portletWindow);
final long renderStartTime = System.nanoTime();
final CachedPortletData<PortletRenderResult> cachedPortletData = cacheState.getCachedPortletData();
cachedPortletData.replay(portletOutputHandler);
final long executionTime = baseExecutionTime + (System.nanoTime() - renderStartTime);
publishRenderEvent(portletWindow, httpServletRequest, renderPart, executionTime, true);
final PortletRenderResult portletResult = cachedPortletData.getPortletResult();
return new PortletRenderResult(portletResult, executionTime);
}
/**
* Publish the portlet render event
*/
protected void publishRenderEvent(IPortletWindow portletWindow, HttpServletRequest httpServletRequest,
RenderPart renderPart, long executionTime, boolean cached) {
final IPortletWindowId portletWindowId = portletWindow.getPortletWindowId();
//Determine if the portlet was targeted
final IPortalRequestInfo portalRequestInfo = this.urlSyntaxProvider.getPortalRequestInfo(httpServletRequest);
final boolean targeted = portletWindowId.equals(portalRequestInfo.getTargetedPortletWindowId());
renderPart.publishRenderExecutionEvent(this.portalEventFactory,
this,
httpServletRequest,
portletWindowId,
executionTime,
targeted,
cached);
}
/**
* Construct a {@link PortletRenderResult} from information in the {@link HttpServletRequest}.
* The second argument is how long the render action took.
*
* @param httpServletRequest
* @param renderTime
* @return an appropriate {@link PortletRenderResult}, never null
*/
protected PortletRenderResult constructPortletRenderResult(HttpServletRequest httpServletRequest, long renderTime) {
final String title = (String)httpServletRequest.getAttribute(IPortletRenderer.ATTRIBUTE__PORTLET_TITLE);
final String newItemCountString = (String)httpServletRequest.getAttribute(IPortletRenderer.ATTRIBUTE__PORTLET_NEW_ITEM_COUNT);
final int newItemCount;
if (newItemCountString != null && StringUtils.isNumeric(newItemCountString)) {
newItemCount = Integer.parseInt(newItemCountString);
} else {
newItemCount = 0;
}
final String link = (String)httpServletRequest.getAttribute(IPortletRenderer.ATTRIBUTE__PORTLET_LINK);
return new PortletRenderResult(title, link, newItemCount, renderTime);
}
@Override
public long doServeResource(IPortletWindowId portletWindowId, HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, PortletResourceOutputHandler portletOutputHandler) throws IOException {
final CacheState<CachedPortletResourceData<Long>, Long> cacheState = this.portletCacheControlService
.getPortletResourceState(httpServletRequest, portletWindowId);
final IPortletWindow portletWindow = this.portletWindowRegistry.getPortletWindow(httpServletRequest, portletWindowId);
enforceConfigPermission(httpServletRequest, portletWindow);
if (cacheState.isUseBrowserData()) {
return doResourceReplayBrowserContent(portletWindow, httpServletRequest, cacheState, portletOutputHandler);
}
if (cacheState.isUseCachedData()) {
return doResourceReplayCachedContent(portletWindow, httpServletRequest, cacheState, portletOutputHandler, 0);
}
final int cacheSizeThreshold = this.portletCacheControlService.getCacheSizeThreshold();
final CachingPortletResourceOutputHandler cachingPortletOutputHandler = new CachingPortletResourceOutputHandler(portletOutputHandler, cacheSizeThreshold);
CacheControl cacheControl = cacheState.getCacheControl();
//Wrap the cache control so it immediately sets the caching related response headers
cacheControl = new HeaderSettingCacheControl(cacheControl, cachingPortletOutputHandler);
//Setup the request and response
httpServletRequest = this.setupPortletRequest(httpServletRequest);
httpServletResponse = PortletResourceHttpServletResponseWrapper.create(httpServletResponse, portletWindow, portletOutputHandler, cacheControl);
httpServletRequest.setAttribute(ATTRIBUTE__PORTLET_CACHE_CONTROL, cacheControl);
httpServletRequest.setAttribute(ATTRIBUTE__PORTLET_OUTPUT_HANDLER, cachingPortletOutputHandler);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Executing resource request for window '" + portletWindow + "'");
}
final long start = System.nanoTime();
try {
this.portletContainer.doServeResource(portletWindow.getPlutoPortletWindow(), httpServletRequest, httpServletResponse);
}
catch (PortletException pe) {
throw new PortletDispatchException("The portlet window '" + portletWindow + "' threw an exception while executing serveResource.", portletWindow, pe);
}
catch (PortletContainerException pce) {
throw new PortletDispatchException("The portlet container threw an exception while executing serveResource on portlet window '" + portletWindow + "'.", portletWindow, pce);
}
catch (IOException ioe) {
throw new PortletDispatchException("The portlet window '" + portletWindow + "' threw an exception while executing serveResource.", portletWindow, ioe);
}
final long executionTime = System.nanoTime() - start;
//See if the portlet signaled to use the cached content
final boolean useCachedContent = cacheControl.useCachedContent();
if (useCachedContent) {
final CachedPortletResourceData<Long> cachedPortletResourceData = cacheState.getCachedPortletData();
if (cachedPortletResourceData != null) {
//Update the expiration time and re-store in the cache
final CachedPortletData<Long> cachedPortletData = cachedPortletResourceData.getCachedPortletData();
cachedPortletData.updateExpirationTime(cacheControl.getExpirationTime());
this.portletCacheControlService.cachePortletResourceOutput(portletWindowId, httpServletRequest, cacheState, cachedPortletResourceData);
}
if (cacheState.isBrowserSetEtag()) {
//Browser-side content matches, send a 304
return doResourceReplayBrowserContent(portletWindow, httpServletRequest, cacheState, portletOutputHandler);
}
return doResourceReplayCachedContent(portletWindow, httpServletRequest, cacheState, cachingPortletOutputHandler, executionTime);
}
publishResourceEvent(portletWindow, httpServletRequest, executionTime, false, false);
if (cacheState != null) {
boolean shouldCache = this.portletCacheControlService.shouldOutputBeCached(cacheControl);
if (shouldCache) {
final CachedPortletResourceData<Long> cachedPortletResourceData = cachingPortletOutputHandler
.getCachedPortletResourceData(executionTime, cacheControl);
if (cachedPortletResourceData != null) {
this.portletCacheControlService.cachePortletResourceOutput(portletWindowId,
httpServletRequest,
cacheState,
cachedPortletResourceData);
}
}
}
return executionTime;
}
protected long doResourceReplayBrowserContent(IPortletWindow portletWindow, HttpServletRequest httpServletRequest,
CacheState<CachedPortletResourceData<Long>, Long> cacheState,
PortletResourceOutputHandler portletOutputHandler) {
enforceConfigPermission(httpServletRequest, portletWindow);
if (logger.isDebugEnabled()) {
logger.debug("Sending 304 for resource request to " + portletWindow);
}
if (portletOutputHandler.isCommitted()) {
throw new IllegalStateException("Attempting to send 304 but response is already committed");
}
final long start = System.nanoTime();
portletOutputHandler.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
final CachedPortletResourceData<Long> cachedPortletResourceData = cacheState.getCachedPortletData();
if (cachedPortletResourceData != null) {
//Freshen up the various caching related headers
final CachedPortletData<Long> cachedPortletData = cachedPortletResourceData.getCachedPortletData();
PortletCachingHeaderUtils.setCachingHeaders(cachedPortletData, portletOutputHandler);
}
final long executionTime = System.nanoTime() - start;
publishResourceEvent(portletWindow, httpServletRequest, executionTime, true, false);
return executionTime;
}
/**
* Replay the cached content inside the {@link CachedPortletData} as the response to a doResource.
*/
protected Long doResourceReplayCachedContent(IPortletWindow portletWindow,
HttpServletRequest httpServletRequest, CacheState<CachedPortletResourceData<Long>, Long> cacheState,
PortletResourceOutputHandler portletOutputHandler, long baseExecutionTime) throws IOException {
enforceConfigPermission(httpServletRequest, portletWindow);
if (logger.isDebugEnabled()) {
logger.debug("Replaying cached content for resource request to " + portletWindow);
}
final long renderStartTime = System.nanoTime();
final CachedPortletResourceData<Long> cachedPortletResourceData = cacheState.getCachedPortletData();
if (cachedPortletResourceData == null) {
throw new PortletDispatchException(
"The portlet window '" + portletWindow + "' indicated via CacheControl#useCachedContent " +
"that the portal should render cached content, however there is no cached content to return. " +
"This is a portlet bug.",
portletWindow);
}
cachedPortletResourceData.replay(portletOutputHandler);
final long executionTime = baseExecutionTime + (System.nanoTime() - renderStartTime);
publishResourceEvent(portletWindow, httpServletRequest, executionTime, false, true);
return executionTime;
}
/**
* Publish the portlet resource event
*/
protected void publishResourceEvent(IPortletWindow portletWindow, HttpServletRequest httpServletRequest,
long executionTime, boolean usedBrowserCache, boolean usedPortalCache) {
final IPortletWindowId portletWindowId = portletWindow.getPortletWindowId();
this.portalEventFactory.publishPortletResourceExecutionEvent(httpServletRequest, this, portletWindowId, executionTime, usedBrowserCache, usedPortalCache);
}
/*
* (non-Javadoc)
* @see org.jasig.portal.portlet.rendering.IPortletRenderer#doReset(org.jasig.portal.portlet.om.IPortletWindowId, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
public void doReset(IPortletWindowId portletWindowId, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
// Don't enforce config mode restriction because this is going to snap the portlet window into VIEW mode anyway.
final IPortletWindow portletWindow = this.portletWindowRegistry.getPortletWindow(httpServletRequest, portletWindowId);
if(portletWindow != null) {
portletWindow.setPortletMode(PortletMode.VIEW);
portletWindow.setRenderParameters(new ParameterMap());
portletWindow.setExpirationCache(null);
httpServletRequest = this.setupPortletRequest(httpServletRequest);
httpServletResponse = PortletHttpServletResponseWrapper.create(httpServletResponse, portletWindow);
httpServletRequest.setAttribute(AdministrativeRequestListenerController.DEFAULT_LISTENER_KEY_ATTRIBUTE, "sessionActionListener");
httpServletRequest.setAttribute(PortletSessionAdministrativeRequestListener.ACTION, PortletSessionAdministrativeRequestListener.SessionAction.CLEAR);
httpServletRequest.setAttribute(PortletSessionAdministrativeRequestListener.SCOPE, PortletSession.PORTLET_SCOPE);
//TODO modify PortletContainer.doAdmin to create a specific "admin" req/res object and context so we don't have to fake it with a render req
//These are required for a render request to be created and admin requests use a render request under the hood
final String characterEncoding = httpServletResponse.getCharacterEncoding();
httpServletRequest.setAttribute(ATTRIBUTE__PORTLET_OUTPUT_HANDLER, new RenderPortletOutputHandler(characterEncoding));
httpServletRequest.setAttribute(ATTRIBUTE__PORTLET_CACHE_CONTROL, new CacheControlImpl());
try {
this.portletContainer.doAdmin(portletWindow.getPlutoPortletWindow(), httpServletRequest, httpServletResponse);
}
catch (PortletException pe) {
throw new PortletDispatchException("The portlet window '" + portletWindow + "' threw an exception while executing admin command to clear session.", portletWindow, pe);
}
catch (PortletContainerException pce) {
throw new PortletDispatchException("The portlet container threw an exception while executing admin command to clear session on portlet window '" + portletWindow + "'.", portletWindow, pce);
}
catch (IOException ioe) {
throw new PortletDispatchException("The portlet window '" + portletWindow + "' threw an exception while executing admin command to clear session.", portletWindow, ioe);
}
} else {
logger.debug("ignoring doReset as portletWindowRegistry#getPortletWindow returned a null result for portletWindowId " + portletWindowId);
}
}
protected HttpServletRequest setupPortletRequest(HttpServletRequest httpServletRequest) {
final HttpServletRequest portletHttpServletRequestWrapper = PortletHttpServletRequestWrapper.create(httpServletRequest);
portletHttpServletRequestWrapper.setAttribute(PortletDelegationLocator.PORTLET_DELECATION_LOCATOR_ATTR, this.portletDelegationLocator);
return portletHttpServletRequestWrapper;
}
/**
* Enforces CONFIG mode access control. If you don't have CONFIG, and the PortletWindow specifies CONFIG, throws
* IllegalAccessException. Otherwise does nothing.
*
* @param httpServletRequest the non-null current HttpServletRequest (for determining the requesting user)
* @param portletWindow a non-null portlet window that might be in CONFIG mode
* @throws org.jasig.portal.AuthorizationException if user is not permitted to access window specifying CONFIG mode
* @throws java.lang.IllegalArgumentException if the request or window are null
*/
protected void enforceConfigPermission(final HttpServletRequest httpServletRequest,
final IPortletWindow portletWindow) {
Validate.notNull(httpServletRequest, "Servlet request must not be null to determine a remote user.");
Validate.notNull(portletWindow, "Portlet window must not be null to determine its mode.");
final PortletMode portletMode = portletWindow.getPortletMode();
if (portletMode != null) {
if (IPortletRenderer.CONFIG.equals(portletMode)) {
final IPerson person = this.personManager.getPerson(httpServletRequest);
final EntityIdentifier ei = person.getEntityIdentifier();
final AuthorizationService authorizationService = AuthorizationService.instance();
final IAuthorizationPrincipal ap = authorizationService.newPrincipal(ei.getKey(), ei.getType());
final IPortletEntity portletEntity = portletWindow.getPortletEntity();
final IPortletDefinition portletDefinition = portletEntity.getPortletDefinition();
if (!ap.canConfigure(portletDefinition.getPortletDefinitionId().getStringId())) {
logger.error("User " + person +
" attempted to use portlet " + portletDefinition.getFName() +
" in " + portletMode + " mode " +
"but lacks permission to use that mode. " +
"THIS MAY BE AN ATTEMPT TO EXPLOIT A HISTORICAL SECURITY FLAW. " +
"YOU SHOULD PROBABLY FIGURE OUT WHO THIS USER IS AND" +
" WHY THEY ARE TRYING TO ACCESS UNAUTHORIZED MODES ILLICITLY. NAUGHTY!");
throw new AuthorizationException(person.getUserName() + " does not have permission to use '" +
portletDefinition.getFName() + "' in " + portletMode + " PortletMode");
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment