/**
* Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information regarding copyright ownership. Apereo
* 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 the
* following location:
*
* <p>http://www.apache.org/licenses/LICENSE-2.0
*
* <p>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.apereo.portal.portlets.portletadmin;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.PortletMode;
import javax.portlet.PortletRequest;
import javax.portlet.PortletSession;
import javax.portlet.WindowState;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.xml.bind.JAXBElement;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.pluto.container.PortletContainerException;
import org.apache.pluto.container.driver.PortalDriverContainerServices;
import org.apache.pluto.container.driver.PortletRegistryService;
import org.apache.pluto.container.om.portlet.DisplayName;
import org.apache.pluto.container.om.portlet.PortletApplicationDefinition;
import org.apache.pluto.container.om.portlet.PortletDefinition;
import org.apache.pluto.container.om.portlet.Supports;
import org.apereo.portal.EntityIdentifier;
import org.apereo.portal.api.portlet.DelegateState;
import org.apereo.portal.api.portlet.DelegationActionResponse;
import org.apereo.portal.api.portlet.PortletDelegationDispatcher;
import org.apereo.portal.api.portlet.PortletDelegationLocator;
import org.apereo.portal.channel.IPortletPublishingService;
import org.apereo.portal.groups.IEntityGroup;
import org.apereo.portal.groups.IGroupMember;
import org.apereo.portal.layout.dlm.remoting.IGroupListHelper;
import org.apereo.portal.layout.dlm.remoting.JsonEntityBean;
import org.apereo.portal.portlet.PortletUtils;
import org.apereo.portal.portlet.dao.jpa.PortletDefinitionImpl;
import org.apereo.portal.portlet.dao.jpa.PortletPreferenceImpl;
import org.apereo.portal.portlet.delegation.jsp.RenderPortletTag;
import org.apereo.portal.portlet.om.IPortletDefinition;
import org.apereo.portal.portlet.om.IPortletPreference;
import org.apereo.portal.portlet.om.IPortletType;
import org.apereo.portal.portlet.om.IPortletWindowId;
import org.apereo.portal.portlet.om.PortletCategory;
import org.apereo.portal.portlet.om.PortletLifecycleState;
import org.apereo.portal.portlet.registry.IPortletCategoryRegistry;
import org.apereo.portal.portlet.registry.IPortletDefinitionRegistry;
import org.apereo.portal.portlet.registry.IPortletTypeRegistry;
import org.apereo.portal.portlet.rendering.IPortletRenderer;
import org.apereo.portal.portletpublishing.xml.MultiValuedPreferenceInputType;
import org.apereo.portal.portletpublishing.xml.Parameter;
import org.apereo.portal.portletpublishing.xml.ParameterInputType;
import org.apereo.portal.portletpublishing.xml.PortletPublishingDefinition;
import org.apereo.portal.portletpublishing.xml.Preference;
import org.apereo.portal.portletpublishing.xml.PreferenceInputType;
import org.apereo.portal.portletpublishing.xml.SingleValuedPreferenceInputType;
import org.apereo.portal.portletpublishing.xml.Step;
import org.apereo.portal.portlets.Attribute;
import org.apereo.portal.portlets.BooleanAttribute;
import org.apereo.portal.portlets.StringListAttribute;
import org.apereo.portal.portlets.fragmentadmin.FragmentAdministrationHelper;
import org.apereo.portal.portlets.groupselector.EntityEnum;
import org.apereo.portal.portlets.portletadmin.xmlsupport.IChannelPublishingDefinitionDao;
import org.apereo.portal.security.AuthorizationPrincipalHelper;
import org.apereo.portal.security.IAuthorizationPrincipal;
import org.apereo.portal.security.IAuthorizationService;
import org.apereo.portal.security.IPermission;
import org.apereo.portal.security.IPermissionManager;
import org.apereo.portal.security.IPerson;
import org.apereo.portal.security.IUpdatingPermissionManager;
import org.apereo.portal.security.PermissionHelper;
import org.apereo.portal.services.GroupService;
import org.apereo.portal.url.IPortalUrlBuilder;
import org.apereo.portal.url.IPortalUrlProvider;
import org.apereo.portal.url.IPortletUrlBuilder;
import org.apereo.portal.url.UrlType;
import org.apereo.portal.utils.ComparableExtractingComparator;
import org.apereo.portal.utils.Tuple;
import org.apereo.portal.xml.PortletDescriptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.context.ServletContextAware;
import org.springframework.webflow.context.ExternalContext;
/**
* Helper methods for the portlet administration workflow.
*
*/
@Service
public final class PortletAdministrationHelper implements ServletContextAware {
private final Log logger = LogFactory.getLog(this.getClass());
private static final String PORTLET_FNAME_FRAGMENT_ADMIN_PORTLET = "fragment-admin";
public static final String[] PORTLET_SUBSCRIBE_ACTIVITIES = {
IPermission.PORTLET_SUBSCRIBER_ACTIVITY, IPermission.PORTLET_BROWSE_ACTIVITY
};
/*
* Autowired beans listed alphabetically by type
*/
@Autowired private FragmentAdministrationHelper fragmentAdminHelper;
@Autowired private IAuthorizationService authorizationService;
@Autowired private IChannelPublishingDefinitionDao portletPublishingDefinitionDao;
@Autowired private IGroupListHelper groupListHelper;
@Autowired private IPortalUrlProvider urlProvider;
@Autowired private IPortletCategoryRegistry portletCategoryRegistry;
@Autowired private IPortletDefinitionRegistry portletDefinitionRegistry;
@Autowired private IPortletPublishingService portletPublishingService;
@Autowired private IPortletTypeRegistry portletTypeRegistry;
@Autowired private PortalDriverContainerServices portalDriverContainerServices;
@Autowired private PortletDelegationLocator portletDelegationLocator;
private ServletContext servletContext;
@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
/**
* Construct a new PortletDefinitionForm for the given IPortletDefinition id. If a
* PortletDefinition matching this ID already exists, the form will be pre-populated with the
* PortletDefinition's current configuration. If the PortletDefinition does not yet exist, a new
* default form will be created.
*
* @param person user that is required to have related lifecycle permission
* @param portletId identifier for the portlet definition
* @return {@PortletDefinitionForm} with set values based on portlet definition or default
* category and principal if no definition is found
*/
public PortletDefinitionForm createPortletDefinitionForm(IPerson person, String portletId) {
IPortletDefinition def = portletDefinitionRegistry.getPortletDefinition(portletId);
// create the new form
final PortletDefinitionForm form;
if (def != null) {
// if this is a pre-existing portlet, set the category and permissions
form = new PortletDefinitionForm(def);
form.setId(def.getPortletDefinitionId().getStringId());
// create a JsonEntityBean for each current category and add it
// to our form bean's category list
Set<PortletCategory> categories = portletCategoryRegistry.getParentCategories(def);
for (PortletCategory cat : categories) {
form.addCategory(new JsonEntityBean(cat));
}
addSubscribePermissionsToForm(def, form);
} else {
form = createNewPortletDefinitionForm();
}
/* 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;
}
/*
* Add to the form SUBSCRIBE and BROWSE activity permissions, along with their principals,
* assigned to the portlet.
*/
private void addSubscribePermissionsToForm(IPortletDefinition def, PortletDefinitionForm form) {
final String portletTargetId = PermissionHelper.permissionTargetIdForPortletDefinition(def);
/* We are concerned with PORTAL_SUBSCRIBE system */
final IPermissionManager pm =
authorizationService.newPermissionManager(IPermission.PORTAL_SUBSCRIBE);
for (String activity : PORTLET_SUBSCRIBE_ACTIVITIES) {
/* Obtain the principals that have permission for the activity on this portlet */
final IAuthorizationPrincipal[] principals =
pm.getAuthorizedPrincipals(activity, portletTargetId);
for (IAuthorizationPrincipal principal : principals) {
JsonEntityBean principalBean;
// first assume this is a group
IEntityGroup group = GroupService.findGroup(principal.getKey());
if (group != null) {
// principal is a group
principalBean = new JsonEntityBean(group, EntityEnum.GROUP);
} else {
// not a group, so it must be a person
IGroupMember member = authorizationService.getGroupMember(principal);
principalBean = new JsonEntityBean(member, EntityEnum.PERSON);
// set the name
String name = groupListHelper.lookupEntityName(principalBean);
principalBean.setName(name);
}
/* Make sure we capture the principal just once*/
if (!form.getPrincipals().contains(principalBean)) {
form.addPrincipal(principalBean);
}
form.addPermission(principalBean.getTypeAndIdHash() + "_" + activity);
}
}
}
/*
* Create a {@code PortletDefinitionForm} and pre-populate it with default categories and principal permissions.
*/
private PortletDefinitionForm createNewPortletDefinitionForm() {
PortletDefinitionForm form = new PortletDefinitionForm();
// pre-populate with top-level category
final IEntityGroup portletCategoriesGroup =
GroupService.getDistinguishedGroup(IPortletDefinition.DISTINGUISHED_GROUP);
form.addCategory(
new JsonEntityBean(
portletCategoriesGroup,
groupListHelper.getEntityType(portletCategoriesGroup)));
// pre-populate with top-level group
final IEntityGroup everyoneGroup =
GroupService.getDistinguishedGroup(IPerson.DISTINGUISHED_GROUP);
JsonEntityBean everyoneBean =
new JsonEntityBean(everyoneGroup, groupListHelper.getEntityType(everyoneGroup));
form.addPrincipal(everyoneBean);
for (String activity : PORTLET_SUBSCRIBE_ACTIVITIES) {
form.addPermission(everyoneBean.getTypeAndIdHash() + "_" + activity);
}
return form;
}
/**
* Persist a new or edited PortletDefinition from a form, replacing existing values.
*
* @param publisher {@code IPerson} that requires permission to save this definition
* @param form form data to persist
* @return new {@code PortletDefinitionForm} for this portlet ID
*/
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 enters 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.isNew()) {
// User must have the previous lifecycle permission
// in AT LEAST ONE previous category as well
IPortletDefinition def =
this.portletDefinitionRegistry.getPortletDefinition(form.getId());
Set<PortletCategory> categories = portletCategoryRegistry.getParentCategories(def);
SortedSet<JsonEntityBean> categoryBeans = new TreeSet<>();
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");
}
}
if (form.isNew()
|| portletDefinitionRegistry.getPortletDefinition(form.getId()).getType().getId()
!= form.getTypeId()) {
// User must have access to the selected CPD if s/he selected it in this interaction
final int selectedTypeId = form.getTypeId();
final PortletPublishingDefinition cpd =
portletPublishingDefinitionDao.getChannelPublishingDefinition(selectedTypeId);
final Map<IPortletType, PortletPublishingDefinition> allowableCpds =
this.getAllowableChannelPublishingDefinitions(publisher);
if (!allowableCpds.containsValue(cpd)) {
logger.warn(
"User '"
+ publisher.getUserName()
+ "' attempted to administer the following portlet without the selected "
+ IPermission.PORTLET_MANAGER_SELECT_PORTLET_TYPE
+ " permission: "
+ form);
throw new SecurityException("Not Authorized");
}
}
// create the principal array from the form's principal list -- only principals with permissions
final Set<IGroupMember> subscribePrincipalSet = new HashSet<>(form.getPrincipals().size());
final Set<IGroupMember> browsePrincipalSet = new HashSet<>(form.getPrincipals().size());
for (JsonEntityBean bean : form.getPrincipals()) {
final String subscribePerm =
bean.getTypeAndIdHash() + "_" + IPermission.PORTLET_SUBSCRIBER_ACTIVITY;
final String browsePerm =
bean.getTypeAndIdHash() + "_" + IPermission.PORTLET_BROWSE_ACTIVITY;
final EntityEnum entityEnum = bean.getEntityType();
final IGroupMember principal =
entityEnum.isGroup()
? (GroupService.findGroup(bean.getId()))
: (GroupService.getGroupMember(bean.getId(), entityEnum.getClazz()));
if (form.getPermissions().contains(subscribePerm)) {
subscribePrincipalSet.add(principal);
}
if (form.getPermissions().contains(browsePerm)) {
browsePrincipalSet.add(principal);
}
}
// create the category list from the form's category bean list
List<PortletCategory> categories = new ArrayList<>();
for (JsonEntityBean category : form.getCategories()) {
String id = category.getId();
String iCatID = id.startsWith("cat") ? id.substring(3) : id;
categories.add(portletCategoryRegistry.getPortletCategory(iCatID));
}
final IPortletType portletType = portletTypeRegistry.getPortletType(form.getTypeId());
if (portletType == null) {
throw new IllegalArgumentException("No IPortletType exists for ID " + form.getTypeId());
}
IPortletDefinition portletDef;
if (form.getId() == null) {
portletDef =
new PortletDefinitionImpl(
portletType,
form.getFname(),
form.getName(),
form.getTitle(),
form.getApplicationId(),
form.getPortletName(),
form.isFramework());
} else {
portletDef = portletDefinitionRegistry.getPortletDefinition(form.getId());
portletDef.setType(portletType);
portletDef.setFName(form.getFname());
portletDef.setName(form.getName());
portletDef.setTitle(form.getTitle());
portletDef.getPortletDescriptorKey().setWebAppName(form.getApplicationId());
portletDef.getPortletDescriptorKey().setPortletName(form.getPortletName());
portletDef.getPortletDescriptorKey().setFrameworkPortlet(form.isFramework());
}
portletDef.setDescription(form.getDescription());
portletDef.setTimeout(form.getTimeout());
// Make parameters (NB: these are different from preferences) in the
// portletDef reflect the state of the form, in case any have changed.
for (String key : form.getParameters().keySet()) {
String value = form.getParameters().get(key).getValue();
if (!StringUtils.isBlank(value)) {
portletDef.addParameter(key, value);
}
}
portletDef.addParameter(
IPortletDefinition.EDITABLE_PARAM, Boolean.toString(form.isEditable()));
portletDef.addParameter(
IPortletDefinition.CONFIGURABLE_PARAM, Boolean.toString(form.isConfigurable()));
portletDef.addParameter(
IPortletDefinition.HAS_HELP_PARAM, Boolean.toString(form.isHasHelp()));
portletDef.addParameter(
IPortletDefinition.HAS_ABOUT_PARAM, Boolean.toString(form.isHasAbout()));
// Now add portlet preferences
List<IPortletPreference> preferenceList = new ArrayList<>();
for (String key : form.getPortletPreferences().keySet()) {
List<String> prefValues = form.getPortletPreferences().get(key).getValue();
if (prefValues != null && prefValues.size() > 0) {
String[] values = prefValues.toArray(new String[prefValues.size()]);
BooleanAttribute readOnly = form.getPortletPreferenceReadOnly().get(key);
preferenceList.add(new PortletPreferenceImpl(key, readOnly.getValue(), values));
}
}
portletDef.setPortletPreferences(preferenceList);
// Lastly update the PortletDefinition's lifecycle state & lifecycle-related metadata
updateLifecycleState(form, portletDef, publisher);
// The final parameter of IGroupMembers is used to set the initial SUBSCRIBE permission set
portletPublishingService.savePortletDefinition(
portletDef, publisher, categories, new ArrayList<>(subscribePrincipalSet));
//updatePermissions(portletDef, subscribePrincipalSet, IPermission.PORTLET_SUBSCRIBER_ACTIVITY);
updatePermissions(portletDef, browsePrincipalSet, IPermission.PORTLET_BROWSE_ACTIVITY);
return this.createPortletDefinitionForm(
publisher, portletDef.getPortletDefinitionId().getStringId());
}
/*
* Update permissions for activity for portlet definition. Adds new principals' permissions passed in and removes
* principals' permissions if not in the list for the given activity.
*/
private void updatePermissions(
IPortletDefinition def, Set<IGroupMember> newPrincipals, String activity) {
final String portletTargetId = PermissionHelper.permissionTargetIdForPortletDefinition(def);
/* We are concerned with PORTAL_SUBSCRIBE system */
final IUpdatingPermissionManager pm =
authorizationService.newUpdatingPermissionManager(IPermission.PORTAL_SUBSCRIBE);
/* Create the new permissions array */
final List<IPermission> newPermissions = new ArrayList<>();
for (final IGroupMember newPrincipal : newPrincipals) {
final IAuthorizationPrincipal authorizationPrincipal =
authorizationService.newPrincipal(newPrincipal);
final IPermission permission = pm.newPermission(authorizationPrincipal);
permission.setType(IPermission.PERMISSION_TYPE_GRANT);
permission.setActivity(activity);
permission.setTarget(portletTargetId);
newPermissions.add(permission);
}
/* Remove former permissions for this portlet / activity */
final IPermission[] oldPermissions = pm.getPermissions(activity, portletTargetId);
pm.removePermissions(oldPermissions);
/* Add the new permissions */
pm.addPermissions(newPermissions.toArray(new IPermission[newPermissions.size()]));
}
/**
* 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());
/*
* It's very important to remove portlets via the portletPublishingService
* because that API cleans up details like category memberships and permissions.
*/
portletPublishingService.removePortletDefinition(def, person);
}
/**
* Check if the link to the Fragment admin portlet should display in the status message.
*
* <p>Checks that the portlet is new, that the portlet has been published and that the user has
* necessary permissions to go to the fragment admin page.
*
* @param person the person publishing/editing the portlet
* @param form the portlet being editted
* @param portletId the id of the saved portlet
* @return true If all three conditions are met
*/
public boolean shouldDisplayLayoutLink(
IPerson person, PortletDefinitionForm form, String portletId) {
if (!form.isNew()) {
return false;
}
// only include the "do layout" link for published portlets.
if (form.getLifecycleState() != PortletLifecycleState.PUBLISHED) {
return false;
}
// check that the user can edit at least 1 fragment.
Map<String, String> layouts =
fragmentAdminHelper.getAuthorizedDlmFragments(person.getUserName());
if (layouts == null || layouts.isEmpty()) {
return false;
}
// check that the user has subscribe priv.
IAuthorizationPrincipal authPrincipal =
authorizationService.newPrincipal(
person.getUserName(), EntityEnum.PERSON.getClazz());
if (!authPrincipal.canSubscribe(portletId)) {
return false;
}
return true;
}
/**
* Get the link to the fragment admin portlet.
*
* @param request the current http request.
* @return the portlet link
*/
public String getFragmentAdminURL(HttpServletRequest request) {
IPortalUrlBuilder builder =
urlProvider.getPortalUrlBuilderByPortletFName(
request, PORTLET_FNAME_FRAGMENT_ADMIN_PORTLET, UrlType.RENDER);
IPortletUrlBuilder portletUrlBuilder = builder.getTargetedPortletUrlBuilder();
portletUrlBuilder.setPortletMode(PortletMode.VIEW);
portletUrlBuilder.setWindowState(WindowState.MAXIMIZED);
return builder.getUrlString();
}
/**
* Get a list of the key names of the currently-set arbitrary portlet preferences.
*
* @param form
* @return
*/
public Set<String> getArbitraryPortletPreferenceNames(PortletDefinitionForm form) {
// set default values for all portlet parameters
PortletPublishingDefinition cpd =
this.portletPublishingDefinitionDao.getChannelPublishingDefinition(
form.getTypeId());
Set<String> currentPrefs = new HashSet<String>();
currentPrefs.addAll(form.getPortletPreferences().keySet());
for (Step step : cpd.getSteps()) {
if (step.getPreferences() != null) {
for (Preference pref : step.getPreferences()) {
currentPrefs.remove(pref.getName());
}
}
}
return currentPrefs;
}
/**
* If the portlet is a portlet and if one of the supported portlet modes is {@link
* IPortletRenderer#CONFIG}
*/
public boolean supportsConfigMode(PortletDefinitionForm form) {
final Tuple<String, String> portletDescriptorKeys = this.getPortletDescriptorKeys(form);
if (portletDescriptorKeys == null) {
return false;
}
final String portletAppId = portletDescriptorKeys.first;
final String portletName = portletDescriptorKeys.second;
final PortletRegistryService portletRegistryService =
this.portalDriverContainerServices.getPortletRegistryService();
final PortletDefinition portletDescriptor;
try {
portletDescriptor = portletRegistryService.getPortlet(portletAppId, portletName);
} catch (PortletContainerException e) {
this.logger.warn(
"Failed to load portlet descriptor for appId='"
+ portletAppId
+ "', portletName='"
+ portletName
+ "'",
e);
return false;
}
if (portletDescriptor == null) {
return false;
}
//Iterate over supported portlet modes, this ignores the content types for now
final List<? extends Supports> supports = portletDescriptor.getSupports();
for (final Supports support : supports) {
final List<String> portletModes = support.getPortletModes();
for (final String portletMode : portletModes) {
if (IPortletRenderer.CONFIG.equals(PortletUtils.getPortletMode(portletMode))) {
return true;
}
}
}
return false;
}
private static final Pattern PARAM_PATTERN =
Pattern.compile("^([^\\[]+)\\['([^\\']+)'\\]\\.value$");
public void cleanOptions(PortletDefinitionForm form, PortletRequest request) {
// Add permission parameters to permissions collection
form.clearPermissions();
for (String activity : PORTLET_SUBSCRIBE_ACTIVITIES) {
addPermissionsFromRequestToForm(form, request, activity);
}
//Names of valid preferences and parameters
final Set<String> preferenceNames = new HashSet<String>();
final Set<String> parameterNames = new HashSet<String>();
//Read all of the submitted channel parameter and portlet preference names from the request
for (final Enumeration<String> e = request.getParameterNames(); e.hasMoreElements(); ) {
final String name = e.nextElement();
final Matcher nameMatcher = PARAM_PATTERN.matcher(name);
if (nameMatcher.matches()) {
final String paramType = nameMatcher.group(1);
final String paramName = nameMatcher.group(2);
if ("portletPreferences".equals(paramType)) {
preferenceNames.add(paramName);
} else if ("parameters".equals(paramType)) {
parameterNames.add(paramName);
}
}
}
//Add all of the parameter and preference names that have default values in the CPD into the valid name sets
final PortletPublishingDefinition cpd =
this.portletPublishingDefinitionDao.getChannelPublishingDefinition(
form.getTypeId());
for (final Step step : cpd.getSteps()) {
final List<Parameter> parameters = step.getParameters();
if (parameters != null) {
for (final Parameter parameter : parameters) {
final JAXBElement<? extends ParameterInputType> parameterInput =
parameter.getParameterInput();
if (parameterInput != null) {
final ParameterInputType parameterInputType = parameterInput.getValue();
if (parameterInputType != null && parameterInputType.getDefault() != null) {
parameterNames.add(parameter.getName());
}
}
}
}
final List<Preference> preferences = step.getPreferences();
if (preferences != null) {
for (final Preference preference : preferences) {
final JAXBElement<? extends PreferenceInputType> preferenceInput =
preference.getPreferenceInput();
final PreferenceInputType preferenceInputType = preferenceInput.getValue();
if (preferenceInputType instanceof MultiValuedPreferenceInputType) {
final MultiValuedPreferenceInputType multiValuedPreferenceInputType =
(MultiValuedPreferenceInputType) preferenceInputType;
final List<String> defaultValues =
multiValuedPreferenceInputType.getDefaults();
if (defaultValues != null && !defaultValues.isEmpty()) {
preferenceNames.add(preference.getName());
}
} else if (preferenceInputType instanceof SingleValuedPreferenceInputType) {
final SingleValuedPreferenceInputType SingleValuedPreferenceInputType =
(SingleValuedPreferenceInputType) preferenceInputType;
if (SingleValuedPreferenceInputType.getDefault() != null) {
preferenceNames.add(preference.getName());
}
}
}
}
}
//Remove portlet preferences from the form object that were not part of this request or defined in the CPD
// - do it only if portlet doesn't support configMode
if (!this.supportsConfigMode(form)) {
final Map<String, StringListAttribute> portletPreferences =
form.getPortletPreferences();
final Map<String, BooleanAttribute> portletPreferencesOverrides =
form.getPortletPreferenceReadOnly();
for (final Iterator<Entry<String, StringListAttribute>> portletPreferenceEntryItr =
portletPreferences.entrySet().iterator();
portletPreferenceEntryItr.hasNext();
) {
final Map.Entry<String, StringListAttribute> portletPreferenceEntry =
portletPreferenceEntryItr.next();
final String key = portletPreferenceEntry.getKey();
final StringListAttribute valueAttr = portletPreferenceEntry.getValue();
if (!preferenceNames.contains(key) || valueAttr == null) {
portletPreferenceEntryItr.remove();
portletPreferencesOverrides.remove(key);
} else {
final List<String> values = valueAttr.getValue();
for (final Iterator<String> iter = values.iterator(); iter.hasNext(); ) {
String value = iter.next();
if (value == null) {
iter.remove();
}
}
if (values.size() == 0) {
portletPreferenceEntryItr.remove();
portletPreferencesOverrides.remove(key);
}
}
}
}
final Map<String, Attribute> parameters = form.getParameters();
for (final Iterator<Entry<String, Attribute>> parameterEntryItr =
parameters.entrySet().iterator();
parameterEntryItr.hasNext();
) {
final Entry<String, Attribute> parameterEntry = parameterEntryItr.next();
final String key = parameterEntry.getKey();
final Attribute value = parameterEntry.getValue();
if (!parameterNames.contains(key)
|| value == null
|| StringUtils.isBlank(value.getValue())) {
parameterEntryItr.remove();
}
}
}
private void addPermissionsFromRequestToForm(
PortletDefinitionForm form, PortletRequest request, String activity) {
final String ending = "_" + activity;
for (final String name : request.getParameterMap().keySet()) {
if (name.endsWith(ending)) {
form.addPermission(name);
}
}
}
/**
* Retreive the list of portlet application contexts currently available in this portlet
* container.
*
* @return list of portlet context
*/
public List<PortletApplicationDefinition> getPortletApplications() {
final PortletRegistryService portletRegistryService =
portalDriverContainerServices.getPortletRegistryService();
final List<PortletApplicationDefinition> contexts =
new ArrayList<PortletApplicationDefinition>();
for (final Iterator<String> iter =
portletRegistryService.getRegisteredPortletApplicationNames();
iter.hasNext();
) {
final String applicationName = iter.next();
final PortletApplicationDefinition applicationDefninition;
try {
applicationDefninition =
portletRegistryService.getPortletApplication(applicationName);
} catch (PortletContainerException e) {
throw new RuntimeException(
"Failed to load PortletApplicationDefinition for '"
+ applicationName
+ "'");
}
final List<? extends PortletDefinition> portlets = applicationDefninition.getPortlets();
Collections.sort(
portlets,
new ComparableExtractingComparator<PortletDefinition, String>(
String.CASE_INSENSITIVE_ORDER) {
@Override
protected String getComparable(PortletDefinition o) {
final List<? extends DisplayName> displayNames = o.getDisplayNames();
if (displayNames != null && displayNames.size() > 0) {
return displayNames.get(0).getDisplayName();
}
return o.getPortletName();
}
});
contexts.add(applicationDefninition);
}
Collections.sort(
contexts,
new ComparableExtractingComparator<PortletApplicationDefinition, String>(
String.CASE_INSENSITIVE_ORDER) {
@Override
protected String getComparable(PortletApplicationDefinition o) {
final String portletContextName = o.getName();
if (portletContextName != null) {
return portletContextName;
}
final String applicationName = o.getContextPath();
if ("/".equals(applicationName)) {
return "ROOT";
}
if (applicationName.startsWith("/")) {
return applicationName.substring(1);
}
return applicationName;
}
});
return contexts;
}
/**
* Get a portlet descriptor matching the current portlet definition form. If the current form
* does not represent a portlet, the application or portlet name fields are blank, or the
* portlet description cannot be retrieved, the method will return <code>null</code>.
*
* @param form
* @return
*/
public PortletDefinition getPortletDescriptor(PortletDefinitionForm form) {
final Tuple<String, String> portletDescriptorKeys = this.getPortletDescriptorKeys(form);
if (portletDescriptorKeys == null) {
return null;
}
final String portletAppId = portletDescriptorKeys.first;
final String portletName = portletDescriptorKeys.second;
final PortletRegistryService portletRegistryService =
portalDriverContainerServices.getPortletRegistryService();
try {
PortletDefinition portletDD =
portletRegistryService.getPortlet(portletAppId, portletName);
return portletDD;
} catch (PortletContainerException e) {
e.printStackTrace();
return null;
}
}
/**
* Pre-populate a new {@link PortletDefinitionForm} with information from the {@link
* PortletDefinition}.
*
* @param form
*/
public void loadDefaultsFromPortletDefinitionIfNew(PortletDefinitionForm form) {
if (!form.isNew()) {
// Get out; we only prepopulate new portlets
return;
}
// appName/portletName must be set at this point
Validate.notBlank(form.getApplicationId(), "ApplicationId not set");
Validate.notBlank(form.getPortletName(), "PortletName not set");
final PortletRegistryService portletRegistryService =
portalDriverContainerServices.getPortletRegistryService();
final PortletDefinition portletDef;
try {
portletDef =
portletRegistryService.getPortlet(
form.getApplicationId(), form.getPortletName());
} catch (PortletContainerException e) {
this.logger.warn(
"Failed to load portlet descriptor for appId='"
+ form.getApplicationId()
+ "', portletName='"
+ form.getPortletName()
+ "'",
e);
return;
}
form.setTitle(portletDef.getPortletName());
form.setName(portletDef.getPortletName());
for (Supports supports : portletDef.getSupports()) {
for (String mode : supports.getPortletModes()) {
if ("edit".equalsIgnoreCase(mode)) {
form.setEditable(true);
} else if ("help".equalsIgnoreCase(mode)) {
form.setHasHelp(true);
} else if ("config".equalsIgnoreCase(mode)) {
form.setConfigurable(true);
}
}
}
}
public PortletLifecycleState[] getLifecycleStates() {
return PortletLifecycleState.values();
}
public Set<PortletLifecycleState> getAllowedLifecycleStates(
IPerson person, SortedSet<JsonEntityBean> categories) {
Set<PortletLifecycleState> states = new TreeSet<PortletLifecycleState>();
if (hasLifecyclePermission(person, PortletLifecycleState.MAINTENANCE, categories)) {
states.add(PortletLifecycleState.CREATED);
states.add(PortletLifecycleState.APPROVED);
states.add(PortletLifecycleState.EXPIRED);
states.add(PortletLifecycleState.PUBLISHED);
states.add(PortletLifecycleState.MAINTENANCE);
} else if (hasLifecyclePermission(person, PortletLifecycleState.EXPIRED, categories)) {
states.add(PortletLifecycleState.CREATED);
states.add(PortletLifecycleState.APPROVED);
states.add(PortletLifecycleState.EXPIRED);
states.add(PortletLifecycleState.PUBLISHED);
} else if (hasLifecyclePermission(person, PortletLifecycleState.PUBLISHED, categories)) {
states.add(PortletLifecycleState.CREATED);
states.add(PortletLifecycleState.APPROVED);
states.add(PortletLifecycleState.PUBLISHED);
} else if (hasLifecyclePermission(person, PortletLifecycleState.APPROVED, categories)) {
states.add(PortletLifecycleState.CREATED);
states.add(PortletLifecycleState.APPROVED);
} else if (hasLifecyclePermission(person, PortletLifecycleState.CREATED, categories)) {
states.add(PortletLifecycleState.CREATED);
}
return states;
}
public boolean hasLifecyclePermission(
IPerson person, PortletLifecycleState state, SortedSet<JsonEntityBean> categories) {
EntityIdentifier ei = person.getEntityIdentifier();
IAuthorizationPrincipal ap = authorizationService.newPrincipal(ei.getKey(), ei.getType());
final String activity;
switch (state) {
case APPROVED:
{
activity = IPermission.PORTLET_MANAGER_APPROVED_ACTIVITY;
break;
}
case CREATED:
{
activity = IPermission.PORTLET_MANAGER_CREATED_ACTIVITY;
break;
}
case PUBLISHED:
{
activity = IPermission.PORTLET_MANAGER_ACTIVITY;
break;
}
case EXPIRED:
{
activity = IPermission.PORTLET_MANAGER_EXPIRED_ACTIVITY;
break;
}
case MAINTENANCE:
{
activity = IPermission.PORTLET_MANAGER_MAINTENANCE_ACTIVITY;
break;
}
default:
{
throw new IllegalArgumentException("");
}
}
if (ap.hasPermission(
IPermission.PORTAL_PUBLISH, activity, IPermission.ALL_PORTLETS_TARGET)) {
logger.debug(
"Found permission for category ALL_PORTLETS and lifecycle state "
+ state.toString());
return true;
}
for (JsonEntityBean category : categories) {
if (ap.canManage(state, category.getId())) {
logger.debug(
"Found permission for category "
+ category.getName()
+ " and lifecycle state "
+ state.toString());
return true;
}
}
logger.debug("No permission for lifecycle state " + state.toString());
return false;
}
public IPortletWindowId getDelegateWindowId(ExternalContext externalContext, String fname) {
final PortletRequest nativeRequest = (PortletRequest) externalContext.getNativeRequest();
final PortletSession portletSession = nativeRequest.getPortletSession();
return (IPortletWindowId)
portletSession.getAttribute(RenderPortletTag.DEFAULT_SESSION_KEY_PREFIX + fname);
}
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);
}
final PortletDelegationDispatcher requestDispatcher =
this.portletDelegationLocator.getRequestDispatcher(actionRequest, portletWindowId);
final DelegationActionResponse delegationResponse =
requestDispatcher.doAction(actionRequest, actionResponse);
final String redirectLocation = delegationResponse.getRedirectLocation();
final DelegateState delegateState = delegationResponse.getDelegateState();
if (redirectLocation != null
|| (delegationResponse.getPortletMode() != null
&& !IPortletRenderer.CONFIG.equals(delegationResponse.getPortletMode()))
|| !IPortletRenderer.CONFIG.equals(delegateState.getPortletMode())) {
//The portlet sent a redirect OR changed it's mode away from CONFIG, assume it is done
return true;
}
return false;
}
/**
* updates the editPortlet form with the portletType of the first (and only) portletDefinition
* passed in through the Map of portlet definitions.
*
* @param portletDefinitions
* @param form
* @return PortletPublishingDefinition of the first portlet definition in the list, null if the
* list is empty or has more than one element.
*/
public PortletPublishingDefinition updateFormForSinglePortletType(
Map<IPortletType, PortletPublishingDefinition> portletDefinitions,
PortletDefinitionForm form) {
if (portletDefinitions.size() != 1) {
return null;
}
IPortletType portletType = portletDefinitions.keySet().iterator().next();
form.setTypeId(portletType.getId());
PortletPublishingDefinition cpd =
portletPublishingDefinitionDao.getChannelPublishingDefinition(portletType.getId());
form.setChannelPublishingDefinition(cpd);
return cpd;
}
public boolean offerPortletSelection(PortletDefinitionForm form) {
final IPortletType portletType = this.portletTypeRegistry.getPortletType(form.getTypeId());
final PortletPublishingDefinition portletPublishingDefinition =
this.portletPublishingDefinitionDao.getChannelPublishingDefinition(
portletType.getId());
final PortletDescriptor portletDescriptor =
portletPublishingDefinition.getPortletDescriptor();
if (portletDescriptor == null) {
return true;
}
final Boolean isFramework = portletDescriptor.isIsFramework();
if (isFramework != null && isFramework) {
form.setFramework(isFramework);
} else {
final String webAppName = portletDescriptor.getWebAppName();
form.setApplicationId(webAppName);
}
final String portletName = portletDescriptor.getPortletName();
form.setPortletName(portletName);
return false;
}
public Map<IPortletType, PortletPublishingDefinition> getAllowableChannelPublishingDefinitions(
IPerson user) {
Map<IPortletType, PortletPublishingDefinition> rslt;
final Map<IPortletType, PortletPublishingDefinition> rawMap =
portletPublishingDefinitionDao.getChannelPublishingDefinitions();
final IAuthorizationPrincipal principal =
AuthorizationPrincipalHelper.principalFromUser(user);
if (principal.hasPermission(
IPermission.PORTAL_PUBLISH,
IPermission.PORTLET_MANAGER_SELECT_PORTLET_TYPE,
IPermission.ALL_PORTLET_TYPES)) {
// Send the whole collection back...
rslt = rawMap;
} else {
// Filter the collection by permissions...
rslt = new HashMap<IPortletType, PortletPublishingDefinition>();
for (Map.Entry<IPortletType, PortletPublishingDefinition> y : rawMap.entrySet()) {
if (principal.hasPermission(
IPermission.PORTAL_PUBLISH,
IPermission.PORTLET_MANAGER_SELECT_PORTLET_TYPE,
y.getKey().getName())) {
rslt.put(y.getKey(), y.getValue());
}
}
}
return rslt;
}
protected Tuple<String, String> getPortletDescriptorKeys(PortletDefinitionForm form) {
if (form.getPortletName() == null
|| (form.getApplicationId() == null && !form.isFramework())) {
return null;
}
final String portletAppId;
if (form.isFramework()) {
portletAppId = this.servletContext.getContextPath();
} else {
portletAppId = form.getApplicationId();
}
final String portletName = form.getPortletName();
return new Tuple<String, String>(portletAppId, portletName);
}
private void updateLifecycleState(
PortletDefinitionForm form, IPortletDefinition portletDef, IPerson publisher) {
/*
* Manage the metadata for each possible lifecycle state in turn...
*/
Date now = new Date(); // Will be entered as the timestamp for states that we trigger
PortletLifecycleState selectedLifecycleState = form.getLifecycleState();
/*
* APPROVED
*/
if (selectedLifecycleState.isEqualToOrAfter(PortletLifecycleState.APPROVED)) {
// We are the 'approver' if it isn't previously approved...
if (portletDef.getApprovalDate() == null) {
portletDef.setApproverId(publisher.getID());
portletDef.setApprovalDate(now);
}
if (selectedLifecycleState.equals(PortletLifecycleState.APPROVED)
&& form.getPublishDate() != null
// Permissions check required (of course) to use the auto-publish feature
&& hasLifecyclePermission(
publisher, PortletLifecycleState.PUBLISHED, form.getCategories())) {
// We are also the 'publisher' if we scheduled the portlet for (future) publication...
portletDef.setPublishDate(form.getPublishDateTime());
portletDef.setPublisherId(publisher.getID());
}
} else {
// Clear previous approval fields, if present...
portletDef.setApprovalDate(null);
portletDef.setApproverId(-1);
}
/*
* PUBLISHED
*/
if (selectedLifecycleState.isEqualToOrAfter(PortletLifecycleState.PUBLISHED)) {
// We are the 'publisher' if it isn't previously published or the publish time hasn't hit yet...
if (portletDef.getPublishDate() == null || portletDef.getPublishDate().after(now)) {
portletDef.setPublisherId(publisher.getID());
portletDef.setPublishDate(now);
}
if (selectedLifecycleState.equals(PortletLifecycleState.PUBLISHED)
&& form.getExpirationDate() != null
// Permissions check required (of course) to use the auto-expire feature
&& hasLifecyclePermission(
publisher, PortletLifecycleState.EXPIRED, form.getCategories())) {
// We are also the 'expirer' if we scheduled the portlet for (future) expiration...
portletDef.setExpirationDate(form.getExpirationDateTime());
portletDef.setExpirerId(publisher.getID());
}
} else if (!selectedLifecycleState.equals(PortletLifecycleState.APPROVED)
|| form.getPublishDate() == null) {
// Clear previous publishing fields, if present...
portletDef.setPublishDate(null);
portletDef.setPublisherId(-1);
}
/*
* EXPIRED
*/
if (selectedLifecycleState.equals(PortletLifecycleState.EXPIRED)) {
// We are only the 'expirer' if we specifically choose EXPIRED
// (MAINTENANCE mode is not considered expired)
portletDef.setExpirerId(publisher.getID());
portletDef.setExpirationDate(now);
} else if (!selectedLifecycleState.equals(PortletLifecycleState.PUBLISHED)
|| form.getExpirationDate() == null) {
// Clear previous expiration fields, if present...
portletDef.setExpirationDate(null);
portletDef.setExpirerId(-1);
}
/*
* MAINTENANCE
*/
if (selectedLifecycleState.equals(PortletLifecycleState.MAINTENANCE)) {
// We are placing the portlet into MAINTENANCE mode;
// an admin will restore it (manually) when available
portletDef.addParameter(PortletLifecycleState.MAINTENANCE_MODE_PARAMETER_NAME, "true");
} else {
// Otherwise we must remove the MAINTENANCE flag, if present
portletDef.removeParameter(PortletLifecycleState.MAINTENANCE_MODE_PARAMETER_NAME);
}
}
}