Skip to content

Instantly share code, notes, and snippets.

@apetro
Last active August 29, 2015 14:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save apetro/e49ece2ebc8ef0bdb31f to your computer and use it in GitHub Desktop.
Save apetro/e49ece2ebc8ef0bdb31f to your computer and use it in GitHub Desktop.
Vulnerability acknowledgement and patch instructions for CVE-2014-3416 illicit access to MANAGE portlets

CVE-2014-3416 - MANAGE permissions ineffectual

Short description:

Whereas the MANAGE permissions are intended to restrict who can MANAGE a given portlet publication, this restriction is reflected only superficially the UI layer and a user with any access to the "portlet-admin" portlet management portlet can through trivial URL manipulation view and edit any portlet publication, regardless of whether he or she has MANAGE permission on that portlet. One need not have MANAGE on a given portlet to view and edit its portlet definition. This renders the MANAGE permission ineffectual.

This problem is exacerbated by potential use of portlet preferences or portlet init parameters in specific portlets to store credentials.

This problem is greatly mitigated by the fact that delegated administration features in uPortal are seldom implemented by adopters.

Remediation:

Do any of these to block the vulnerability.

0. In practice this vulnerability may not apply to you

If you have no delegated portlet adminstrators intended to MANAGE a non-empty sub-set of your portlets, you have no users in a position to exploit this vulnerability. You should still consider patching, because latent vulnerabilities are bad, but you are not currently vulnerable to exploit.

1. Revoke SUBSCRIBE on portlet-admin

Revoke SUBSCRIBE on portlet-admin for any user who is not a Portal Administrator or who is not intentionally granted SUBSCRIBE on portlet-admin AND MANAGE on all portlets. That is, stop using delegated administration over portlets, or at least stop believing that it's providing any meaningful access restriction. Recognize that SUBSCRIBE on portlet-admin IS MANAGE on all portlets under CVE-2014-3416 .

2. Upgrade to uPortal 4.0.13.1 / 4.0.14 / latest master

That would be uPortal 4.0.13.1

or master after uPortal 4.1rc3 , towards uPortal 4.1, which includes the commit on master fixing the bug.

or uPortal 4.0.14, which includes the commit on 4.0-patches fixing the bug (NOT YET RELEASED).

The portlet-admin portlet is fixed in these releases to actually enforce MANAGE permissions rather than only using these to filter the UI listing of portlets.

3. Apply the fix from uPortal 4.0.14 as a patch to your local environment.

See the accompanying patch file or the referenced commit on GitHub. It touches a few files. You might be better off just upgrading to build on 4.0.14.

4. Evolve to a better architecture

A deeper fix would be to decompose the portlet-admin portlet into a more layered architecture, rely on a PortletManagementService, and enforce permissions in the service layer so that individual portlets and controllers are no longer responsible for security enforcement and so cannot neglect to enforce permissions over portlet management. This may be pursued in future uPortal product development.

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: Medium?

  • Impact: High: Where this vulnerability is present, it will often expose privileged credentials.
  • Possibility: Low: Most uPortal deployments have no users with more-than-zero but less-than-all MANAGE access, and so are not affected by this vulnerability since there are zero users in a position to illicitly access forbidden portlet registrations.
  • Ease: High: Very easy for a user with any access to portlet-admin.

DREAD model: 5/10

  • Damage potential: Read-write to the portlet publications, potentially with passwords, opportunities for SQL injection, opportunities for JavaScript injection : 8
  • Reproducibility: Fully reproducible: 10
  • Exploitability: Requires use of a delegated admin account: 1
  • Affected users: Affects very few uPortal adopters. Possibly none. : 1
  • Discoverability: Can be found out easily by users with access to portlet-admin; apparent from review of available source code: 5

Acknowledgements

Andrew Petro / the My UW-Madison team discovered this vulnerability. Drew Wills has developed the initial fix included in the 4.0.13.1 release and the patched branches.

From dd069c1728845b885f270ea96a4b8d1b5709a453 Mon Sep 17 00:00:00 2001
From: drewwills <wills.drew@gmail.com>
Date: Wed, 14 May 2014 05:09:39 -0700
Subject: [PATCH] UP-4066 Manage Portlets: Group and category selection use
permissions to get forest root
This change provides several necessary fixes for delegated portlet administration.
First, it updaes the 'Select Categories' and 'Select Groups' steps by filtering
the available choices based on the user's permissions. (In the past, Portlet
Administration users were almost always superusers.) Secondly -- and also in
furtherance of delegated portlet admin -- this change includes several additional
permissions checks during and at the end of the Portlet Manager publishing wizard.
These check are aimed at preventing a Portlet Manager user who is a non-superuser
from stepping outside the bounds of granted authority.
---
pom.xml | 6 ++
uportal-war/pom.xml | 5 +
.../layout/dlm/remoting/GroupListHelperImpl.java | 119 ++++++++++++++++++++-
.../layout/dlm/remoting/IGroupListHelper.java | 49 ++++++---
.../portal/layout/dlm/remoting/JsonEntityBean.java | 6 ++
.../portletadmin/PortletAdministrationHelper.java | 119 +++++++++++++++------
.../security/AuthorizationPrincipalHelper.java | 43 ++++++++
.../org/jasig/portal/security/IPermission.java | 40 +++++--
.../evaluator/PortalPermissionEvaluator.java | 2 +-
.../WEB-INF/flows/edit-portlet/edit-portlet.xml | 31 +++---
.../flows/portlet-manager/portlet-manager.xml | 28 +++--
11 files changed, 356 insertions(+), 92 deletions(-)
create mode 100644 uportal-war/src/main/java/org/jasig/portal/security/AuthorizationPrincipalHelper.java
diff --git a/pom.xml b/pom.xml
index f65d7de..7871bae 100755
--- a/pom.xml
+++ b/pom.xml
@@ -142,6 +142,7 @@
<commons-httpcomponents.version>4.2.4</commons-httpcomponents.version>
<commons-io.version>2.4</commons-io.version>
<commons-lang.version>2.6</commons-lang.version>
+ <commons-lang3.version>3.1</commons-lang3.version>
<commons-logging.version>1.1.3</commons-logging.version>
<commons-logging-api.version>1.1</commons-logging-api.version>
<commons-pool.version>1.6</commons-pool.version>
@@ -378,6 +379,11 @@
<version>${commons-lang.version}</version>
</dependency>
<dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ <version>${commons-lang3.version}</version>
+ </dependency>
+ <dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>${commons-logging.version}</version>
diff --git a/uportal-war/pom.xml b/uportal-war/pom.xml
index b4f49ac..c36cf22 100644
--- a/uportal-war/pom.xml
+++ b/uportal-war/pom.xml
@@ -116,6 +116,11 @@
</dependency>
<dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ </dependency>
+
+ <dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</dependency>
diff --git a/uportal-war/src/main/java/org/jasig/portal/layout/dlm/remoting/GroupListHelperImpl.java b/uportal-war/src/main/java/org/jasig/portal/layout/dlm/remoting/GroupListHelperImpl.java
index 9469f79..e26b09e 100644
--- a/uportal-war/src/main/java/org/jasig/portal/layout/dlm/remoting/GroupListHelperImpl.java
+++ b/uportal-war/src/main/java/org/jasig/portal/layout/dlm/remoting/GroupListHelperImpl.java
@@ -20,6 +20,7 @@
package org.jasig.portal.layout.dlm.remoting;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
@@ -35,7 +36,10 @@ import org.jasig.portal.groups.IEntityNameFinder;
import org.jasig.portal.groups.IGroupConstants;
import org.jasig.portal.groups.IGroupMember;
import org.jasig.portal.portlets.groupselector.EntityEnum;
+import org.jasig.portal.security.AuthorizationPrincipalHelper;
import org.jasig.portal.security.IAuthorizationPrincipal;
+import org.jasig.portal.security.IPermission;
+import org.jasig.portal.security.IPerson;
import org.jasig.portal.services.AuthorizationService;
import org.jasig.portal.services.EntityNameFinderService;
import org.jasig.portal.services.GroupService;
@@ -113,7 +117,120 @@ public class GroupListHelperImpl implements IGroupListHelper {
return bean;
}
-
+
+ @Override
+ public JsonEntityBean getIndividualBestRootEntity(final IPerson person,
+ final String groupType, final String permissionOwner,
+ final String permissionActivity) {
+ return getIndividualBestRootEntity(person, groupType, permissionOwner, new String[] { permissionActivity });
+ }
+
+ @Override
+ public JsonEntityBean getIndividualBestRootEntity(final IPerson person,
+ final String groupType, final String permissionOwner,
+ final String[] permissionActivities) {
+
+ if (log.isDebugEnabled()) {
+ log.debug("Choosing best root group for user='" + person.getUserName() +
+ "', groupType='" + groupType + "', permissionOwner='" +
+ permissionOwner + "', permissionActivities='" +
+ Arrays.toString(permissionActivities) + "'");
+ }
+
+ final IAuthorizationPrincipal principal = AuthorizationPrincipalHelper.principalFromUser(person);
+ final JsonEntityBean canonicalRootGroup = getRootEntity(groupType);
+
+ if (log.isDebugEnabled()) {
+ log.debug("Found for groupType='" + groupType +
+ "' the following canonicalRootGroup: " +
+ canonicalRootGroup);
+ }
+
+ // First check the appropriate canonical super-target for the specified type
+ String canonicalSuperTarget = null;
+ if (JsonEntityBean.ENTITY_GROUP.equals(groupType)) {
+ canonicalSuperTarget = IPermission.ALL_GROUPS_TARGET;
+ } else if (JsonEntityBean.ENTITY_CATEGORY.equals(groupType)) {
+ canonicalSuperTarget = IPermission.ALL_CATEGORIES_TARGET;
+ } else {
+ throw new RuntimeException("Unrecognized groupType: " + groupType);
+ }
+ if (log.isDebugEnabled()) {
+ log.debug("Identified for groupType='" + groupType +
+ "' the following canonicalSuperTarget: " +
+ canonicalSuperTarget);
+ }
+ for (String activity : permissionActivities) {
+ if (principal.hasPermission(permissionOwner, activity, canonicalSuperTarget)) {
+ return canonicalRootGroup;
+ }
+ }
+
+ // Next check the canonical root group itself
+ for (String activity : permissionActivities) {
+ if (principal.hasPermission(permissionOwner, activity, canonicalRootGroup.getId())) {
+ return canonicalRootGroup;
+ }
+ }
+
+ // So much for the easy paths -- see if the user has any records at all for this specific owner/activity
+ JsonEntityBean rslt = null; // Default
+ final List<IPermission> permissionsOfRelevantActivity = new ArrayList<IPermission>();
+ for (String activity : permissionActivities) {
+ permissionsOfRelevantActivity.addAll(
+ Arrays.asList(principal.getAllPermissions(permissionOwner, activity, null))
+ );
+ }
+ if (log.isDebugEnabled()) {
+ log.debug("For user='" + person.getUserName() +
+ "', groupType='" + groupType + "', permissionOwner='" +
+ permissionOwner + "', permissionActivities='" +
+ Arrays.toString(permissionActivities) + "' permissionsOfRelevantTypes.size()=" +
+ permissionsOfRelevantActivity.size());
+ }
+ switch (permissionsOfRelevantActivity.size()) {
+ case 0:
+ // No problem -- user doesn't have any of this sort of permission (leave it null)
+ break;
+ default:
+ // We need to make some sort of determination as to the best
+ // root group to send back. With luck there aren't many matches.
+ for (IPermission p : permissionsOfRelevantActivity) {
+ final JsonEntityBean candidate = getEntity(groupType, p.getTarget(), true);
+ // Pass on any matches of the wrong groupType...
+ if (!candidate.getEntityTypeAsString().equalsIgnoreCase(groupType)) {
+ continue;
+ }
+ if (rslt == null) {
+ // First allowable selection; run with this one
+ // unless/until we're forced to make a choice.
+ rslt = candidate;
+ } else {
+ // For the present we'll assume the match with the most
+ // children is the best; this approach should work
+ // decently unless folks start putting redundant
+ // permissions records in the DB for multiple levels of
+ // the same rich hierarchy.
+ if (candidate.getChildren().size() > rslt.getChildren().size()) {
+ rslt = candidate;
+ }
+ }
+ }
+ break;
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("Selected for user='" + person.getUserName() +
+ "', groupType='" + groupType + "', permissionOwner='" +
+ permissionOwner + "', permissionActivities='" +
+ Arrays.toString(permissionActivities) +
+ "' the following best root group: " + rslt);
+ }
+
+ return rslt;
+
+ }
+
/*
* (non-Javadoc)
* @see org.jasig.portal.layout.dlm.remoting.IGroupListHelper#getEntityTypesForGroupType(java.lang.String)
diff --git a/uportal-war/src/main/java/org/jasig/portal/layout/dlm/remoting/IGroupListHelper.java b/uportal-war/src/main/java/org/jasig/portal/layout/dlm/remoting/IGroupListHelper.java
index b617460..02a0494 100644
--- a/uportal-war/src/main/java/org/jasig/portal/layout/dlm/remoting/IGroupListHelper.java
+++ b/uportal-war/src/main/java/org/jasig/portal/layout/dlm/remoting/IGroupListHelper.java
@@ -25,6 +25,7 @@ import java.util.Set;
import org.jasig.portal.groups.IGroupMember;
import org.jasig.portal.portlets.groupselector.EntityEnum;
import org.jasig.portal.security.IAuthorizationPrincipal;
+import org.jasig.portal.security.IPerson;
/**
* Helper methods for retrieving portal entities.
@@ -42,16 +43,30 @@ public interface IGroupListHelper {
* @param searchTerm search string
* @return set of matching JsonEntityBeans
*/
- public Set<JsonEntityBean> search(String entityType, String searchTerm);
+ Set<JsonEntityBean> search(String entityType, String searchTerm);
+
+ /**
+ * Get the root entity for a particular type of group entity.
+ *
+ * @param groupType
+ * @return
+ */
+ JsonEntityBean getRootEntity(String groupType);
+
+ /**
+ * Obtain the user's best root (starting) entity available given the
+ * specified permissions activity.
+ */
+ JsonEntityBean getIndividualBestRootEntity(IPerson person,
+ String groupType, String permissionOwner, String permissionActivity);
+
+ /**
+ * Obtain the user's best root (starting) entity available given the
+ * specified permissions activities.
+ */
+ JsonEntityBean getIndividualBestRootEntity(IPerson person, String groupType,
+ String permissionOwner, String[] permissionActivities);
- /**
- * Get the root entity for a particular type of group entity.
- *
- * @param groupType
- * @return
- */
- public JsonEntityBean getRootEntity(String groupType);
-
/**
* Get the set of entity types allowed as children of the specified group
* type.
@@ -59,7 +74,7 @@ public interface IGroupListHelper {
* @param groupType
* @return
*/
- public Set<String> getEntityTypesForGroupType(String groupType);
+ Set<String> getEntityTypesForGroupType(String groupType);
/**
* Return the string representation of the type of a specified entity object.
@@ -67,7 +82,7 @@ public interface IGroupListHelper {
* @param entity Entity whose type needs to be determined
* @return One of the possible EntityEnum string representations
*/
- public EntityEnum getEntityType(IGroupMember entity);
+ EntityEnum getEntityType(IGroupMember entity);
/**
* Find the name of a specified entity.
@@ -75,7 +90,7 @@ public interface IGroupListHelper {
* @param entityBean JsonEntityBean representation of an entity
* @return Entity name, or <code>null</code> if none is found
*/
- public String lookupEntityName(JsonEntityBean entityBean);
+ String lookupEntityName(JsonEntityBean entityBean);
/**
* Return a JsonEntityBean for the supplied IGroupMember instance.
@@ -83,7 +98,7 @@ public interface IGroupListHelper {
* @param member
* @return
*/
- public JsonEntityBean getEntity(IGroupMember member);
+ JsonEntityBean getEntity(IGroupMember member);
/**
* Retrieve an individual entity matching the specified type and id. If
@@ -97,7 +112,7 @@ public interface IGroupListHelper {
* @param populateChildren <code>true</code> to populate the bean with children
* @return JsonEntityBean representation or <code>null</code>
*/
- public JsonEntityBean getEntity(String entityType, String entityId, boolean populateChildren);
+ JsonEntityBean getEntity(String entityType, String entityId, boolean populateChildren);
/**
* Get a list of JsonEntityBeans for a supplied list of string identifiers,
@@ -108,10 +123,10 @@ public interface IGroupListHelper {
* @param params List of string identifiers
* @return List of matching JsonEntityBeans
*/
- public List<JsonEntityBean> getEntityBeans(List<String> params);
+ List<JsonEntityBean> getEntityBeans(List<String> params);
- public JsonEntityBean getEntityForPrincipal(String principalString);
+ JsonEntityBean getEntityForPrincipal(String principalString);
- public IAuthorizationPrincipal getPrincipalForEntity(JsonEntityBean entity);
+ IAuthorizationPrincipal getPrincipalForEntity(JsonEntityBean entity);
}
diff --git a/uportal-war/src/main/java/org/jasig/portal/layout/dlm/remoting/JsonEntityBean.java b/uportal-war/src/main/java/org/jasig/portal/layout/dlm/remoting/JsonEntityBean.java
index b014684..194a5f4 100644
--- a/uportal-war/src/main/java/org/jasig/portal/layout/dlm/remoting/JsonEntityBean.java
+++ b/uportal-war/src/main/java/org/jasig/portal/layout/dlm/remoting/JsonEntityBean.java
@@ -143,11 +143,17 @@ public class JsonEntityBean implements Serializable, Comparable<JsonEntityBean>
* Identifies this bean uniquely as a permissions target. NOTE: This id is
* not the fname (for portlets) or name field (for groups), but rater a
* unique String like 'PORTLET_ID.19' or 'local.36' or 'pags.Authenticated Users'
+ *
+ * @since uPortal 4.0.14
*/
public String getTargetString() {
return targetString;
}
+ /**
+ *
+ * @since uPortal 4.0.14
+ */
public void setTargetString(String targetString) {
this.targetString = targetString;
}
diff --git a/uportal-war/src/main/java/org/jasig/portal/portlets/portletadmin/PortletAdministrationHelper.java b/uportal-war/src/main/java/org/jasig/portal/portlets/portletadmin/PortletAdministrationHelper.java
index 180686b..a8b7a15 100644
--- a/uportal-war/src/main/java/org/jasig/portal/portlets/portletadmin/PortletAdministrationHelper.java
+++ b/uportal-war/src/main/java/org/jasig/portal/portlets/portletadmin/PortletAdministrationHelper.java
@@ -182,10 +182,10 @@ public class PortletAdministrationHelper implements ServletContextAware {
* @param chanId
* @return
*/
- public PortletDefinitionForm getPortletDefinitionForm(String portletId) {
-
- IPortletDefinition def = portletDefinitionRegistry.getPortletDefinition(portletId);
-
+ public PortletDefinitionForm getPortletDefinitionForm(IPerson person, String portletId) {
+
+ IPortletDefinition def = portletDefinitionRegistry.getPortletDefinition(portletId);
+
// create the new form
final PortletDefinitionForm form;
if (def != null) {
@@ -248,18 +248,54 @@ public class PortletAdministrationHelper implements ServletContextAware {
form.addGroup(new JsonEntityBean(everyoneGroup, groupListHelper.getEntityType(everyoneGroup)));
}
- return form;
- }
-
- /**
- * Persist a new or edited PortletDefinition.
- *
- * @param form
- * @param publisher
- */
- public PortletDefinitionForm savePortletRegistration(PortletDefinitionForm form,
- IPerson publisher) throws Exception {
-
+ /* TODO: Service-Layer Security Reboot (great need of refactoring with a community-approved plan in place) */
+ // User must have SOME FORM of lifecycle permission over AT LEAST ONE
+ // category in which this portlet resides; lifecycle permissions are
+ // hierarchical, so we'll test with the weakest.
+ if (!hasLifecyclePermission(person, PortletLifecycleState.CREATED, form.getCategories())) {
+ logger.warn("User '" + person.getUserName() +
+ "' attempted to edit the following portlet without MANAGE permission: " + def);
+ throw new SecurityException("Not Authorized");
+ }
+
+ return form;
+ }
+
+ /**
+ * Persist a new or edited PortletDefinition.
+ *
+ * @param form
+ * @param publisher
+ */
+ public PortletDefinitionForm savePortletRegistration(IPerson publisher,
+ PortletDefinitionForm form) throws Exception {
+
+ /* TODO: Service-Layer Security Reboot (great need of refactoring with a community-approved plan in place) */
+
+ // User must have the selected lifecycle permission over AT LEAST ONE
+ // category in which this portlet resides. (This is the same check that
+ // is made when the user enteres the lifecycle-selection step in the wizard.)
+ if (!hasLifecyclePermission(publisher, form.getLifecycleState(), form.getCategories())) {
+ logger.warn("User '" + publisher.getUserName() +
+ "' attempted to save the following portlet without the selected MANAGE permission: " + form);
+ throw new SecurityException("Not Authorized");
+ }
+ if (form.getId() != null && !form.getId().equals("-1")) {
+ // Portlet is not new; user must have the previous MANAGE permission
+ // in AT LEAST ONE previous category as well
+ IPortletDefinition def = this.portletDefinitionRegistry.getPortletDefinition(form.getId());
+ Set<PortletCategory> categories = portletCategoryRegistry.getParentCategories(def);
+ List<JsonEntityBean> categoryBeans = new ArrayList<JsonEntityBean>();
+ for (PortletCategory cat : categories) {
+ categoryBeans.add(new JsonEntityBean(cat));
+ }
+ if (!hasLifecyclePermission(publisher, def.getLifecycleState(), categoryBeans)) {
+ logger.warn("User '" + publisher.getUserName() +
+ "' attempted to save the following portlet without the previous MANAGE permission: " + form);
+ throw new SecurityException("Not Authorized");
+ }
+ }
+
// create the group array from the form's group list
IGroupMember[] groupMembers = new IGroupMember[form.getGroups().size()];
for (int i = 0; i < groupMembers.length; i++) {
@@ -272,7 +308,7 @@ public class PortletAdministrationHelper implements ServletContextAware {
}
}
-
+
// create the category array from the form's category list
PortletCategory[] categories = new PortletCategory[form.getCategories().size()];
for (ListIterator<JsonEntityBean> iter = form.getCategories().listIterator(); iter.hasNext();) {
@@ -420,20 +456,38 @@ public class PortletAdministrationHelper implements ServletContextAware {
portletPublishingService.savePortletDefinition(portletDef, publisher, Arrays.asList(categories), Arrays.asList(groupMembers));
- return this.getPortletDefinitionForm(portletDef.getPortletDefinitionId().getStringId());
+ return this.getPortletDefinitionForm(publisher, portletDef.getPortletDefinitionId().getStringId());
}
-
- /**
- * Delete the portlet with the given portlet ID.
- *
- * @param portletID the portlet ID
- * @param person the person removing the portlet
- */
- public void removePortletRegistration(String portletId, IPerson person) {
- IPortletDefinition def = portletDefinitionRegistry.getPortletDefinition(portletId);
- portletDefinitionRegistry.deletePortletDefinition(def);
- }
-
+
+ /**
+ * Delete the portlet with the given portlet ID.
+ *
+ * @param person the person removing the portlet
+ * @param form
+ */
+ public void removePortletRegistration(IPerson person, PortletDefinitionForm form) {
+
+ /* TODO: Service-Layer Security Reboot (great need of refactoring with a community-approved plan in place) */
+ // Arguably a check here is redundant since -- in the current
+ // portlet-manager webflow -- you can't get to this point in the
+ // conversation with out first obtaining a PortletDefinitionForm; but
+ // it makes sense to check permissions here as well since the route(s)
+ // to reach this method could evolve in the future.
+
+ // Let's enforce the policy that you may only delete a portlet thet's
+ // currently in a lifecycle state you have permission to MANAGE.
+ // (They're hierarchical.)
+ if (!hasLifecyclePermission(person, form.getLifecycleState(), form.getCategories())) {
+ logger.warn("User '" + person.getUserName() + "' attempted to remove portlet '" +
+ form.getFname() + "' without the proper MANAGE permission");
+ throw new SecurityException("Not Authorized");
+ }
+
+ IPortletDefinition def = portletDefinitionRegistry.getPortletDefinition(form.getId());
+ portletDefinitionRegistry.deletePortletDefinition(def);
+
+ }
+
/**
* Get a list of the key names of the currently-set arbitrary portlet
* preferences.
@@ -794,9 +848,10 @@ public class PortletAdministrationHelper implements ServletContextAware {
}
public boolean configModeAction(ExternalContext externalContext, String fname) throws IOException {
+
final ActionRequest actionRequest = (ActionRequest)externalContext.getNativeRequest();
final ActionResponse actionResponse = (ActionResponse)externalContext.getNativeResponse();
-
+
final IPortletWindowId portletWindowId = this.getDelegateWindowId(externalContext, fname);
if (portletWindowId == null) {
throw new IllegalStateException("Cannot execute configModeAciton without a delegate window ID in the session for key: " + RenderPortletTag.DEFAULT_SESSION_KEY_PREFIX + fname);
@@ -815,7 +870,7 @@ public class PortletAdministrationHelper implements ServletContextAware {
//The portlet sent a redirect OR changed it's mode away from CONFIG, assume it is done
return true;
}
-
+
return false;
}
diff --git a/uportal-war/src/main/java/org/jasig/portal/security/AuthorizationPrincipalHelper.java b/uportal-war/src/main/java/org/jasig/portal/security/AuthorizationPrincipalHelper.java
new file mode 100644
index 0000000..7853420
--- /dev/null
+++ b/uportal-war/src/main/java/org/jasig/portal/security/AuthorizationPrincipalHelper.java
@@ -0,0 +1,43 @@
+package org.jasig.portal.security;
+
+import org.apache.commons.lang3.Validate;
+import org.jasig.portal.EntityIdentifier;
+import org.jasig.portal.services.AuthorizationService;
+
+/**
+ * Static convenience methods for working with IAuthorizationPricipal.
+ *
+ * Currently offers one method which encapsulates the magic for
+ * converting a uPortal IPerson to an IAuthorizationPrincipal.
+ *
+ * @since uPortal 4.1
+ */
+public final class AuthorizationPrincipalHelper {
+
+ /**
+ * Convenience method for converting an IPerson to an IAuthorizationPrincipal.
+ * @param user a non-null valid IPerson
+ * @return an IAuthorizationPrincipal representing that user
+ * @throws IllegalArgumentException if the user object is null or defective.
+ * @since uPortal 4.1
+ */
+ public static IAuthorizationPrincipal principalFromUser(final IPerson user) {
+
+ Validate.notNull(user, "Cannot determine an authorization principal for null user.");
+
+ final EntityIdentifier userEntityIdentifier = user.getEntityIdentifier();
+ Validate.notNull(user, "The user object is defective: lacks entity identifier.");
+
+ final String userEntityKey = userEntityIdentifier.getKey();
+ Validate.notNull(userEntityKey, "The user object is defective: lacks entity key.");
+ final Class userEntityType = userEntityIdentifier.getType();
+ Validate.notNull(userEntityType, "The user object is defective: lacks entity type.");
+
+ final IAuthorizationPrincipal principal =
+ AuthorizationService.instance().newPrincipal(userEntityKey, userEntityType);
+
+ return principal;
+
+ }
+
+}
diff --git a/uportal-war/src/main/java/org/jasig/portal/security/IPermission.java b/uportal-war/src/main/java/org/jasig/portal/security/IPermission.java
index 427d510..18d3f10 100644
--- a/uportal-war/src/main/java/org/jasig/portal/security/IPermission.java
+++ b/uportal-war/src/main/java/org/jasig/portal/security/IPermission.java
@@ -56,12 +56,26 @@ public interface IPermission {
public static final String PORTLET_MANAGER_CREATED_ACTIVITY = "MANAGE_CREATED";
public static final String PORTLET_MANAGER_APPROVED_ACTIVITY = "MANAGE_APPROVED";
public static final String PORTLET_MANAGER_EXPIRED_ACTIVITY = "MANAGE_EXPIRED";
-
+
+ /*
+ * All management permissions in one handy array
+ */
+ public static final String[] PORTLET_MANAGER_MANAGE_ACTIVITIES = new String[] {
+ PORTLET_MANAGER_CREATED_ACTIVITY, PORTLET_MANAGER_APPROVED_ACTIVITY,
+ PORTLET_MANAGER_ACTIVITY, PORTLET_MANAGER_EXPIRED_ACTIVITY
+ };
+
/*
* PortletMode permissions
*/
public static final String PORTLET_MODE_CONFIG = "PORTLET_MODE_CONFIG";
-
+
+ /*
+ * UP_GROUP (GaP) Permissions
+ */
+
+ public static final String VIEW_GROUP_ACTIVITY = "VIEW_GROUP";
+
/*
Permission types. At present only 2, but that could change.
*/
@@ -69,14 +83,24 @@ public interface IPermission {
public static final String PERMISSION_TYPE_DENY = "DENY";
/*
- A String representing the uPortal framework, used, for example, for
- Permission.owner when the framework grants a Permission.
- */
+ * Permission Owner Strings
+ */
+
+ /**
+ * A String representing the uPortal framework, used, for example, for
+ * Permission.owner when the framework grants a Permission.
+ */
+ public static final String PORTAL_SYSTEM = "UP_SYSTEM";
+
+ /**
+ * Represents the GaP subsystem as a permissions owner
+ */
+ public static final String PORTAL_GROUPS = "UP_GROUPS";
+
public static final String PORTAL_PUBLISH = "UP_PORTLET_PUBLISH";
-
+
public static final String PORTAL_SUBSCRIBE = "UP_PORTLET_SUBSCRIBE";
-
- public static final String PORTAL_SYSTEM = "UP_SYSTEM";
+
/*
A String which, when concatentated with a portlet id, represents a portal
diff --git a/uportal-war/src/main/java/org/jasig/portal/spring/security/evaluator/PortalPermissionEvaluator.java b/uportal-war/src/main/java/org/jasig/portal/spring/security/evaluator/PortalPermissionEvaluator.java
index 902f510..8709b24 100644
--- a/uportal-war/src/main/java/org/jasig/portal/spring/security/evaluator/PortalPermissionEvaluator.java
+++ b/uportal-war/src/main/java/org/jasig/portal/spring/security/evaluator/PortalPermissionEvaluator.java
@@ -69,7 +69,7 @@ public class PortalPermissionEvaluator implements PermissionEvaluator {
// Assume it already represents a valid uPortal permission target
targetId = (String) targetDomainObject;
} else if (targetDomainObject instanceof JsonEntityBean) {
- // The id field of a JsonEntityBean is the target String in permissions records
+ // JsonEntityBean objects now have a targetString member
targetId = ((JsonEntityBean) targetDomainObject).getTargetString();
}
diff --git a/uportal-war/src/main/webapp/WEB-INF/flows/edit-portlet/edit-portlet.xml b/uportal-war/src/main/webapp/WEB-INF/flows/edit-portlet/edit-portlet.xml
index 890b5ab..f864185 100644
--- a/uportal-war/src/main/webapp/WEB-INF/flows/edit-portlet/edit-portlet.xml
+++ b/uportal-war/src/main/webapp/WEB-INF/flows/edit-portlet/edit-portlet.xml
@@ -35,9 +35,14 @@
<!-- Initialize a list of available portlet types -->
<on-start>
+ <set name="flashScope.servletRequest"
+ value="portalRequestUtils.getPortletHttpRequest(externalContext.getNativeRequest())"/>
+ <set name="flowScope.person"
+ value="personManager.getPerson(servletRequest)"/>
<set name="flowScope.portletTypes"
value="channelPublishingDefinitionDao.getChannelPublishingDefinitions()"/>
- <set name="flowScope.completed" value="portlet.id != null and portlet.id != '-1'"/>
+ <set name="flowScope.completed"
+ value="portlet.id != null and portlet.id != '-1'"/>
</on-start>
<!-- If we're creating a new portlet, display the first step in the workflow.
@@ -102,7 +107,7 @@
<on-entry>
<set name="flashScope.entityTypes" value="new java.util.HashSet()"/>
<evaluate expression="entityTypes.add('category')"/>
- <set name="flashScope.rootEntity" value="groupListHelper.getEntity('category', 'local.1', false)"/>
+ <set name="flashScope.rootEntity" value="groupListHelper.getIndividualBestRootEntity(person, 'category', T(org.jasig.portal.security.IPermission).PORTAL_PUBLISH, T(org.jasig.portal.security.IPermission).PORTLET_MANAGER_MANAGE_ACTIVITIES)"/>
</on-entry>
<!-- View Parameters -->
<input name="selectTypes" value="entityTypes"/>
@@ -137,7 +142,7 @@
<set name="flashScope.entityTypes" value="new java.util.HashSet()"/>
<evaluate expression="entityTypes.add('person')"/>
<evaluate expression="entityTypes.add('group')"/>
- <set name="flashScope.rootEntity" value="groupListHelper.getEntity('group', 'local.0', false)"/>
+ <set name="flashScope.rootEntity" value="groupListHelper.getIndividualBestRootEntity(person, 'group', T(org.jasig.portal.security.IPermission).PORTAL_GROUPS, T(org.jasig.portal.security.IPermission).VIEW_GROUP_ACTIVITY)"/>
</on-entry>
<!-- View Parameters -->
<input name="selectTypes" value="entityTypes"/>
@@ -157,7 +162,7 @@
<!-- Group input/output mapping -->
<input name="selectedGroups" value="portlet.groups"/>
<output name="selectedGroups" value="portlet.groups"/>
-
+
<transition on="back" to="chooseCategory"/>
<transition on="finish" to="chooseGroupNextScreen"/>
</subflow-state>
@@ -182,8 +187,6 @@
<on-entry>
<set name="flashScope.servletRequest"
value="portalRequestUtils.getPortletHttpRequest(externalContext.getNativeRequest())"/>
- <set name="flashScope.person"
- value="personManager.getPerson(servletRequest)"/>
<set name="viewScope.lifecycleStates" value="portletAdministrationHelper.getAllowedLifecycleStates(person, portlet.categories)"/>
</on-entry>
<transition on="back" to="chooseGroup" validate="false"/>
@@ -217,20 +220,12 @@
<!-- save the portlet -->
<transition on="save" to="finishPortletEdit">
- <set name="flashScope.servletRequest"
- value="portalRequestUtils.getPortletHttpRequest(externalContext.getNativeRequest())"/>
- <set name="flashScope.person"
- value="personManager.getPerson(servletRequest)"/>
- <evaluate expression="portletAdministrationHelper.savePortletRegistration(portlet, person)"></evaluate>
+ <evaluate expression="portletAdministrationHelper.savePortletRegistration(person, portlet)"></evaluate>
</transition>
- <!-- save the portlet -->
+ <!-- save and configure the portlet -->
<transition on="saveAndConfig" to="configMode">
- <set name="flashScope.servletRequest"
- value="portalRequestUtils.getPortletHttpRequest(externalContext.getNativeRequest())"/>
- <set name="flashScope.person"
- value="personManager.getPerson(servletRequest)"/>
- <set name="flowScope.portlet" value="portletAdministrationHelper.savePortletRegistration(portlet, person)" />
+ <set name="flowScope.portlet" value="portletAdministrationHelper.savePortletRegistration(person, portlet)" />
</transition>
<!-- cancel our portlet edit and exit the sub-flow -->
@@ -250,7 +245,7 @@
<set name="flashScope.configComplete" value="portletAdministrationHelper.configModeAction(externalContext, portlet.getFname())" />
<transition to="configMode" on="#{!flashScope.configComplete}" />
<transition to="reviewPortlet" on="#{flashScope.configComplete}">
- <set name="flowScope.portlet" value="portletAdministrationHelper.getPortletDefinitionForm(portlet.id)"/>
+ <set name="flowScope.portlet" value="portletAdministrationHelper.getPortletDefinitionForm(person, portlet.id)"/>
</transition>
</action-state>
diff --git a/uportal-war/src/main/webapp/WEB-INF/flows/portlet-manager/portlet-manager.xml b/uportal-war/src/main/webapp/WEB-INF/flows/portlet-manager/portlet-manager.xml
index 291f0b4..a8ffacd 100644
--- a/uportal-war/src/main/webapp/WEB-INF/flows/portlet-manager/portlet-manager.xml
+++ b/uportal-war/src/main/webapp/WEB-INF/flows/portlet-manager/portlet-manager.xml
@@ -25,13 +25,17 @@
http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
<on-start>
+ <set name="flashScope.servletRequest"
+ value="portalRequestUtils.getPortletHttpRequest(externalContext.getNativeRequest())"/>
+ <set name="flowScope.person"
+ value="personManager.getPerson(servletRequest)"/>
<set name="flowScope.portletTypes"
value="portletTypeRegistry.getPortletTypes()"/>
</on-start>
<!--
| Portlet selection view
- |
+ |
| Present a list of currently configured portlets and allow the
| administrative user to either select a portlet to edit or to elect to
| create a new portlet
@@ -45,7 +49,7 @@
populate it with the selected portlet's data -->
<transition on="editPortlet" to="edit-portlet">
<set name="flowScope.portlet"
- value="portletAdministrationHelper.getPortletDefinitionForm(requestParameters.portletId)"/>
+ value="portletAdministrationHelper.getPortletDefinitionForm(person, requestParameters.portletId)"/>
</transition>
<!-- If we're creating a new portlet, create a new form -->
<transition on="createPortlet" to="edit-portlet">
@@ -55,15 +59,13 @@
<!-- If we're deleting a portlet, confirm the user wants to delete it -->
<transition on="removePortlet" to="confirmRemove">
<set name="flowScope.portlet"
- value="portletAdministrationHelper.getPortletDefinitionForm(requestParameters.portletId)"/>
- <set name="flowScope.portletId"
- value="requestParameters.portletId"/>
+ value="portletAdministrationHelper.getPortletDefinitionForm(person, requestParameters.portletId)"/>
</transition>
</view-state>
-
+
<!--
| Portlet editing subflow
- |
+ |
| Edit or create the selected portlet
+-->
<subflow-state id="edit-portlet" subflow="edit-portlet">
@@ -72,22 +74,18 @@
<transition on="cancelPortletEdit" to="finishPortletEdit"/>
</subflow-state>
- <!--
+ <!--
| Portlet deletion
+-->
<view-state id="confirmRemove">
<transition on="remove" to="listChannels">
<!-- Remove the portlet -->
- <set name="flashScope.servletRequest"
- value="portalRequestUtils.getPortletHttpRequest(externalContext.getNativeRequest())"/>
- <set name="flashScope.person"
- value="personManager.getPerson(servletRequest)"/>
- <evaluate expression="portletAdministrationHelper.removePortletRegistration(flowScope.portletId,flashScope.person)"/>
+ <evaluate expression="portletAdministrationHelper.removePortletRegistration(person, portlet)"/>
</transition>
<transition on="cancel" to="listChannels"/>
</view-state>
-
+
<!-- End state -->
<end-state id="finishPortletEdit" />
-
+
</flow>
--
1.8.4.2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment