CVE-2023-47320
Description
Silverpeas Core 6.3.1 and below allows low-privileged attackers to enable Maintenance Mode, causing denial of service.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Silverpeas Core 6.3.1 and below allows low-privileged attackers to enable Maintenance Mode, causing denial of service.
CVE-2023-47320 describes an incorrect access control vulnerability in Silverpeas Core 6.3.1 and earlier versions [3]. The root cause is that a controller class, JobDomainPeasSessionController, was extending AbstractComponentSessionController instead of AbstractAdminComponentSessionController, which lacks the proper authorization checks for administrator-only functions [2]. This oversight allows any authenticated user, regardless of privilege level, to invoke the method that puts the application into Maintenance Mode.
To exploit this vulnerability, an attacker only needs a low-privilege account on the Silverpeas instance [3]. No special network position or additional authentication is required beyond a valid user session. The broken access control permits the attacker to call the administrator-only function that toggles Maintenance Mode, which is intended to be restricted to users with administrative rights [1].
The impact is a denial of service: when Maintenance Mode is enabled, the application becomes unavailable to all users, including legitimate administrators [3]. This effectively shuts down the collaborative platform, preventing any access or operations. The vulnerability is classified as a severe access control issue, as it allows a low-privilege user to perform an action that disrupts the entire service.
Silverpeas has addressed this vulnerability in a commit that changes the superclass of JobDomainPeasSessionController from AbstractComponentSessionController to AbstractAdminComponentSessionController, ensuring that only administrators can access the function [2]. Users should update to a patched version of Silverpeas Core to mitigate the risk. No workaround is documented; the software is expected to be updated to a version containing the fix.
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.silverpeas.core:silverpeas-core-warMaven | < 6.3.2 | 6.3.2 |
org.silverpeas.core:silverpeas-core-webMaven | < 6.3.2 | 6.3.2 |
Affected products
3- Silverpeas/Coredescription
- ghsa-coords2 versions
< 6.3.2+ 1 more
- (no CPE)range: < 6.3.2
- (no CPE)range: < 6.3.2
Patches
1fcb4a9740b6cBug #13812: fixing broken access control allows denial of service via maintenance mode
34 files changed · +1567 −245
core-library/src/main/java/org/silverpeas/core/admin/service/cache/AdminCache.java+1 −0 modified@@ -379,6 +379,7 @@ public void opAddSpace(final SpaceInst theSpace) { .ifPresent(subSpaces::remove); subSpaces.add(theSpace); f.setSubSpaces(subSpaces); + resetManageableSpaceIds(); }); } }
core-war/src/main/java/com/sun/portal/portletcontainer/driver/admin/PortletDeployerServlet.java+9 −5 modified@@ -23,18 +23,19 @@ */ package com.sun.portal.portletcontainer.driver.admin; -import org.silverpeas.web.portlets.portal.DesktopMessages; -import org.silverpeas.web.portlets.portal.PropertiesContext; -import org.silverpeas.core.admin.user.model.UserDetail; import com.sun.portal.portletcontainer.admin.PortletRegistryHelper; import com.sun.portal.portletcontainer.admin.deployment.WebAppDeployerException; import com.sun.portal.portletcontainer.context.registry.PortletRegistryException; +import org.silverpeas.core.admin.user.model.User; +import org.silverpeas.core.admin.user.model.UserDetail; +import org.silverpeas.core.web.mvc.webcomponent.SilverpeasAuthenticatedHttpServlet; +import org.silverpeas.web.portlets.portal.DesktopMessages; +import org.silverpeas.web.portlets.portal.PropertiesContext; import javax.servlet.RequestDispatcher; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; @@ -48,7 +49,7 @@ * AdminServlet is a router for admin related requests like deploying/undeploying of portlets and * creating of portlet windows. */ -public class PortletDeployerServlet extends HttpServlet { +public class PortletDeployerServlet extends SilverpeasAuthenticatedHttpServlet { private static final long serialVersionUID = 7041695476364573175L; private static final Logger logger = Logger.getLogger(PortletDeployerServlet.class @@ -131,6 +132,9 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) public void doGetPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if (!User.getCurrentRequester().isAccessAdmin()) { + throwHttpForbiddenError(); + } String language = getLanguage(request);
core-war/src/main/java/com/sun/portal/portletcontainer/driver/admin/UploadServlet.java+8 −4 modified@@ -23,8 +23,6 @@ */ package com.sun.portal.portletcontainer.driver.admin; -import org.silverpeas.web.portlets.portal.DesktopMessages; -import org.silverpeas.core.admin.user.model.UserDetail; import com.sun.portal.portletcontainer.admin.PortletRegistryHelper; import com.sun.portal.portletcontainer.admin.deployment.WebAppDeployerException; import com.sun.portal.portletcontainer.context.registry.PortletRegistryException; @@ -34,12 +32,15 @@ import org.apache.commons.fileupload.disk.SilverpeasDiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.apache.commons.io.FilenameUtils; +import org.silverpeas.core.admin.user.model.User; +import org.silverpeas.core.admin.user.model.UserDetail; +import org.silverpeas.core.web.mvc.webcomponent.SilverpeasAuthenticatedHttpServlet; +import org.silverpeas.web.portlets.portal.DesktopMessages; import javax.servlet.RequestDispatcher; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; @@ -53,7 +54,7 @@ /** * UploadServlet is responsible for uploading the portlet war file */ -public class UploadServlet extends HttpServlet { +public class UploadServlet extends SilverpeasAuthenticatedHttpServlet { private static final long serialVersionUID = 6041525805480787611L; @@ -76,6 +77,9 @@ public void init(ServletConfig config) throws ServletException { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if (!User.getCurrentRequester().isAccessAdmin()) { + throwHttpForbiddenError(); + } // Initialize DesktopMessages' Resource Bundle DesktopMessages.init(getLanguage(request));
core-war/src/main/java/org/silverpeas/web/importexport/control/ImportExportSessionController.java+8 −2 modified@@ -32,7 +32,7 @@ import org.silverpeas.core.util.MultiSilverpeasBundle; import org.silverpeas.core.util.ServiceProvider; import org.silverpeas.core.util.WAAttributeValuePair; -import org.silverpeas.core.web.mvc.controller.AbstractComponentSessionController; +import org.silverpeas.core.web.mvc.controller.AbstractAdminComponentSessionController; import org.silverpeas.core.web.mvc.controller.ComponentContext; import org.silverpeas.core.web.mvc.controller.MainSessionController; @@ -41,7 +41,8 @@ /** * @author neysseri */ -public class ImportExportSessionController extends AbstractComponentSessionController { +public class ImportExportSessionController extends AbstractAdminComponentSessionController { + private static final long serialVersionUID = -6252741698097859228L; ExportTask exportTask = null; Exception errorOccured = null; @@ -56,6 +57,11 @@ public ImportExportSessionController(MainSessionController mainSessionCtrl, super(mainSessionCtrl, componentContext, multilangBundle, iconBundle); } + @Override + public boolean isAccessGranted() { + return true; + } + public ImportReport processImport(String xmlFileName, MultiSilverpeasBundle resource) throws ImportExportException { ImportReport importReport = importExport.processImport(getUserDetail(), xmlFileName);
core-war/src/main/java/org/silverpeas/web/importexport/servlets/ImportExportRequestRouter.java+9 −10 modified@@ -23,23 +23,20 @@ */ package org.silverpeas.web.importexport.servlets; +import org.apache.commons.fileupload.FileItem; import org.silverpeas.core.importexport.report.ExportPDFReport; import org.silverpeas.core.importexport.report.ExportReport; import org.silverpeas.core.importexport.report.ImportReport; -import org.silverpeas.web.importexport.control.ImportExportSessionController; - +import org.silverpeas.core.node.model.NodePK; +import org.silverpeas.core.util.MultiSilverpeasBundle; +import org.silverpeas.core.util.WAAttributeValuePair; +import org.silverpeas.core.util.file.FileRepositoryManager; import org.silverpeas.core.util.file.FileUploadUtil; - +import org.silverpeas.core.web.http.HttpRequest; import org.silverpeas.core.web.mvc.controller.ComponentContext; import org.silverpeas.core.web.mvc.controller.MainSessionController; import org.silverpeas.core.web.mvc.route.ComponentRequestRouter; -import org.silverpeas.core.util.MultiSilverpeasBundle; -import org.silverpeas.core.util.file.FileRepositoryManager; -import org.silverpeas.core.util.WAAttributeValuePair; -import org.silverpeas.core.node.model.NodePK; - -import org.apache.commons.fileupload.FileItem; -import org.silverpeas.core.web.http.HttpRequest; +import org.silverpeas.web.importexport.control.ImportExportSessionController; import java.io.File; import java.util.List; @@ -83,8 +80,10 @@ public String getDestination(String function, ImportExportSessionController impo String destination = ""; try { if (function.startsWith("Main")) { + importExportSC.checkAdminAccessOnly(); destination = "/importExportPeas/jsp/welcome.jsp"; } else if ("Import".equals(function)) { + importExportSC.checkAdminAccessOnly(); File file = null; List<FileItem> items = request.getFileItems(); for (FileItem item : items) {
core-war/src/main/java/org/silverpeas/web/jobdomain/control/JobDomainPeasSessionController.java+159 −2 modified@@ -88,7 +88,7 @@ import org.silverpeas.core.util.logging.SilverLogger; import org.silverpeas.core.web.authentication.LoginServlet; import org.silverpeas.core.web.http.HttpRequest; -import org.silverpeas.core.web.mvc.controller.AbstractComponentSessionController; +import org.silverpeas.core.web.mvc.controller.AbstractAdminComponentSessionController; import org.silverpeas.core.web.mvc.controller.ComponentContext; import org.silverpeas.core.web.mvc.controller.MainSessionController; import org.silverpeas.core.web.mvc.webcomponent.WebMessager; @@ -116,14 +116,15 @@ import static org.silverpeas.core.admin.domain.DomainDriverManagerProvider.getCurrentDomainDriverManager; import static org.silverpeas.core.personalization.service.PersonalizationServiceProvider.getPersonalizationService; import static org.silverpeas.core.util.ResourceLocator.getSettingBundle; +import static org.silverpeas.core.util.StringUtil.defaultStringIfNotDefined; import static org.silverpeas.core.util.StringUtil.isDefined; /** * Class declaration * * @author */ -public class JobDomainPeasSessionController extends AbstractComponentSessionController { +public class JobDomainPeasSessionController extends AbstractAdminComponentSessionController { public static final String REPLACE_RIGHTS = "1"; public static final String ADD_RIGHTS = "2"; @@ -179,6 +180,15 @@ public JobDomainPeasSessionController(MainSessionController mainSessionCtrl, .getString("customersTemplatePath")); } + /** + * Dedicated for tests + */ + protected JobDomainPeasSessionController(final MainSessionController controller, + final ComponentContext context, final String localizedMessagesBundleName, + final String iconFileName, final String settingsFileName) { + super(controller, context, localizedMessagesBundleName, iconFileName, settingsFileName); + } + public int getMinLengthLogin() { return JobDomainSettings.m_MinLengthLogin; } @@ -187,11 +197,95 @@ public boolean isUserAddingAllowedForGroupManager() { return JobDomainSettings.m_UserAddingAllowedForGroupManagers; } + @Override public boolean isAccessGranted() { return getUserDetail().isAccessAdmin() || getUserDetail().isAccessDomainManager() || isOnlySpaceManager() || !getUserManageableGroupIds().isEmpty(); } + public void checkDomainAccessGranted(final String domainId) { + checkDomainAccessGranted(domainId, true); + } + + public void checkCurrentDomainAccessGranted(final boolean readOnly) { + final String domainId = Optional.ofNullable(getTargetDomain()).map(Domain::getId).orElse(null); + checkDomainAccessGranted(domainId, readOnly); + } + + void checkDomainAccessGranted(final String domainId, final boolean readOnly) { + checkAccessGranted(domainId, new DomainAccessContext(), readOnly); + } + + public void checkUserAccessGranted(final String userId, final boolean readOnly) { + final User user = getUserDetail(userId); + if (user == null || getTargetDomain() == null || + !getTargetDomain().getId().equals(user.getDomainId()) || + getTargetDomain().getId().equals(Domain.MIXED_DOMAIN_ID)) { + throwForbiddenError(); + } else { + checkAccessGranted(user.getDomainId(), new UserAccessContext(user), readOnly); + } + } + + public void checkGroupAccessGranted(final String groupId, final boolean readOnly) { + final Group group = getOrganisationController().getGroup(groupId); + if (group == null) { + throwForbiddenError(); + } else { + final String domainId = defaultStringIfNotDefined(group.getDomainId(), Domain.MIXED_DOMAIN_ID); + if (getTargetDomain() == null || !getTargetDomain().getId().equals(domainId)) { + throwForbiddenError(); + } else { + checkAccessGranted(domainId, new GroupAccessContext(group), readOnly); + } + } + } + + private <T> void checkAccessGranted(final String domainId, final AccessContext<T> accessContext, + final boolean readOnly) { + final UserDetail ud = getUserDetail(); + final boolean granted = ud.isAccessAdmin() || isAccessGrantedOnNotFullAdminAccess( + defaultStringIfNotDefined(domainId, Domain.MIXED_DOMAIN_ID), accessContext, readOnly); + if (!granted) { + throwForbiddenError(); + } + } + + private <T> boolean isAccessGrantedOnNotFullAdminAccess(final String domainId, + final AccessContext<T> accessContext, final boolean readOnly) { + final UserDetail ud = getUserDetail(); + final boolean equalsUserDomain = domainId.equals(ud.getDomainId()); + boolean granted = ud.isAccessDomainManager() && equalsUserDomain; + if (!granted && (readOnly || accessContext.getType().isGroup())) { + Stream<Pair<Group, String>> groupStream = getUserManageableGroups().stream() + .map(g -> Pair.of(g, defaultStringIfNotDefined(g.getDomainId(), Domain.MIXED_DOMAIN_ID))) + .filter(p -> !ud.isDomainAdminRestricted() || + p.getSecond().equals(Domain.MIXED_DOMAIN_ID) || + p.getSecond().equals(ud.getDomainId())); + if (!readOnly && accessContext.getType().isGroup()) { + final Group aimedGroup = (Group) accessContext.getResource(); + if (aimedGroup == null) { + groupStream = groupStream.filter(g -> false); + } else { + final List<String> aimedGroupPath = Stream.concat( + Stream.of(aimedGroup.getId()), + Optional.of(aimedGroup) + .filter(g -> isDefined(g.getSuperGroupId())) + .stream() + .flatMap(g -> getOrganisationController().getPathToGroup(g.getId()).stream())) + .collect(toList()); + groupStream = groupStream.filter(p -> aimedGroupPath.contains(p.getFirst().getId())); + } + } + granted = groupStream.map(Pair::getSecond).anyMatch(domainId::equals); + if (!granted && readOnly) { + granted = (equalsUserDomain || Domain.MIXED_DOMAIN_ID.equals(domainId)) && + (isOnlySpaceManager() || isCommunityManager()); + } + } + return granted; + } + public void setRefreshDomain(boolean refreshDomain) { this.refreshDomain = refreshDomain; } @@ -200,6 +294,9 @@ public void setRefreshDomain(boolean refreshDomain) { * USER functions */ public void setTargetUser(String userId) { + if (isDefined(userId)) { + checkUserAccessGranted(userId, true); + } targetUserId = userId; processIndex(targetUserId); } @@ -1455,6 +1552,7 @@ public void setTargetDomain(String domainId) { targetDomain = null; targetDomainId = ""; } else { + checkDomainAccessGranted(domainId); List<String> manageableGroupIds = null; targetDomainId = domainId; if (isOnlyGroupManager()) { @@ -2409,4 +2507,63 @@ public boolean isOnlySpaceManager() { !isOnlyGroupManager() && !isManagerOfCurrentDomain() && ArrayUtil.isNotEmpty(getUserManageableSpaceIds()); } + + /** + * In order to check the user granted access to domain services, the context of use MUST be set. + * <p> + * This context is an implementation of this abstraction. + * </p> + * @param <T> + */ + private static abstract class AccessContext<T> { + private final AccessContextType type; + private final T resource; + + private AccessContext(final AccessContextType accessContext, final T resource) { + this.type = accessContext; + this.resource = resource; + } + + public AccessContextType getType() { + return type; + } + + public T getResource() { + return resource; + } + + enum AccessContextType { + DOMAIN, GROUP, USER; + boolean isGroup() { + return this == GROUP; + } + } + } + + /** + * Context of a domain access. + */ + private static class DomainAccessContext extends AccessContext<Void> { + private DomainAccessContext() { + super(AccessContextType.DOMAIN, null); + } + } + + /** + * Context of user services access. + */ + private static class UserAccessContext extends AccessContext<User> { + private UserAccessContext(final User user) { + super(AccessContextType.USER, user); + } + } + + /** + * Context of group services access. + */ + private static class GroupAccessContext extends AccessContext<Group> { + private GroupAccessContext(final Group group) { + super(AccessContextType.GROUP, group); + } + } } \ No newline at end of file
core-war/src/main/java/org/silverpeas/web/jobdomain/servlets/JobDomainPeasRequestRouter.java+48 −18 modified@@ -52,7 +52,7 @@ import org.silverpeas.core.web.http.RequestParameterDecoder; import org.silverpeas.core.web.mvc.controller.ComponentContext; import org.silverpeas.core.web.mvc.controller.MainSessionController; -import org.silverpeas.core.web.mvc.route.ComponentRequestRouter; +import org.silverpeas.core.web.mvc.route.AdminComponentRequestRouter; import org.silverpeas.core.web.selection.Selection; import org.silverpeas.web.jobdomain.JobDomainPeasException; import org.silverpeas.web.jobdomain.JobDomainSettings; @@ -72,7 +72,7 @@ import static org.silverpeas.web.jobdomain.servlets.RemovedUserUIEntity.convertRemovedUserList; public class JobDomainPeasRequestRouter extends - ComponentRequestRouter<JobDomainPeasSessionController> { + AdminComponentRequestRouter<JobDomainPeasSessionController> { private static final long serialVersionUID = 1L; @@ -120,6 +120,8 @@ public class JobDomainPeasRequestRouter extends private static final String DISPLAY_REMOVED_USERS_DEST = "displayRemovedUsers"; private static final String DOMAIN_USER_FILTER_MANAGEMENT_DEST = "domainUserFilterManagement.jsp"; private static final String IS_ONLY_SPACE_MANAGER_ATTR = "isOnlySpaceManager"; + private static final String WRITE_OPERATION_PARTS = + "(?i)^.*(create|update|modify|delete|remove|block|activate|import|synchro).*$"; @Override public JobDomainPeasSessionController createComponentSessionController( @@ -147,15 +149,12 @@ public String getSessionControlBeanName() { * "/almanach/jsp/almanach.jsp?flag=user") */ @Override - public String getDestination(String function, JobDomainPeasSessionController jobDomainSC, + public String getAdminDestination(String function, JobDomainPeasSessionController jobDomainSC, HttpRequest request) { String destination = ""; try { - if (!jobDomainSC.isAccessGranted()) { - throw new JobDomainPeasException("Bad right for user {0}", jobDomainSC.getUserId()); - } // 1) Performs the action // ---------------------- if (function.startsWith("selectUserOrGroup")) { @@ -176,6 +175,7 @@ public String getDestination(String function, JobDomainPeasSessionController job } if ("blankUsers".equals(function)) { + jobDomainSC.checkCurrentDomainAccessGranted(false); final List<String> userIds = new ArrayList<>(); request.mergeSelectedItemsInto(userIds); if (!userIds.isEmpty()) { @@ -197,20 +197,23 @@ public String getDestination(String function, JobDomainPeasSessionController job jobDomainSC.setTargetUser(user.getId()); destination = USER_CONTENT_DEST; } else if ("restoreUsers".equals(function)) { + jobDomainSC.checkCurrentDomainAccessGranted(false); final List<String> userIds = new ArrayList<>(); request.mergeSelectedItemsInto(userIds); for (final String u : userIds) { jobDomainSC.restoreUser(u); } destination = getDestination(DISPLAY_REMOVED_USERS_DEST, jobDomainSC, request); } else if ("deleteUsers".equals(function)) { + jobDomainSC.checkCurrentDomainAccessGranted(false); final List<String> userIds = new ArrayList<>(); request.mergeSelectedItemsInto(userIds); for (final String u : userIds) { jobDomainSC.deleteUser(u); } destination = getDestination(DISPLAY_REMOVED_USERS_DEST, jobDomainSC, request); } else if ("restoreGroups".equals(function)) { + jobDomainSC.checkCurrentDomainAccessGranted(false); final List<String> groupIds = new ArrayList<>(); request.mergeSelectedItemsInto(groupIds); boolean refreshDomainNav = false; @@ -222,6 +225,7 @@ public String getDestination(String function, JobDomainPeasSessionController job } destination = getDestination(DISPLAY_REMOVED_GROUPS_DEST, jobDomainSC, request); } else if ("deleteGroups".equals(function)) { + jobDomainSC.checkCurrentDomainAccessGranted(false); final List<String> groupIds = new ArrayList<>(); request.mergeSelectedItemsInto(groupIds); for (final String group : groupIds) { @@ -231,6 +235,14 @@ public String getDestination(String function, JobDomainPeasSessionController job } else if (function.startsWith("user")) { // USER Actions -------------------------------------------- String userId = request.getParameter("Iduser"); + final boolean readOperation = !function.matches(WRITE_OPERATION_PARTS); + if (isDefined(userId)) { + jobDomainSC.checkUserAccessGranted(userId, readOperation); + } else if (jobDomainSC.getTargetUserDetail() != null) { + jobDomainSC.checkUserAccessGranted(jobDomainSC.getTargetUserDetail().getId(), readOperation); + } else { + jobDomainSC.checkCurrentDomainAccessGranted(readOperation); + } if (function.startsWith(USER_CONTENT_FCT)) { if (isDefined(userId)) { jobDomainSC.setTargetUser(userId); @@ -406,44 +418,55 @@ public String getDestination(String function, JobDomainPeasSessionController job boolean bHaveToRefreshDomain = false; jobDomainSC.setTargetUser(null); + String groupId = request.getParameter(IDGROUP_PARAM); + final boolean readOperation = !function.matches(WRITE_OPERATION_PARTS); + if (isDefined(groupId)) { + jobDomainSC.checkGroupAccessGranted(groupId, readOperation); + } else if (jobDomainSC.getTargetGroup() != null) { + jobDomainSC.checkGroupAccessGranted(jobDomainSC.getTargetGroup().getId(), readOperation); + } else { + jobDomainSC.checkCurrentDomainAccessGranted(readOperation); + } // Browse functions // ---------------- if (function.startsWith(GROUP_CONTENT_FCT)) { - String groupId = request.getParameter(IDGROUP_PARAM); if (isDefined(groupId)) { jobDomainSC.goIntoGroup(groupId); } } else if (function.startsWith("groupExport.txt")) { - String groupId = request.getParameter(IDGROUP_PARAM); if (isDefined(groupId)) { - jobDomainSC.goIntoGroup(request.getParameter(IDGROUP_PARAM)); + jobDomainSC.goIntoGroup(groupId); destination = "exportgroup.jsp"; } } else if (function.startsWith("groupReturn")) { - jobDomainSC.returnIntoGroup(request.getParameter(IDGROUP_PARAM)); + jobDomainSC.returnIntoGroup(groupId); } else if (function.startsWith("groupSet")) { jobDomainSC.returnIntoGroup(null); - jobDomainSC.goIntoGroup(request.getParameter(IDGROUP_PARAM)); + jobDomainSC.goIntoGroup(groupId); } else if (function.startsWith("groupCreate")) { - bHaveToRefreshDomain = jobDomainSC.createGroup(request.getParameter("Idparent"), + final String parentGroupId = request.getParameter("Idparent"); + if (isDefined(parentGroupId)) { + jobDomainSC.checkGroupAccessGranted(parentGroupId, false); + } + bHaveToRefreshDomain = jobDomainSC.createGroup(parentGroupId, WebEncodeHelper.htmlStringToJavaString(request.getParameter(GROUP_NAME_PARAM)), WebEncodeHelper.htmlStringToJavaString(request.getParameter("groupDescription")), request.getParameter("groupRule")); } else if (function.startsWith("groupUpdate")) { - bHaveToRefreshDomain = jobDomainSC.modifyGroup(request.getParameter(IDGROUP_PARAM), + bHaveToRefreshDomain = jobDomainSC.modifyGroup(groupId, WebEncodeHelper.htmlStringToJavaString(request.getParameter(GROUP_NAME_PARAM)), WebEncodeHelper.htmlStringToJavaString(request.getParameter("groupDescription")), request.getParameter("groupRule")); } else if (function.startsWith("groupAddRemoveUsers")) { bHaveToRefreshDomain = jobDomainSC .updateGroupSubUsers(jobDomainSC.getTargetGroup().getId(), jobDomainSC.getSelectedUsersIds()); } else if (function.startsWith("groupRemove")) { - bHaveToRefreshDomain = jobDomainSC.removeGroup(request.getParameter(IDGROUP_PARAM)); + bHaveToRefreshDomain = jobDomainSC.removeGroup(groupId); } else if (function.startsWith("groupDelete")) { - bHaveToRefreshDomain = jobDomainSC.deleteGroup(request.getParameter(IDGROUP_PARAM)); + bHaveToRefreshDomain = jobDomainSC.deleteGroup(groupId); } else if (function.startsWith("groupSynchro")) { - final Optional<Group> synchronizedGroup = jobDomainSC.synchroGroup(request.getParameter(IDGROUP_PARAM)); + final Optional<Group> synchronizedGroup = jobDomainSC.synchroGroup(groupId); if (synchronizedGroup.isPresent()) { final Group group = synchronizedGroup.get(); if (group.isRemovedState()) { @@ -453,7 +476,7 @@ public String getDestination(String function, JobDomainPeasSessionController job } } } else if (function.startsWith("groupUnSynchro")) { - bHaveToRefreshDomain = jobDomainSC.unsynchroGroup(request.getParameter(IDGROUP_PARAM)); + bHaveToRefreshDomain = jobDomainSC.unsynchroGroup(groupId); } else if (function.startsWith("groupImport")) { bHaveToRefreshDomain = jobDomainSC.importGroup(WebEncodeHelper.htmlStringToJavaString(request.getParameter( GROUP_NAME_PARAM))); @@ -480,7 +503,7 @@ public String getDestination(String function, JobDomainPeasSessionController job destination = getDestination("groupManagersView", jobDomainSC, request); } else if ("groupOpen".equals(function)) { - String groupId = request.getParameter("groupId"); + groupId = request.getParameter("groupId"); if (jobDomainSC.isAccessGranted() || jobDomainSC.isGroupManagerOnGroup(groupId)) { OrganizationController orgaController = jobDomainSC.getOrganisationController(); @@ -529,6 +552,12 @@ public String getDestination(String function, JobDomainPeasSessionController job // DOMAIN Actions -------------------------------------------- } else if (function.startsWith(DOMAIN_ATTR)) { jobDomainSC.setTargetUser(null); + final boolean writeOperation = function.matches(WRITE_OPERATION_PARTS); + if (writeOperation) { + jobDomainSC.checkAdminAccessOnly(); + } else if (jobDomainSC.getTargetDomain() != null) { + jobDomainSC.checkCurrentDomainAccessGranted(true); + } if (function.startsWith("domainModifyUserFilter")) { destination = handleUserFilterModification(jobDomainSC, request); } else if (function.startsWith("domainGoTo")) { @@ -811,6 +840,7 @@ public String getDestination(String function, JobDomainPeasSessionController job } else if ("SelectRightsUserOrGroup".equals(function)) { destination = jobDomainSC.initSelectionRightsUserOrGroup(); } else if ("AssignSameRights".equals(function)) { + jobDomainSC.checkAdminAccessOnly(); if (!jobDomainSC.isRightCopyReplaceEnabled()) { throwHttpForbiddenError(); }
core-war/src/main/java/org/silverpeas/web/jobmanager/control/JobManagerPeasSessionController.java+35 −14 modified@@ -28,7 +28,7 @@ import org.silverpeas.core.util.ArrayUtil; import org.silverpeas.core.util.logging.SilverLogger; import org.silverpeas.core.wbe.WbeSettings; -import org.silverpeas.core.web.mvc.controller.AbstractComponentSessionController; +import org.silverpeas.core.web.mvc.controller.AbstractAdminComponentSessionController; import org.silverpeas.core.web.mvc.controller.ComponentContext; import org.silverpeas.core.web.mvc.controller.MainSessionController; import org.silverpeas.web.jobmanager.JobManagerService; @@ -48,7 +48,7 @@ * * @author */ -public class JobManagerPeasSessionController extends AbstractComponentSessionController { +public class JobManagerPeasSessionController extends AbstractAdminComponentSessionController { private Map<String, JobManagerService> services = null; private String idCurrentServiceActif = null; @@ -144,7 +144,7 @@ private void initServices() { JobManagerService jSTAT4 = new JobManagerService("34", "JSTAT4", LEVEL_OPERATION, webContext + getURL(CMP_SILVERSTATISTICSPEAS, null, null) + "ViewPDCAccess", null, false); - boolean kmServiceAllowed = false; + boolean pdcServicesAdded = false; int nbServices = 0; JobManagerSettings jobManagerSettings = JobManagerSettings.get(); @@ -162,7 +162,7 @@ private void initServices() { services.put(jKM.getId(), jKM); services.put(jKM1.getId(), jKM1); services.put(jKM2.getId(), jKM2); - kmServiceAllowed = true; + pdcServicesAdded = true; } if (jobManagerSettings.isToolSpecificAuthentVisible() || @@ -241,7 +241,7 @@ private void initServices() { services.put(jKM1.getId(), jKM1); services.put(jKM2.getId(), jKM2); - kmServiceAllowed = true; + pdcServicesAdded = true; } services.put(jSTAT.getId(), jSTAT); @@ -255,7 +255,7 @@ private void initServices() { services.put(jKM1.getId(), jKM1); services.put(jKM2.getId(), jKM2); - kmServiceAllowed = true; + pdcServicesAdded = true; } else if (getUserDetail().isAccessDomainManager() || !getUserManageableGroupIds().isEmpty()) { String[] id1 = { jdp.getId() }; jDesigner = new JobManagerService("1", "JD", LEVEL_SERVICE, null, id1, false); @@ -265,15 +265,9 @@ private void initServices() { services.put(jdp.getId(), jdp); } - boolean isPDCManager = false; - - try { - isPDCManager = PdcManager.get().isUserManager(getUserId()); - } catch (PdcException e) { - SilverLogger.getLogger(this).error(e); - } + final boolean isPDCManager = isPDCManager(); - if (!kmServiceAllowed && isPDCManager) { + if (!pdcServicesAdded && isPDCManager) { String[] id1 = { jKM1.getId() }; jKM = new JobManagerService(Integer.toString(nbServices + 1), "JKM", LEVEL_SERVICE, null, id1, false); @@ -283,6 +277,33 @@ private void initServices() { } } + @Override + public boolean isAccessGranted() { + boolean accessGranted = + getUserDetail().isAccessAdmin() || getUserManageableSpaceIds().length != 0; + if (!accessGranted) { + accessGranted = + getUserDetail().isAccessDomainManager() || !getUserManageableGroupIds().isEmpty(); + } + if (!accessGranted) { + JobManagerSettings jobManagerSettings = JobManagerSettings.get(); + accessGranted = jobManagerSettings.isKMVisible() && + (getUserDetail().isAccessPdcManager() || isPDCManager()); + } + return accessGranted; + } + + private boolean isPDCManager() { + boolean isPDCManager = false; + + try { + isPDCManager = PdcManager.get().isUserManager(getUserId()); + } catch (PdcException e) { + SilverLogger.getLogger(this).error(e); + } + return isPDCManager; + } + // retourne les services de niveau level // 0 => niveau Service // 1 => opération
core-war/src/main/java/org/silverpeas/web/jobmanager/servlets/JobManagerPeasRequestRouter.java+4 −3 modified@@ -26,7 +26,7 @@ import org.silverpeas.core.web.http.HttpRequest; import org.silverpeas.core.web.mvc.controller.ComponentContext; import org.silverpeas.core.web.mvc.controller.MainSessionController; -import org.silverpeas.core.web.mvc.route.ComponentRequestRouter; +import org.silverpeas.core.web.mvc.route.AdminComponentRequestRouter; import org.silverpeas.web.jobmanager.JobManagerService; import org.silverpeas.web.jobmanager.control.JobManagerPeasSessionController; @@ -40,7 +40,7 @@ * @author */ public class JobManagerPeasRequestRouter extends - ComponentRequestRouter<JobManagerPeasSessionController> { + AdminComponentRequestRouter<JobManagerPeasSessionController> { private static final long serialVersionUID = -2003485584890163789L; private static final String OPERATION_ACTION = "Operation"; @@ -78,7 +78,8 @@ public String getSessionControlBeanName() { * @return The complete destination URL for a forward (ex : * "/almanach/jsp/almanach.jsp?flag=user") */ - public String getDestination(String function, JobManagerPeasSessionController jobManagerSC, + @Override + public String getAdminDestination(String function, JobManagerPeasSessionController jobManagerSC, HttpRequest request) { String destination; try {
core-war/src/main/java/org/silverpeas/web/jobstartpage/control/JobStartPagePeasSessionController.java+23 −36 modified@@ -51,7 +51,6 @@ import org.silverpeas.core.clipboard.ClipboardSelection; import org.silverpeas.core.contribution.template.publication.PublicationTemplateException; import org.silverpeas.core.contribution.template.publication.PublicationTemplateManager; -import org.silverpeas.core.exception.SilverpeasRuntimeException; import org.silverpeas.core.i18n.I18NHelper; import org.silverpeas.core.template.SilverpeasTemplate; import org.silverpeas.core.template.SilverpeasTemplateFactory; @@ -69,7 +68,7 @@ import org.silverpeas.core.util.logging.SilverLogger; import org.silverpeas.core.util.memory.MemoryUnit; import org.silverpeas.core.web.look.SilverpeasLook; -import org.silverpeas.core.web.mvc.controller.AbstractComponentSessionController; +import org.silverpeas.core.web.mvc.controller.AbstractAdminComponentSessionController; import org.silverpeas.core.web.mvc.controller.ComponentContext; import org.silverpeas.core.web.mvc.controller.MainSessionController; import org.silverpeas.core.web.selection.Selection; @@ -93,7 +92,7 @@ * * @author */ -public class JobStartPagePeasSessionController extends AbstractComponentSessionController { +public class JobStartPagePeasSessionController extends AbstractAdminComponentSessionController { public static final int SCOPE_BACKOFFICE = 0; public static final int SCOPE_FRONTOFFICE = 1; @@ -130,13 +129,24 @@ public JobStartPagePeasSessionController(MainSessionController mainSessionCtrl, JobStartPagePeasSettings.CUSTOMERS_TEMPLATE_PATH); } + /** + * Dedicated for tests + */ + public JobStartPagePeasSessionController(final MainSessionController controller, + final ComponentContext context, final String localizedMessagesBundleName, + final String iconFileName, final String settingsFileName) { + super(controller, context, localizedMessagesBundleName, iconFileName, settingsFileName); + adminController = null; + } + // Init at first entry public void init() { m_NavBarMgr.initWithUser(this, getUserDetail()); } - public boolean isUserAdmin() { - return getUserDetail().isAccessAdmin(); + @Override + public boolean isAccessGranted() { + return isAccessGranted(getManagedSpaceId(), getManagedInstanceId(), true); } // method du spaceInst @@ -149,6 +159,7 @@ public SpaceInst getSpaceInstById() { public void setManagedSpaceId(String sId, boolean isManagedSpaceRoot) { String spaceId = getShortSpaceId(sId); + checkAccessGranted(spaceId, null, true); m_ManagedSpaceId = spaceId; m_isManagedSpaceRoot = isManagedSpaceRoot; @@ -250,6 +261,7 @@ public void refreshCurrentSpaceCache() { } public void setManagedInstanceId(String sId) { + checkAccessGranted(null, sId, true); m_ManagedInstanceId = sId; setScope(SCOPE_BACKOFFICE); } @@ -263,10 +275,6 @@ public String getManagedInstanceId() { return m_ManagedInstanceId; } - public boolean isComponentManageable(String componentId) { - return getOrganisationController().isComponentManageable(componentId, getUserId()); - } - public void setManagedProfile(ProfileInst sProfile) { m_ManagedProfile = sProfile; @@ -353,25 +361,6 @@ public SpaceInst[] getBrotherSpaces(boolean isNew) { return m_BrothersSpaces; } - // Get spaces "manageable" by the current user (ie spaces in maintenance or current space) - public SpaceInst[] getUserManageableSpacesIds() { - List<SpaceInst> vManageableSpaces = new ArrayList<SpaceInst>(); - String[] sids = getUserManageableSpaceIds(); - SpaceInst currentSpace = getSpaceInstById(); - String currentSpaceId = (currentSpace == null) ? "-1" : currentSpace.getId(); - - for (String sid : sids) { - if (isSpaceInMaintenance(sid.substring(2)) || sid.equals(currentSpaceId)) { - vManageableSpaces.add(adminController.getSpaceInstById(sid)); - } - } - - SpaceInst[] aManageableSpaces = vManageableSpaces.toArray( - new SpaceInst[vManageableSpaces.size()]); - Arrays.sort(aManageableSpaces, Comparator.comparing(SpaceInst::getOrderNum)); - return aManageableSpaces; - } - public void setSpacePlace(String idSpaceBefore) { int orderNum = 0; int i; @@ -1107,6 +1096,7 @@ public void cutComponent(String id) throws ClipboardException { } private void copyOrCutComponent(String id, boolean cut) throws ClipboardException { + checkAccessGranted(null, id, false); ComponentInst componentInst = getComponentInst(id); ComponentSelection compoSelect = new ComponentSelection(componentInst); compoSelect.setCutted(cut); @@ -1122,6 +1112,7 @@ public void cutSpace(String id) throws ClipboardException { } private void copyOrCutSpace(String id, boolean cut) throws ClipboardException { + checkAccessGranted(id, null, false); SpaceInst space = getSpaceInstById(id); SpaceSelection spaceSelect = new SpaceSelection(space); spaceSelect.setCutted(cut); @@ -1135,6 +1126,7 @@ private void copyOrCutSpace(String id, boolean cut) throws ClipboardException { * @throws JobStartPagePeasException */ public void paste(Map<String, String> options) throws ClipboardException, JobStartPagePeasException { + checkAccessGranted(getManagedSpaceId(), getManagedInstanceId(), false); try { Collection<ClipboardSelection> clipObjects = getClipboardSelectedObjects(); boolean refreshCache = false; @@ -1170,8 +1162,7 @@ public void paste(Map<String, String> options) throws ClipboardException, JobSta } } catch (Exception e) { m_NavBarMgr.resetAllCache(); - throw new JobStartPagePeasException("JobStartPagePeasSessionController.paste()", - SilverpeasRuntimeException.ERROR, "jobStartPagePeas.EX_PASTE_ERROR", e); + throw new JobStartPagePeasException(e); } clipboardPasteDone(); } @@ -1206,8 +1197,7 @@ public Set<String> getCopiedComponents() throws JobStartPagePeasException { } } } catch (Exception e) { - throw new JobStartPagePeasException("JobStartPagePeasSessionController.getCopiedComponents()", - SilverpeasRuntimeException.ERROR, "jobStartPagePeas.EX_PASTE_ERROR", e); + throw new JobStartPagePeasException(e); } return copiedComponents; } @@ -1228,8 +1218,7 @@ private void pasteComponent(PasteDetail pasteDetail) throws JobStartPagePeasExce refreshCurrentSpaceCache(); } } catch (Exception e) { - throw new JobStartPagePeasException("JobStartPagePeasSessionController.pasteComponent()", - SilverpeasRuntimeException.ERROR, "jobStartPagePeas.EX_PASTE_ERROR", + throw new JobStartPagePeasException( "componentId = " + pasteDetail.getFromComponentId() + " in space " + getManagedSpaceId(), e); } @@ -1252,8 +1241,6 @@ private void pasteSpace(PasteDetail pasteDetail) throws JobStartPagePeasExceptio } } catch (Exception e) { throw new JobStartPagePeasException( - "JobStartPagePeasSessionController.pasteSpace()", - SilverpeasRuntimeException.ERROR, "jobStartPagePeas.EX_PASTE_ERROR", "spaceId = " + pasteDetail.getFromSpaceId() + " in space " + getManagedSpaceId(), e); } }
core-war/src/main/java/org/silverpeas/web/jobstartpage/JobStartPagePeasException.java+8 −58 modified@@ -30,75 +30,25 @@ package org.silverpeas.web.jobstartpage; -import org.silverpeas.core.exception.SilverpeasException; +import org.silverpeas.core.SilverpeasException; /** - * Class declaration - * @author + * Exception when an error is raised from a Silverpeas space and component related job. */ public class JobStartPagePeasException extends SilverpeasException { private static final long serialVersionUID = 7820592552517371949L; - /** - * Constructor declaration - * @param callingClass - * @param errorLevel - * @param message - * - */ - public JobStartPagePeasException(String callingClass, int errorLevel, - String message) { - super(callingClass, errorLevel, message); - } - - /** - * Constructor declaration - * @param callingClass - * @param errorLevel - * @param message - * @param extraParams - * - */ - public JobStartPagePeasException(String callingClass, int errorLevel, - String message, String extraParams) { - super(callingClass, errorLevel, message, extraParams); - } - /** - * Constructor declaration - * @param callingClass - * @param errorLevel - * @param message - * @param nested - * - */ - public JobStartPagePeasException(String callingClass, int errorLevel, - String message, Exception nested) { - super(callingClass, errorLevel, message, nested); + public JobStartPagePeasException(final String message, String ... parameters) { + super(message, parameters); } - /** - * Constructor declaration - * @param callingClass - * @param errorLevel - * @param message - * @param extraParams - * @param nested - * - */ - public JobStartPagePeasException(String callingClass, int errorLevel, - String message, String extraParams, Exception nested) { - super(callingClass, errorLevel, message, extraParams, nested); + public JobStartPagePeasException(final String message, final Throwable cause) { + super(message, cause); } - /** - * Method declaration - * @return - * - */ - public String getModule() { - return "jobStartPagePeas"; + public JobStartPagePeasException(final Throwable cause) { + super(cause); } - }
core-war/src/main/java/org/silverpeas/web/jobstartpage/servlets/JobStartPagePeasRequestRouter.java+19 −19 modified@@ -43,7 +43,7 @@ import org.silverpeas.core.web.http.HttpRequest; import org.silverpeas.core.web.mvc.controller.ComponentContext; import org.silverpeas.core.web.mvc.controller.MainSessionController; -import org.silverpeas.core.web.mvc.route.ComponentRequestRouter; +import org.silverpeas.core.web.mvc.route.AdminComponentRequestRouter; import org.silverpeas.core.web.selection.Selection; import org.silverpeas.web.jobstartpage.JobStartPagePeasSettings; import org.silverpeas.web.jobstartpage.NavBarJsonEncoder; @@ -61,7 +61,8 @@ import static java.util.Optional.ofNullable; import static org.silverpeas.core.util.JSONCodec.encodeObject; -public class JobStartPagePeasRequestRouter extends ComponentRequestRouter<JobStartPagePeasSessionController> { +public class JobStartPagePeasRequestRouter extends + AdminComponentRequestRouter<JobStartPagePeasSessionController> { private static final long serialVersionUID = 3751632991093466433L; private static final String WELCOME_SPACE_ADMIN_TEMPLATE_FILE = "/space/welcome_space_admin_"; @@ -99,6 +100,8 @@ public class JobStartPagePeasRequestRouter extends ComponentRequestRouter<JobSta private static final String ROLE_INSTANCE_FULL_DEST = "/jobStartPagePeas/jsp/roleInstance.jsp"; private static final String ROLE_ITEMS_PREFIX = "roleItems"; private static final String SPACE_TYPE = "Space"; + private static final String WRITE_OPERATION_PARTS = + "(?i)^.*(create|update|modify|delete|remove|effective).*$"; @Override public JobStartPagePeasSessionController createComponentSessionController( @@ -233,10 +236,12 @@ public String getDestinationNavBar(String function, } destination = "/jobStartPagePeas/jsp/welcome.jsp"; } else if (VIEW_BIN_FCT.equals(function)) { + jobStartPageSC.checkAdminAccessOnly(); request.setAttribute("Spaces", jobStartPageSC.getRemovedSpaces()); request.setAttribute("Components", jobStartPageSC.getRemovedComponents()); destination = "/jobStartPagePeas/jsp/bin.jsp"; } else if ("RestoreFromBin".equals(function) || "RemoveDefinitely".equals(function)) { + jobStartPageSC.checkAdminAccessOnly(); final List<String> spaceIds; final List<String> componentIds; final String itemId = request.getParameter("ItemId"); @@ -304,13 +309,9 @@ public String getDestinationComponent(String function, COMPONENT_INFO_FULL_DEST; } else if ("SetupComponent".equals(function)) { String compoId = request.getParameter(COMPONENT_ID_PARAM); - if (jobStartPageSC.isComponentManageable(compoId)) { - jobStartPageSC.setManagedInstanceId(compoId, - JobStartPagePeasSessionController.SCOPE_FRONTOFFICE); - destination = getDestination("UpdateInstance", jobStartPageSC, request); - } else { - destination = "/admin/jsp/accessForbidden.jsp"; - } + jobStartPageSC.setManagedInstanceId(compoId, + JobStartPagePeasSessionController.SCOPE_FRONTOFFICE); + destination = getDestination("UpdateInstance", jobStartPageSC, request); } else if (GO_TO_CURRENT_COMPONENT_FCT.equals(function)) { destination = COMPONENT_INFO_FULL_DEST; } else if ("ListComponent".equals(function)) { @@ -466,15 +467,9 @@ public String getDestinationComponent(String function, destination = getDestination(WELCOME_FCT, jobStartPageSC, request); } } else if (function.equals("OpenComponent")) { - // check if user can update it - String id = request.getParameter(COMPONENT_ID_PARAM); - if (!jobStartPageSC.isComponentManageable(id)) { - destination = "/admin/jsp/accessForbidden.jsp"; - } else { - jobStartPageSC.init(); - request.setAttribute("PopupMode", true); - destination = getDestination(GO_TO_COMPONENT_FCT, jobStartPageSC, request); - } + jobStartPageSC.init(); + request.setAttribute("PopupMode", true); + destination = getDestination(GO_TO_COMPONENT_FCT, jobStartPageSC, request); } return destination; @@ -718,8 +713,13 @@ public String getDestinationSpace(String function, * "/almanach/jsp/almanach.jsp?flag=user") */ @Override - public String getDestination(String function, JobStartPagePeasSessionController jobStartPageSC, + public String getAdminDestination(String function, JobStartPagePeasSessionController jobStartPageSC, HttpRequest request) { + if (function.matches(WRITE_OPERATION_PARTS)) { + final String spaceId = jobStartPageSC.getManagedSpaceId(); + final String instanceId = jobStartPageSC.getManagedInstanceId(); + jobStartPageSC.checkAccessGranted(spaceId, instanceId, false); + } if (request.getParameterAsBoolean(HAVE_TO_REFRESH_NAV_BAR_ATTR)) { request.setAttribute(HAVE_TO_REFRESH_NAV_BAR_ATTR, Boolean.TRUE); }
core-war/src/main/java/org/silverpeas/web/pdc/control/PdcSessionController.java+13 −2 modified@@ -23,6 +23,7 @@ */ package org.silverpeas.web.pdc.control; +import org.silverpeas.core.SilverpeasRuntimeException; import org.silverpeas.core.admin.service.AdminController; import org.silverpeas.core.admin.user.model.Group; import org.silverpeas.core.admin.user.model.UserDetail; @@ -40,7 +41,7 @@ import org.silverpeas.core.util.ServiceProvider; import org.silverpeas.core.util.URLUtil; import org.silverpeas.core.util.logging.SilverLogger; -import org.silverpeas.core.web.mvc.controller.AbstractComponentSessionController; +import org.silverpeas.core.web.mvc.controller.AbstractAdminComponentSessionController; import org.silverpeas.core.web.mvc.controller.ComponentContext; import org.silverpeas.core.web.mvc.controller.MainSessionController; import org.silverpeas.core.web.selection.Selection; @@ -54,7 +55,8 @@ import java.util.Iterator; import java.util.List; -public class PdcSessionController extends AbstractComponentSessionController { +public class PdcSessionController extends AbstractAdminComponentSessionController { + private static final long serialVersionUID = -7993993070048344281L; private String currentView = "P"; private Axis currentAxis = null; @@ -76,6 +78,15 @@ public PdcSessionController(MainSessionController mainSessionCtrl, currentLanguage = getLanguage(); } + @Override + public boolean isAccessGranted() { + try { + return isPDCAdmin() || getPdcBm().isUserManager(getUserId()); + } catch (PdcException e) { + throw new SilverpeasRuntimeException(e); + } + } + private PdcManager getPdcBm() { return PdcServiceProvider.getPdcManager(); }
core-war/src/main/java/org/silverpeas/web/pdc/servlets/PdcRequestRouter.java+3 −3 modified@@ -34,7 +34,7 @@ import org.silverpeas.core.web.http.HttpRequest; import org.silverpeas.core.web.mvc.controller.ComponentContext; import org.silverpeas.core.web.mvc.controller.MainSessionController; -import org.silverpeas.core.web.mvc.route.ComponentRequestRouter; +import org.silverpeas.core.web.mvc.route.AdminComponentRequestRouter; import org.silverpeas.core.web.mvc.util.AccessForbiddenException; import org.silverpeas.web.pdc.control.PdcSessionController; @@ -44,7 +44,7 @@ import java.util.List; import java.util.StringTokenizer; -public class PdcRequestRouter extends ComponentRequestRouter<PdcSessionController> { +public class PdcRequestRouter extends AdminComponentRequestRouter<PdcSessionController> { private static final long serialVersionUID = -1233766141114104308L; @@ -85,7 +85,7 @@ public String getSessionControlBeanName() { * "/notificationUser/jsp/notificationUser.jsp?flag=user") */ @Override - public String getDestination(String function, PdcSessionController pdcSC, + public String getAdminDestination(String function, PdcSessionController pdcSC, HttpRequest request) { String destination = ""; try {
core-war/src/main/java/org/silverpeas/web/silverstatistics/control/SilverStatisticsPeasSessionController.java+8 −2 modified@@ -58,7 +58,7 @@ import org.silverpeas.core.util.UnitUtil; import org.silverpeas.core.util.logging.SilverLogger; import org.silverpeas.core.util.memory.MemoryUnit; -import org.silverpeas.core.web.mvc.controller.AbstractComponentSessionController; +import org.silverpeas.core.web.mvc.controller.AbstractAdminComponentSessionController; import org.silverpeas.core.web.mvc.controller.ComponentContext; import org.silverpeas.core.web.mvc.controller.MainSessionController; import org.silverpeas.core.web.selection.Selection; @@ -85,7 +85,8 @@ * Class declaration * @author */ -public class SilverStatisticsPeasSessionController extends AbstractComponentSessionController { +public class SilverStatisticsPeasSessionController extends AbstractAdminComponentSessionController { + private static final long serialVersionUID = -8394342857531676676L; public static final int INDICE_VALUE = 0; public static final int INDICE_LIB = 1; @@ -174,6 +175,11 @@ public SilverStatisticsPeasSessionController(MainSessionController mainSessionCt initYears(); } + @Override + public boolean isAccessGranted() { + return isAccessGranted(null, null, true); + } + public UserAccessLevel getUserProfile() { if (getUserDetail().isAccessAdmin()) { return getUserDetail().getAccessLevel();
core-war/src/main/java/org/silverpeas/web/silverstatistics/servlets/SilverStatisticsPeasRequestRouter.java+20 −8 modified@@ -23,35 +23,39 @@ */ package org.silverpeas.web.silverstatistics.servlets; +import org.apache.commons.lang3.math.NumberUtils; +import org.silverpeas.core.admin.user.constant.UserAccessLevel; +import org.silverpeas.core.chart.period.PeriodChart; +import org.silverpeas.core.chart.pie.PieChart; import org.silverpeas.core.util.StringUtil; +import org.silverpeas.core.web.http.HttpRequest; import org.silverpeas.core.web.mvc.controller.ComponentContext; import org.silverpeas.core.web.mvc.controller.MainSessionController; -import org.silverpeas.core.web.mvc.route.ComponentRequestRouter; +import org.silverpeas.core.web.mvc.route.AdminComponentRequestRouter; import org.silverpeas.web.silverstatistics.control.SilverStatisticsPeasSessionController; import org.silverpeas.web.silverstatistics.vo.AxisStatsFilter; import org.silverpeas.web.silverstatistics.vo.CrossAxisStatsFilter; import org.silverpeas.web.silverstatistics.vo.CrossStatisticVO; import org.silverpeas.web.silverstatistics.vo.StatisticVO; -import org.apache.commons.lang3.math.NumberUtils; -import org.silverpeas.core.admin.user.constant.UserAccessLevel; -import org.silverpeas.core.chart.period.PeriodChart; -import org.silverpeas.core.chart.pie.PieChart; -import org.silverpeas.core.web.http.HttpRequest; import javax.servlet.http.HttpServletRequest; import java.util.Calendar; import java.util.List; +import static org.silverpeas.core.util.StringUtil.isNotDefined; + /** * Class declaration * @author */ public class SilverStatisticsPeasRequestRouter extends - ComponentRequestRouter<SilverStatisticsPeasSessionController> { + AdminComponentRequestRouter<SilverStatisticsPeasSessionController> { private static final long serialVersionUID = -7422373100761515806L; private final SilverStatisticsActionAccessController actionAccessController = new SilverStatisticsActionAccessController(); + private static final String PDC_FUNCTION = "(?i)^.*(pdc).*$"; + private static final String SPACE_MANAGER_FUNCTION = "(?i)^.*(access|volume).*$"; /** * Method declaration @@ -87,7 +91,7 @@ public String getSessionControlBeanName() { * "/almanach/jsp/almanach.jsp?flag=user") */ @Override - public String getDestination(String function, SilverStatisticsPeasSessionController statsSC, + public String getAdminDestination(String function, SilverStatisticsPeasSessionController statsSC, HttpRequest request) { String destination = ""; UserAccessLevel userProfile = statsSC.getUserProfile(); @@ -98,6 +102,14 @@ public String getDestination(String function, SilverStatisticsPeasSessionControl return null; } + if (function.matches(SPACE_MANAGER_FUNCTION) && !function.matches(PDC_FUNCTION)) { + final String accessSpaceId = StringUtil.defaultStringIfNotDefined( + request.getParameter("SpaceId"), statsSC.getAccessSpaceId()); + statsSC.checkAccessGranted(accessSpaceId, null, isNotDefined(accessSpaceId)); + } else { + statsSC.checkAdminAccessOnly(); + } + Calendar calendar = Calendar.getInstance(); String monthOfCurrentYear = String.valueOf(calendar.get(Calendar.MONTH)); String currentYear = String.valueOf(calendar.get(Calendar.YEAR));
core-war/src/main/java/org/silverpeas/web/templatedesigner/control/TemplateDesignerSessionController.java+3 −2 modified@@ -45,7 +45,7 @@ import org.silverpeas.core.util.file.FileFolderManager; import org.silverpeas.core.util.file.FileRepositoryManager; import org.silverpeas.core.util.logging.SilverLogger; -import org.silverpeas.core.web.mvc.controller.AbstractComponentSessionController; +import org.silverpeas.core.web.mvc.controller.AbstractAdminComponentSessionController; import org.silverpeas.core.web.mvc.controller.ComponentContext; import org.silverpeas.core.web.mvc.controller.MainSessionController; import org.silverpeas.core.web.mvc.webcomponent.WebMessager; @@ -64,7 +64,8 @@ import java.util.Map; import java.util.Objects; -public class TemplateDesignerSessionController extends AbstractComponentSessionController { +public class TemplateDesignerSessionController extends AbstractAdminComponentSessionController { + private static final long serialVersionUID = -6869148156113542542L; private static final String VIEW_HTML = "view.html"; private static final String UPDATE_HTML = "update.html";
core-war/src/main/java/org/silverpeas/web/templatedesigner/servlets/TemplateDesignerRequestRouter.java+15 −18 modified@@ -23,42 +23,39 @@ */ package org.silverpeas.web.templatedesigner.servlets; -import java.io.File; -import java.io.IOException; -import java.util.Arrays; -import java.util.Enumeration; -import java.util.List; - -import javax.servlet.http.HttpServletRequest; - import org.apache.commons.fileupload.FileItem; -import org.silverpeas.core.contribution.content.form.field.PublicationsPickerField; -import org.silverpeas.core.security.encryption.cipher.CryptoException; - import org.silverpeas.core.contribution.content.form.DataRecord; import org.silverpeas.core.contribution.content.form.FieldTemplate; import org.silverpeas.core.contribution.content.form.Form; import org.silverpeas.core.contribution.content.form.FormException; import org.silverpeas.core.contribution.content.form.PagesContext; -import org.silverpeas.core.pdc.form.fieldtype.PdcField; +import org.silverpeas.core.contribution.content.form.field.PublicationsPickerField; import org.silverpeas.core.contribution.content.form.record.GenericFieldTemplate; import org.silverpeas.core.contribution.content.form.record.Label; import org.silverpeas.core.contribution.content.form.record.Parameter; import org.silverpeas.core.contribution.content.form.record.ParameterValue; import org.silverpeas.core.contribution.template.publication.PublicationTemplate; import org.silverpeas.core.contribution.template.publication.PublicationTemplateImpl; -import org.silverpeas.web.templatedesigner.control.TemplateDesignerSessionController; +import org.silverpeas.core.pdc.form.fieldtype.PdcField; +import org.silverpeas.core.security.encryption.cipher.CryptoException; import org.silverpeas.core.util.StringUtil; +import org.silverpeas.core.util.file.FileRepositoryManager; import org.silverpeas.core.util.file.FileUploadUtil; import org.silverpeas.core.web.http.HttpRequest; - import org.silverpeas.core.web.mvc.controller.ComponentContext; import org.silverpeas.core.web.mvc.controller.MainSessionController; -import org.silverpeas.core.web.mvc.route.ComponentRequestRouter; -import org.silverpeas.core.util.file.FileRepositoryManager; +import org.silverpeas.core.web.mvc.route.AdminComponentRequestRouter; +import org.silverpeas.web.templatedesigner.control.TemplateDesignerSessionController; + +import javax.servlet.http.HttpServletRequest; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.List; public class TemplateDesignerRequestRouter extends - ComponentRequestRouter<TemplateDesignerSessionController> { + AdminComponentRequestRouter<TemplateDesignerSessionController> { private static final long serialVersionUID = 1117593114737219878L; @@ -96,7 +93,7 @@ public TemplateDesignerSessionController createComponentSessionController( * "/almanach/jsp/almanach.jsp?flag=user") */ @Override - public String getDestination(String function, + public String getAdminDestination(String function, TemplateDesignerSessionController templateDesignerSC, HttpRequest request) { String destination = "";
core-war/src/main/java/org/silverpeas/web/variables/VariablesRequestRouter.java+5 −8 modified@@ -23,15 +23,16 @@ */ package org.silverpeas.web.variables; -import org.silverpeas.core.admin.user.model.User; import org.silverpeas.core.util.SilverpeasList; import org.silverpeas.core.util.StringUtil; import org.silverpeas.core.web.http.HttpRequest; import org.silverpeas.core.web.mvc.controller.ComponentContext; import org.silverpeas.core.web.mvc.controller.MainSessionController; -import org.silverpeas.core.web.mvc.route.ComponentRequestRouter; +import org.silverpeas.core.web.mvc.route.AdminComponentRequestRouter; -public class VariablesRequestRouter extends ComponentRequestRouter<VariablesSessionController> { +public class VariablesRequestRouter + extends AdminComponentRequestRouter<VariablesSessionController> { + private static final long serialVersionUID = 880659914133424280L; private static final String SESSION_BEAN_NAME = "Variables"; @@ -41,13 +42,9 @@ public String getSessionControlBeanName() { } @Override - public String getDestination(final String action, final VariablesSessionController sc, + public String getAdminDestination(final String action, final VariablesSessionController sc, final HttpRequest request) { - if (!User.getCurrentRequester().isAccessAdmin()) { - throwHttpForbiddenError(); - } - String destination = "/variables/jsp/variables.jsp"; if ("Main".equals(action)) {
core-war/src/main/java/org/silverpeas/web/variables/VariablesSessionController.java+3 −2 modified@@ -24,7 +24,7 @@ package org.silverpeas.web.variables; import org.silverpeas.core.variables.Variable; -import org.silverpeas.core.web.mvc.controller.AbstractComponentSessionController; +import org.silverpeas.core.web.mvc.controller.AbstractAdminComponentSessionController; import org.silverpeas.core.web.mvc.controller.ComponentContext; import org.silverpeas.core.web.mvc.controller.MainSessionController; import org.silverpeas.core.webapi.variables.VariablesWebManager; @@ -34,7 +34,8 @@ import java.util.List; import java.util.Set; -public class VariablesSessionController extends AbstractComponentSessionController { +public class VariablesSessionController extends AbstractAdminComponentSessionController { + private static final long serialVersionUID = -6077929073402709139L; private final Set<String> selectedValueIds = new HashSet<>(); private Variable currentVariable;
core-war/src/main/java/org/silverpeas/web/workflowdesigner/control/WorkflowDesignerSessionController.java+2 −2 modified@@ -37,7 +37,7 @@ import org.silverpeas.core.util.StringUtil; import org.silverpeas.core.util.file.FileServerUtils; import org.silverpeas.core.util.file.FileUtil; -import org.silverpeas.core.web.mvc.controller.AbstractComponentSessionController; +import org.silverpeas.core.web.mvc.controller.AbstractAdminComponentSessionController; import org.silverpeas.core.web.mvc.controller.ComponentContext; import org.silverpeas.core.web.mvc.controller.MainSessionController; import org.silverpeas.core.workflow.api.ProcessModelManager; @@ -59,7 +59,7 @@ import static java.util.Optional.ofNullable; import static org.silverpeas.core.util.file.FileRepositoryManager.getTemporaryPath; -public class WorkflowDesignerSessionController extends AbstractComponentSessionController { +public class WorkflowDesignerSessionController extends AbstractAdminComponentSessionController { public static final String TYPE_USER = "user"; public static final String FORMS = "forms";
core-war/src/main/java/org/silverpeas/web/workflowdesigner/servlets/WorkflowDesignerRequestRouter.java+4 −9 modified@@ -25,12 +25,11 @@ import org.apache.commons.fileupload.FileItem; import org.silverpeas.core.exception.SilverpeasException; -import org.silverpeas.core.util.ResourceLocator; import org.silverpeas.core.util.StringUtil; import org.silverpeas.core.web.http.HttpRequest; import org.silverpeas.core.web.mvc.controller.ComponentContext; import org.silverpeas.core.web.mvc.controller.MainSessionController; -import org.silverpeas.core.web.mvc.route.ComponentRequestRouter; +import org.silverpeas.core.web.mvc.route.AdminComponentRequestRouter; import org.silverpeas.core.workflow.api.Workflow; import org.silverpeas.core.workflow.api.WorkflowException; import org.silverpeas.core.workflow.api.model.*; @@ -51,7 +50,7 @@ import java.util.StringTokenizer; public class WorkflowDesignerRequestRouter extends - ComponentRequestRouter<WorkflowDesignerSessionController> { + AdminComponentRequestRouter<WorkflowDesignerSessionController> { private static final long serialVersionUID = -6747786008527861783L; static private Map<String, FunctionHandler> mapHandler; // mapping of functions to their handlers @@ -90,16 +89,12 @@ public WorkflowDesignerSessionController createComponentSessionController( * @return The complete destination URL for a forward (ex : * "/almanach/jsp/almanach.jsp?flag=user") */ - public String getDestination(String function, + @Override + public String getAdminDestination(String function, WorkflowDesignerSessionController workflowDesignerSC, HttpRequest request) { String destination = null; FunctionHandler handler = getHandler(function); - // Check access rights - if (!workflowDesignerSC.getUserDetail().isAccessAdmin()) { - return ResourceLocator.getGeneralSettingBundle().getString("accessForbidden"); - } - try { if (handler != null) { destination = handler.getDestination(function, workflowDesignerSC, request);
core-war/src/main/webapp/admin/jsp/cipherkey.jsp+7 −1 modified@@ -1,4 +1,4 @@ -<%-- +<%@ page import="org.silverpeas.core.admin.user.model.User" %><%-- Copyright (C) 2000 - 2022 Silverpeas @@ -26,9 +26,15 @@ <%@page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%> +<%@ taglib uri="http://www.silverpeas.com/tld/silverFunctions" prefix="silfn" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> <%@ taglib uri="http://www.silverpeas.com/tld/viewGenerator" prefix="view"%> +<c:set var="currentUser" value="${silfn:currentUser()}"/> +<c:if test="${currentUser == null or not currentUser.accessAdmin}"> + <c:redirect url="/Login"/> +</c:if> + <fmt:setLocale value="${sessionScope['SilverSessionController'].favoriteLanguage}" /> <view:setBundle basename="org.silverpeas.crypto.multilang.cryptoBundle" />
core-war/src/main/webapp/admin/jsp/errorpageMain.jsp+17 −0 modified@@ -46,6 +46,10 @@ if (response.isCommitted() == false) { <%@ page import="org.silverpeas.core.util.WebEncodeHelper" %> <%@ page import="org.silverpeas.core.web.mvc.util.HomePageUtil" %> <%@ page import="java.io.PrintWriter" %> +<%@ page import="org.silverpeas.core.security.authorization.ForbiddenRuntimeException" %> +<%@ page import="javax.ws.rs.WebApplicationException" %> +<%@ page import="org.silverpeas.core.util.logging.SilverLogger" %> +<%@ page import="org.silverpeas.core.web.mvc.webcomponent.SilverpeasHttpServlet" %> <%@ include file="import.jsp" %> @@ -64,6 +68,19 @@ if (exception instanceof SilverpeasTrappedException) { extraInfos = StringUtil.defaultStringIfNotDefined(ste.getExtraInfos(), null); // Trace the exception HomePageUtil.traceException(exception); +} else if (exception instanceof ForbiddenRuntimeException) { + SilverLogger.getLogger(SilverpeasHttpServlet.class).error(exception.getMessage()); + response.sendError(HttpServletResponse.SC_FORBIDDEN, exception.getMessage()); + return; +} else if (exception instanceof WebApplicationException) { + final WebApplicationException wae = (WebApplicationException) exception; + if (wae.getResponse().getStatus() == HttpServletResponse.SC_FORBIDDEN) { + SilverLogger.getLogger(SilverpeasHttpServlet.class).error(wae.getMessage()); + } + if (!response.isCommitted()) { + response.sendError(wae.getResponse().getStatus(), exception.getMessage()); + } + return; } else { // Trace the exception HomePageUtil.traceException(toDisplayException);
core-war/src/main/webapp/admin/jsp/SilverLoggerAdmin.jsp+7 −5 modified@@ -58,17 +58,19 @@ <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> +<%@ taglib uri="http://www.silverpeas.com/tld/silverFunctions" prefix="silfn" %> <%@ taglib uri="http://www.silverpeas.com/tld/viewGenerator" prefix="view" %> + +<c:set var="currentUser" value="${silfn:currentUser()}"/> +<c:if test="${currentUser == null or not currentUser.accessAdmin}"> + <c:redirect url="/Login"/> +</c:if> + <% response.setHeader("Cache-Control", "no-store"); //HTTP 1.1 response.setHeader("Pragma", "no-cache"); //HTTP 1.0 response.setDateHeader("Expires", -1); //prevents caching at the proxy server - UserDetail currentUser = UserDetail.getCurrentRequester(); - if (currentUser == null || !currentUser.isAccessAdmin()) { - request.getRequestDispatcher("../../Login.jsp").forward(request, response); - } - List<String> loggingLevels = new ArrayList<>(); for (Level level : Level.values()) { loggingLevels.add(level.name());
core-war/src/main/webapp/applicationIndexer/jsp/applicationIndexer.jsp+6 −7 modified@@ -36,7 +36,6 @@ response.setDateHeader ("Expires",-1); //prevents caching at the proxy server %> <%@ page import="org.silverpeas.core.admin.space.SpaceInstLight"%> -<%@ page import="org.silverpeas.core.admin.user.constant.UserAccessLevel"%> <%@ page import="org.silverpeas.core.util.LocalizationBundle"%> <%@ page import="org.silverpeas.core.util.ResourceLocator"%> @@ -49,6 +48,7 @@ response.setDateHeader ("Expires",-1); //prevents caching at the proxy server <%@ page errorPage="../../admin/jsp/errorpage.jsp"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://www.silverpeas.com/tld/silverFunctions" prefix="silfn" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> <%@ taglib uri="http://www.silverpeas.com/tld/viewGenerator" prefix="view"%> <%@ taglib prefix="fmy" uri="http://java.sun.com/jsp/jstl/fmt" %> @@ -92,14 +92,13 @@ response.setDateHeader ("Expires",-1); //prevents caching at the proxy server } %> +<c:set var="currentUser" value="${silfn:currentUser()}"/> +<c:if test="${currentUser == null or not currentUser.accessAdmin}"> + <c:redirect url="/Login"/> +</c:if> + <% final MainSessionController mainSessionCtrl = (MainSessionController) session.getAttribute(MainSessionController.MAIN_SESSION_CONTROLLER_ATT); -if (mainSessionCtrl == null || !UserAccessLevel.ADMINISTRATOR.equals(mainSessionCtrl.getUserAccessLevel())) { - // No session controller in the request -> security exception - String sessionTimeout = ResourceLocator.getGeneralSettingBundle().getString("sessionTimeout"); - getServletConfig().getServletContext().getRequestDispatcher(sessionTimeout).forward(request, response); - return; -} %> <c:set var="_userLanguage" value="<%=mainSessionCtrl.getFavoriteLanguage()%>" scope="request"/>
core-war/src/main/webapp/silverpeasinfos.jsp+2 −2 modified@@ -55,8 +55,8 @@ <%@ taglib uri="http://www.silverpeas.com/tld/viewGenerator" prefix="view" %> <c:set var="currentUser" value="${silfn:currentUser()}"/> -<c:if test="${currentUser == null or !currentUser.accessAdmin}"> - <c:redirect url="/Login.jsp"/> +<c:if test="${currentUser == null or not currentUser.accessAdmin}"> + <c:redirect url="/Login"/> </c:if> <html>
core-war/src/test/java/org/silverpeas/web/jobdomain/control/JobDomainPeasSessionControllerAccessGrantedTest.java+508 −0 added@@ -0,0 +1,508 @@ +/* + * Copyright (C) 2000 - 2023 Silverpeas + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * As a special exception to the terms and conditions of version 3.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * Open Source Software ("FLOSS") applications as described in Silverpeas's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * "https://www.silverpeas.org/legal/floss_exception.html" + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +package org.silverpeas.web.jobdomain.control; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; +import org.mockito.stubbing.Answer; +import org.silverpeas.core.admin.domain.model.Domain; +import org.silverpeas.core.admin.service.OrganizationController; +import org.silverpeas.core.admin.user.model.Group; +import org.silverpeas.core.admin.user.model.UserDetail; +import org.silverpeas.core.admin.user.service.UserProvider; +import org.silverpeas.core.security.authorization.ForbiddenRuntimeException; +import org.silverpeas.core.test.extention.EnableSilverTestEnv; +import org.silverpeas.core.test.extention.TestManagedMock; +import org.silverpeas.core.test.extention.TestedBean; +import org.silverpeas.core.util.StringUtil; +import org.silverpeas.core.web.mvc.controller.ComponentContext; + +import java.util.List; + +import static java.util.stream.Collectors.toList; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.silverpeas.core.admin.domain.model.Domain.MIXED_DOMAIN_ID; +import static org.silverpeas.core.admin.user.constant.UserAccessLevel.USER; + +/** + * @author silveryocha + */ +@EnableSilverTestEnv +class JobDomainPeasSessionControllerAccessGrantedTest { + + private static final String LOGGED_USER_DOMAIN_ID = "26"; + private static final String OTHER_DOMAIN_ID = "38"; + private static final String USER_ID_ON_DOMAIN_OF_LOGGED_USER_ID = "7"; + private static final String USER_ID_ON_OTHER_DOMAIN = "8"; + private static final String USER_ID_ON_MIXED_DOMAIN_EVEN_IF_NOT_POSSIBLE = "9"; + private static final String GROUP_ID_ON_DOMAIN_OF_LOGGED_USER_ID = LOGGED_USER_DOMAIN_ID; + private static final String GROUP_ID_ON_OTHER_DOMAIN = OTHER_DOMAIN_ID; + private static final String GROUP_ID_ON_MIXED_DOMAIN = USER_ID_ON_DOMAIN_OF_LOGGED_USER_ID; + + @TestManagedMock + private Domain rightDomain; + + @TestManagedMock + private Domain wrongDomain; + + @TestedBean + private JobDomainPeasSessionController4Test controller; + + @BeforeEach + void setup(@TestManagedMock OrganizationController orgaController, + @TestManagedMock UserProvider userProvider) { + when(rightDomain.getId()).thenReturn(LOGGED_USER_DOMAIN_ID); + when(wrongDomain.getId()).thenReturn(OTHER_DOMAIN_ID); + controller.setupDefaultLoggedUser(); + final Answer<Object> getUserAnswer = i -> { + final String id = i.getArgument(0); + final UserDetail user = mock(UserDetail.class); + when(user.getId()).thenReturn(id); + when(user.getDomainId()).thenAnswer(a -> { + switch (id) { + case USER_ID_ON_OTHER_DOMAIN: + return OTHER_DOMAIN_ID; + case USER_ID_ON_MIXED_DOMAIN_EVEN_IF_NOT_POSSIBLE: + return MIXED_DOMAIN_ID; + default: + return LOGGED_USER_DOMAIN_ID; + } + }); + return user; + }; + when(userProvider.getUser(anyString())).thenAnswer(getUserAnswer); + when(orgaController.getUserDetail(anyString())).thenAnswer(getUserAnswer); + when(orgaController.getGroup(anyString())).thenAnswer(i -> { + final String id = i.getArgument(0); + final int index = id.indexOf("_"); + final String topLevelGroupId = index > 0 ? id.substring(0, index) : id; + final Group group = mock(Group.class); + when(group.getId()).thenReturn(id); + when(group.getDomainId()).thenAnswer(a -> { + switch (topLevelGroupId) { + case GROUP_ID_ON_OTHER_DOMAIN: + return OTHER_DOMAIN_ID; + case GROUP_ID_ON_MIXED_DOMAIN: + return MIXED_DOMAIN_ID; + default: + return LOGGED_USER_DOMAIN_ID; + } + }); + if (index > 0) { + when(group.getSuperGroupId()).thenReturn(topLevelGroupId); + } + return group; + }); + when(orgaController.getPathToGroup(anyString())).thenAnswer(i -> { + final String id = i.getArgument(0); + final int index = id.indexOf("_"); + if (index > 0) { + return List.of(id.substring(0, index)); + } else { + return List.of(); + } + }); + } + + /** + * DOMAIN + */ + + @DisplayName("User with admin access level has always granted access to a domain") + @Test + void adminDomainAccessGranted() { + when(controller.getUserDetail().isAccessAdmin()).thenReturn(true); + controller.checkDomainAccessGranted(MIXED_DOMAIN_ID); + controller.checkDomainAccessGranted(MIXED_DOMAIN_ID, false); + controller.checkDomainAccessGranted(LOGGED_USER_DOMAIN_ID); + controller.checkDomainAccessGranted(LOGGED_USER_DOMAIN_ID, false); + controller.checkDomainAccessGranted(OTHER_DOMAIN_ID); + controller.checkDomainAccessGranted(OTHER_DOMAIN_ID, false); + } + + @DisplayName("User with user access level has never granted access to a domain") + @Test + void simpleUserDomainAccessNotGranted() { + assertForbidden(() -> controller.checkDomainAccessGranted(MIXED_DOMAIN_ID)); + assertForbidden(() -> controller.checkDomainAccessGranted(MIXED_DOMAIN_ID, false)); + assertForbidden(() -> controller.checkDomainAccessGranted(LOGGED_USER_DOMAIN_ID)); + assertForbidden(() -> controller.checkDomainAccessGranted(LOGGED_USER_DOMAIN_ID, false)); + assertForbidden(() -> controller.checkDomainAccessGranted(OTHER_DOMAIN_ID)); + assertForbidden(() -> controller.checkDomainAccessGranted(OTHER_DOMAIN_ID, false)); + } + + @DisplayName("User with domain access level, without managed group, has granted access if " + + "accessed domain corresponds to its domain") + @Test + void domainAdminUserDomainAccess() { + when(controller.getUserDetail().isAccessDomainManager()).thenReturn(true); + assertDomainAdminUserDomainAccess(); + when(controller.getUserDetail().isDomainAdminRestricted()).thenReturn(true); + assertDomainAdminUserDomainAccess(); + } + + private void assertDomainAdminUserDomainAccess() { + assertForbidden(() -> controller.checkDomainAccessGranted(MIXED_DOMAIN_ID)); + assertForbidden(() -> controller.checkDomainAccessGranted(MIXED_DOMAIN_ID, false)); + controller.checkDomainAccessGranted(LOGGED_USER_DOMAIN_ID); + controller.checkDomainAccessGranted(LOGGED_USER_DOMAIN_ID, false); + assertForbidden(() -> controller.checkDomainAccessGranted(OTHER_DOMAIN_ID)); + assertForbidden(() -> controller.checkDomainAccessGranted(OTHER_DOMAIN_ID, false)); + } + + @DisplayName("User with domain access level, and with managed groups, has granted access if " + + "accessed domain does not correspond to its domain") + @Test + void domainAdminUserDomainAccessWithManageableGroups() { + when(controller.getUserDetail().isAccessDomainManager()).thenReturn(true); + when(controller.getUserDetail().getDomainId()).thenReturn(OTHER_DOMAIN_ID); + assertDomainAdminUserDomainAccessWithManageableGroups(false); + when(controller.getUserDetail().isDomainAdminRestricted()).thenReturn(true); + assertDomainAdminUserDomainAccessWithManageableGroups(true); + } + + private void assertDomainAdminUserDomainAccessWithManageableGroups(boolean domainRestricted) { + controller.setManageableGroupIds(); + assertForbidden(() -> controller.checkDomainAccessGranted(MIXED_DOMAIN_ID)); + assertForbidden(() -> controller.checkDomainAccessGranted(MIXED_DOMAIN_ID, false)); + assertForbidden(() -> controller.checkDomainAccessGranted(LOGGED_USER_DOMAIN_ID)); + assertForbidden(() -> controller.checkDomainAccessGranted(LOGGED_USER_DOMAIN_ID, false)); + controller.checkDomainAccessGranted(OTHER_DOMAIN_ID); + controller.checkDomainAccessGranted(OTHER_DOMAIN_ID, false); + // setting now the managed groups on mixed domain + controller.setManageableGroupIds(MIXED_DOMAIN_ID); + controller.checkDomainAccessGranted(MIXED_DOMAIN_ID); + assertForbidden(() -> controller.checkDomainAccessGranted(MIXED_DOMAIN_ID, false)); + assertForbidden(() -> controller.checkDomainAccessGranted(LOGGED_USER_DOMAIN_ID)); + assertForbidden(() -> controller.checkDomainAccessGranted(LOGGED_USER_DOMAIN_ID, false)); + controller.checkDomainAccessGranted(OTHER_DOMAIN_ID); + controller.checkDomainAccessGranted(OTHER_DOMAIN_ID, false); + // setting now the managed groups on same domain of LOGGED user domain + controller.setManageableGroupIds(LOGGED_USER_DOMAIN_ID); + assertForbidden(() -> controller.checkDomainAccessGranted(MIXED_DOMAIN_ID)); + assertForbidden(() -> controller.checkDomainAccessGranted(MIXED_DOMAIN_ID, false)); + if (domainRestricted) { + assertForbidden(() -> controller.checkDomainAccessGranted(LOGGED_USER_DOMAIN_ID)); + } else { + controller.checkDomainAccessGranted(LOGGED_USER_DOMAIN_ID); + } + assertForbidden(() -> controller.checkDomainAccessGranted(LOGGED_USER_DOMAIN_ID, false)); + controller.checkDomainAccessGranted(OTHER_DOMAIN_ID); + controller.checkDomainAccessGranted(OTHER_DOMAIN_ID, false); + // setting now the managed groups on other domain than the one of LOGGED user domain + controller.setManageableGroupIds(OTHER_DOMAIN_ID); + assertForbidden(() -> controller.checkDomainAccessGranted(MIXED_DOMAIN_ID)); + assertForbidden(() -> controller.checkDomainAccessGranted(MIXED_DOMAIN_ID, false)); + assertForbidden(() -> controller.checkDomainAccessGranted(LOGGED_USER_DOMAIN_ID)); + assertForbidden(() -> controller.checkDomainAccessGranted(LOGGED_USER_DOMAIN_ID, false)); + controller.checkDomainAccessGranted(OTHER_DOMAIN_ID); + controller.checkDomainAccessGranted(OTHER_DOMAIN_ID, false); + } + + @DisplayName("User with user access level, and with managed groups, has granted access if " + + "accessed domain does not correspond to its domain") + @Test + void simpleUserDomainAccessWithManageableGroups() { + when(controller.getUserDetail().getDomainId()).thenReturn(OTHER_DOMAIN_ID); + assertSimpleUserDomainAccessWithManageableGroups(false); + when(controller.getUserDetail().isDomainAdminRestricted()).thenReturn(true); + assertSimpleUserDomainAccessWithManageableGroups(true); + } + + private void assertSimpleUserDomainAccessWithManageableGroups(boolean domainRestricted) { + controller.setManageableGroupIds(); + assertForbidden(() -> controller.checkDomainAccessGranted(MIXED_DOMAIN_ID)); + assertForbidden(() -> controller.checkDomainAccessGranted(MIXED_DOMAIN_ID, false)); + assertForbidden(() -> controller.checkDomainAccessGranted(LOGGED_USER_DOMAIN_ID)); + assertForbidden(() -> controller.checkDomainAccessGranted(LOGGED_USER_DOMAIN_ID, false)); + assertForbidden(() -> controller.checkDomainAccessGranted(OTHER_DOMAIN_ID)); + assertForbidden(() -> controller.checkDomainAccessGranted(OTHER_DOMAIN_ID, false)); + // setting now the managed groups on mixed domain + controller.setManageableGroupIds(MIXED_DOMAIN_ID); + controller.checkDomainAccessGranted(MIXED_DOMAIN_ID); + assertForbidden(() -> controller.checkDomainAccessGranted(MIXED_DOMAIN_ID, false)); + assertForbidden(() -> controller.checkDomainAccessGranted(LOGGED_USER_DOMAIN_ID)); + assertForbidden(() -> controller.checkDomainAccessGranted(LOGGED_USER_DOMAIN_ID, false)); + assertForbidden(() -> controller.checkDomainAccessGranted(OTHER_DOMAIN_ID)); + assertForbidden(() -> controller.checkDomainAccessGranted(OTHER_DOMAIN_ID, false)); + // setting now the managed groups on same domain of LOGGED user domain + controller.setManageableGroupIds(LOGGED_USER_DOMAIN_ID); + assertForbidden(() -> controller.checkDomainAccessGranted(MIXED_DOMAIN_ID)); + assertForbidden(() -> controller.checkDomainAccessGranted(MIXED_DOMAIN_ID, false)); + if (domainRestricted) { + assertForbidden(() -> controller.checkDomainAccessGranted(LOGGED_USER_DOMAIN_ID)); + } else { + controller.checkDomainAccessGranted(LOGGED_USER_DOMAIN_ID); + } + assertForbidden(() -> controller.checkDomainAccessGranted(LOGGED_USER_DOMAIN_ID, false)); + assertForbidden(() -> controller.checkDomainAccessGranted(OTHER_DOMAIN_ID)); + assertForbidden(() -> controller.checkDomainAccessGranted(OTHER_DOMAIN_ID, false)); + // setting now the managed groups on other domain than the one of LOGGED user domain + controller.setManageableGroupIds(OTHER_DOMAIN_ID); + assertForbidden(() -> controller.checkDomainAccessGranted(MIXED_DOMAIN_ID)); + assertForbidden(() -> controller.checkDomainAccessGranted(MIXED_DOMAIN_ID, false)); + assertForbidden(() -> controller.checkDomainAccessGranted(LOGGED_USER_DOMAIN_ID)); + assertForbidden(() -> controller.checkDomainAccessGranted(LOGGED_USER_DOMAIN_ID, false)); + controller.checkDomainAccessGranted(OTHER_DOMAIN_ID); + assertForbidden(() -> controller.checkDomainAccessGranted(OTHER_DOMAIN_ID, false)); + } + + @DisplayName("User with user access level, without managed groups and with space management " + + "right, has granted access in read only mode") + @Test + void simpleUserDomainAccessWhenSpaceManager() { + controller.setManageableSpaceIds(); + assertForbidden(() -> controller.checkDomainAccessGranted(MIXED_DOMAIN_ID)); + assertForbidden(() -> controller.checkDomainAccessGranted(MIXED_DOMAIN_ID, false)); + assertForbidden(() -> controller.checkDomainAccessGranted(LOGGED_USER_DOMAIN_ID)); + assertForbidden(() -> controller.checkDomainAccessGranted(LOGGED_USER_DOMAIN_ID, false)); + assertForbidden(() -> controller.checkDomainAccessGranted(OTHER_DOMAIN_ID)); + assertForbidden(() -> controller.checkDomainAccessGranted(OTHER_DOMAIN_ID, false)); + controller.setManageableSpaceIds("3"); + controller.checkDomainAccessGranted(MIXED_DOMAIN_ID); + assertForbidden(() -> controller.checkDomainAccessGranted(MIXED_DOMAIN_ID, false)); + controller.checkDomainAccessGranted(LOGGED_USER_DOMAIN_ID); + assertForbidden(() -> controller.checkDomainAccessGranted(LOGGED_USER_DOMAIN_ID, false)); + assertForbidden(() -> controller.checkDomainAccessGranted(OTHER_DOMAIN_ID)); + assertForbidden(() -> controller.checkDomainAccessGranted(OTHER_DOMAIN_ID, false)); + } + + @DisplayName("User with user access level, without managed groups and community manager " + + "right, has granted access in read only mode") + @Test + void simpleUserDomainAccessWhenCommunityManager() { + controller.setCommunityManager(true); + controller.checkDomainAccessGranted(MIXED_DOMAIN_ID); + assertForbidden(() -> controller.checkDomainAccessGranted(MIXED_DOMAIN_ID, false)); + controller.checkDomainAccessGranted(LOGGED_USER_DOMAIN_ID); + assertForbidden(() -> controller.checkDomainAccessGranted(LOGGED_USER_DOMAIN_ID, false)); + assertForbidden(() -> controller.checkDomainAccessGranted(OTHER_DOMAIN_ID)); + assertForbidden(() -> controller.checkDomainAccessGranted(OTHER_DOMAIN_ID, false)); + } + + /** + * USER + */ + + @DisplayName("When admin modifying a user, domain access in write mode is verified and " + + "target domain MUST be set to the right domain") + @Test + void adminUserAccessGranted() { + controller.setManageableGroupIds(GROUP_ID_ON_MIXED_DOMAIN, GROUP_ID_ON_DOMAIN_OF_LOGGED_USER_ID, + GROUP_ID_ON_OTHER_DOMAIN); + assertAdminUserAccessGranted(); + controller.setManageableSpaceIds("3"); + assertAdminUserAccessGranted(); + } + + private void assertAdminUserAccessGranted() { + controller.setTargetDomain(null); + assertForbidden(() -> controller.checkUserAccessGranted(USER_ID_ON_MIXED_DOMAIN_EVEN_IF_NOT_POSSIBLE, true)); + assertForbidden(() -> controller.checkUserAccessGranted(USER_ID_ON_MIXED_DOMAIN_EVEN_IF_NOT_POSSIBLE, false)); + assertForbidden(() -> controller.checkUserAccessGranted(USER_ID_ON_DOMAIN_OF_LOGGED_USER_ID, true)); + assertForbidden(() -> controller.checkUserAccessGranted(USER_ID_ON_DOMAIN_OF_LOGGED_USER_ID, false)); + assertForbidden(() -> controller.checkUserAccessGranted(USER_ID_ON_OTHER_DOMAIN, true)); + assertForbidden(() -> controller.checkUserAccessGranted(USER_ID_ON_OTHER_DOMAIN, false)); + // target domain is the mixed one + controller.setTargetDomain(MIXED_DOMAIN_ID); + assertForbidden(() -> controller.checkUserAccessGranted(USER_ID_ON_MIXED_DOMAIN_EVEN_IF_NOT_POSSIBLE, true)); + assertForbidden(() -> controller.checkUserAccessGranted(USER_ID_ON_MIXED_DOMAIN_EVEN_IF_NOT_POSSIBLE, false)); + assertForbidden(() -> controller.checkUserAccessGranted(USER_ID_ON_DOMAIN_OF_LOGGED_USER_ID, true)); + assertForbidden(() -> controller.checkUserAccessGranted(USER_ID_ON_DOMAIN_OF_LOGGED_USER_ID, false)); + assertForbidden(() -> controller.checkUserAccessGranted(USER_ID_ON_OTHER_DOMAIN, true)); + assertForbidden(() -> controller.checkUserAccessGranted(USER_ID_ON_OTHER_DOMAIN, false)); + // target domain is the one of the admin + controller.setTargetDomain(LOGGED_USER_DOMAIN_ID); + assertForbidden(() -> controller.checkUserAccessGranted(USER_ID_ON_MIXED_DOMAIN_EVEN_IF_NOT_POSSIBLE, true)); + assertForbidden(() -> controller.checkUserAccessGranted(USER_ID_ON_MIXED_DOMAIN_EVEN_IF_NOT_POSSIBLE, false)); + controller.checkUserAccessGranted(USER_ID_ON_DOMAIN_OF_LOGGED_USER_ID, true); + assertForbidden(() -> controller.checkUserAccessGranted(USER_ID_ON_DOMAIN_OF_LOGGED_USER_ID, false)); + assertForbidden(() -> controller.checkUserAccessGranted(USER_ID_ON_OTHER_DOMAIN, true)); + assertForbidden(() -> controller.checkUserAccessGranted(USER_ID_ON_OTHER_DOMAIN, false)); + // target domain is not the one of the admin + controller.setTargetDomain(OTHER_DOMAIN_ID); + assertForbidden(() -> controller.checkUserAccessGranted(USER_ID_ON_MIXED_DOMAIN_EVEN_IF_NOT_POSSIBLE, true)); + assertForbidden(() -> controller.checkUserAccessGranted(USER_ID_ON_MIXED_DOMAIN_EVEN_IF_NOT_POSSIBLE, false)); + assertForbidden(() -> controller.checkUserAccessGranted(USER_ID_ON_DOMAIN_OF_LOGGED_USER_ID, true)); + assertForbidden(() -> controller.checkUserAccessGranted(USER_ID_ON_DOMAIN_OF_LOGGED_USER_ID, false)); + controller.checkUserAccessGranted(USER_ID_ON_OTHER_DOMAIN, true); + assertForbidden(() -> controller.checkUserAccessGranted(USER_ID_ON_OTHER_DOMAIN, false)); + } + + /** + * GROUP + */ + + @DisplayName("When admin modifying a group, domain access in write mode is verified and " + + "target domain MUST be set to the right domain") + @Test + void adminGroupAccessGranted() { + controller.setManageableGroupIds(GROUP_ID_ON_MIXED_DOMAIN, GROUP_ID_ON_DOMAIN_OF_LOGGED_USER_ID, + GROUP_ID_ON_OTHER_DOMAIN); + assertAdminGroupAccessGranted(); + controller.setManageableSpaceIds("4"); + assertAdminUserAccessGranted(); + } + + private void assertAdminGroupAccessGranted() { + assertForbidden(() -> controller.checkGroupAccessGranted(GROUP_ID_ON_MIXED_DOMAIN, true)); + assertForbidden(() -> controller.checkGroupAccessGranted(GROUP_ID_ON_MIXED_DOMAIN, false)); + assertForbidden(() -> controller.checkGroupAccessGranted(GROUP_ID_ON_DOMAIN_OF_LOGGED_USER_ID, true)); + assertForbidden(() -> controller.checkGroupAccessGranted(GROUP_ID_ON_DOMAIN_OF_LOGGED_USER_ID, false)); + assertForbidden(() -> controller.checkGroupAccessGranted(GROUP_ID_ON_OTHER_DOMAIN, true)); + assertForbidden(() -> controller.checkGroupAccessGranted(GROUP_ID_ON_OTHER_DOMAIN, false)); + // target domain is the mixed one + controller.setTargetDomain(MIXED_DOMAIN_ID); + controller.checkGroupAccessGranted(GROUP_ID_ON_MIXED_DOMAIN, true); + controller.checkGroupAccessGranted(GROUP_ID_ON_MIXED_DOMAIN, false); + assertForbidden(() -> controller.checkGroupAccessGranted(GROUP_ID_ON_DOMAIN_OF_LOGGED_USER_ID, true)); + assertForbidden(() -> controller.checkGroupAccessGranted(GROUP_ID_ON_DOMAIN_OF_LOGGED_USER_ID, false)); + assertForbidden(() -> controller.checkGroupAccessGranted(GROUP_ID_ON_OTHER_DOMAIN, true)); + assertForbidden(() -> controller.checkGroupAccessGranted(GROUP_ID_ON_OTHER_DOMAIN, false)); + assertForbidden(() -> controller.checkGroupAccessGranted(GROUP_ID_ON_OTHER_DOMAIN + "_sub", true)); + assertForbidden(() -> controller.checkGroupAccessGranted(GROUP_ID_ON_OTHER_DOMAIN + "_sub", false)); + // target domain is the one of the admin + controller.setTargetDomain(LOGGED_USER_DOMAIN_ID); + assertForbidden(() -> controller.checkGroupAccessGranted(GROUP_ID_ON_MIXED_DOMAIN, true)); + assertForbidden(() -> controller.checkGroupAccessGranted(GROUP_ID_ON_MIXED_DOMAIN, false)); + controller.checkGroupAccessGranted(GROUP_ID_ON_DOMAIN_OF_LOGGED_USER_ID, true); + controller.checkGroupAccessGranted(GROUP_ID_ON_DOMAIN_OF_LOGGED_USER_ID, false); + assertForbidden(() -> controller.checkGroupAccessGranted(GROUP_ID_ON_OTHER_DOMAIN, true)); + assertForbidden(() -> controller.checkGroupAccessGranted(GROUP_ID_ON_OTHER_DOMAIN, false)); + assertForbidden(() -> controller.checkGroupAccessGranted(GROUP_ID_ON_OTHER_DOMAIN + "_sub", true)); + assertForbidden(() -> controller.checkGroupAccessGranted(GROUP_ID_ON_OTHER_DOMAIN + "_sub", false)); + // target domain is not the one of the admin + controller.setTargetDomain(OTHER_DOMAIN_ID); + assertForbidden(() -> controller.checkGroupAccessGranted(GROUP_ID_ON_MIXED_DOMAIN, true)); + assertForbidden(() -> controller.checkGroupAccessGranted(GROUP_ID_ON_MIXED_DOMAIN, false)); + assertForbidden(() -> controller.checkGroupAccessGranted(GROUP_ID_ON_DOMAIN_OF_LOGGED_USER_ID, true)); + assertForbidden(() -> controller.checkGroupAccessGranted(GROUP_ID_ON_DOMAIN_OF_LOGGED_USER_ID, false)); + controller.checkGroupAccessGranted(GROUP_ID_ON_OTHER_DOMAIN, true); + controller.checkGroupAccessGranted(GROUP_ID_ON_OTHER_DOMAIN, false); + controller.checkGroupAccessGranted(GROUP_ID_ON_OTHER_DOMAIN + "_sub", true); + controller.checkGroupAccessGranted(GROUP_ID_ON_OTHER_DOMAIN + "_sub", false); + } + + private void assertForbidden(final Executable executable) { + assertThrows(ForbiddenRuntimeException.class, executable); + } + + private static class JobDomainPeasSessionController4Test extends JobDomainPeasSessionController { + private static final long serialVersionUID = -2464677549792058075L; + + private final UserDetail loggedUser = mock(UserDetail.class); + private boolean communityManager = false; + private String[] manageableSpaceIds = new String[]{}; + private List<String> manageableGroupIds = List.of(); + private Domain targetDomain = null; + + public JobDomainPeasSessionController4Test() { + super(null, mockedContext(), null, null, null); + } + + private static ComponentContext mockedContext() { + final ComponentContext mock = mock(ComponentContext.class); + when(mock.getCurrentComponentName()).thenReturn("unknown"); + return mock; + } + + private void setupDefaultLoggedUser() { + when(loggedUser.getId()).thenReturn("2"); + when(loggedUser.getDomainId()).thenReturn(LOGGED_USER_DOMAIN_ID); + when(loggedUser.getAccessLevel()).thenReturn(USER); + } + + public void setCommunityManager(final boolean communityManager) { + this.communityManager = communityManager; + } + + public void setManageableSpaceIds(String... spaceIds) { + manageableSpaceIds = spaceIds; + } + + /** + * Sets the manageable group identifiers. + * <p> + * IMPORTANT: the given identifier is used to set domain id too. So giving the identifier + * of domain. + * </p> + * @param groupIds several identifiers. + */ + public void setManageableGroupIds(String... groupIds) { + manageableGroupIds = List.of(groupIds); + } + + @Override + public void setTargetDomain(final String targetDomainId) { + if (StringUtil.isDefined(targetDomainId)) { + this.targetDomain = mock(Domain.class); + when(this.targetDomain.getId()).thenReturn(targetDomainId); + } else { + this.targetDomain = null; + } + } + + @Override + public Domain getTargetDomain() { + return targetDomain; + } + + @Override + public boolean isCommunityManager() { + return communityManager; + } + + @Override + public UserDetail getUserDetail() { + return loggedUser; + } + + @Override + protected String[] getUserManageableSpaceIds() { + return manageableSpaceIds; + } + + @Override + protected List<String> getUserManageableGroupIds() { + return manageableGroupIds; + } + + @Override + public List<Group> getUserManageableGroups() { + return manageableGroupIds.stream().map(i -> { + Group group = mock(Group.class); + when(group.getId()).thenReturn(i); + when(group.getDomainId()).thenReturn( + GROUP_ID_ON_MIXED_DOMAIN.equals(i) ? null : i); + return group; + }).collect(toList()); + } + } +} \ No newline at end of file
core-war/src/test/java/org/silverpeas/web/jobstartpage/control/JobStartPagePeasSessionControllerTest.java+301 −0 added@@ -0,0 +1,301 @@ +/* + * Copyright (C) 2000 - 2023 Silverpeas + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * As a special exception to the terms and conditions of version 3.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * Open Source Software ("FLOSS") applications as described in Silverpeas's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * "https://www.silverpeas.org/legal/floss_exception.html" + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +package org.silverpeas.web.jobstartpage.control; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; +import org.silverpeas.core.admin.component.model.SilverpeasComponentInstance; +import org.silverpeas.core.admin.service.OrganizationController; +import org.silverpeas.core.admin.space.SpaceInst; +import org.silverpeas.core.admin.space.SpaceInstLight; +import org.silverpeas.core.admin.user.model.UserDetail; +import org.silverpeas.core.security.authorization.ForbiddenRuntimeException; +import org.silverpeas.core.test.extention.EnableSilverTestEnv; +import org.silverpeas.core.test.extention.TestManagedMock; +import org.silverpeas.core.test.extention.TestedBean; +import org.silverpeas.core.web.mvc.controller.ComponentContext; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.lang.Integer.parseInt; +import static java.util.Optional.of; +import static java.util.Optional.ofNullable; +import static java.util.function.Predicate.not; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.silverpeas.core.admin.space.SpaceInst.SPACE_KEY_PREFIX; +import static org.silverpeas.core.admin.user.constant.UserAccessLevel.USER; + +/** + * @author silveryocha + */ +@EnableSilverTestEnv +class JobStartPagePeasSessionControllerTest { + + private static final String LOGGED_USER_DOMAIN_ID = "26"; + + private static final String SPACE_A = "WA1"; + private static final String SPACE_A_A1 = "WA11"; + private static final String SPACE_A_A1_A11 = "WA111"; + private static final String SPACE_A_A2 = "WA12"; + private static final String SPACE_B = "WA2"; + private static final List<String> ALL_SPACE_IDS = List.of( + SPACE_A, SPACE_A_A1, SPACE_A_A1_A11, SPACE_A_A2, + SPACE_B); + private static final Map<String, String> SPACE_SPACE = Map.of( + SPACE_A_A1, SPACE_A, + SPACE_A_A1_A11, SPACE_A_A1, + SPACE_A_A2, SPACE_A); + + private static final String INSTANCE_AA = "kmelia1"; + private static final String INSTANCE_A_A1A = "kmelia11"; + private static final String INSTANCE_A_A1B = "kmelia12"; + private static final String INSTANCE_A_A1_A11A = "kmelia111"; + private static final String INSTANCE_BA = "kmelia2"; + private static final List<String> ALL_INSTANCE_IDS = List.of( + INSTANCE_AA, INSTANCE_A_A1A, INSTANCE_A_A1B, INSTANCE_A_A1_A11A, + INSTANCE_BA); + private static final Map<String, String> INSTANCE_SPACE = Map.of( + INSTANCE_AA, SPACE_A, + INSTANCE_A_A1A, SPACE_A_A1, + INSTANCE_A_A1B, SPACE_A_A1, + INSTANCE_A_A1_A11A, SPACE_A_A1_A11, + INSTANCE_BA, SPACE_B); + + @TestedBean + private JobStartPagePeasSessionController4Test controller; + + @BeforeEach + void setup(@TestManagedMock OrganizationController orgaController) { + when(orgaController.getSpaceInstById(anyString())).thenAnswer(i -> { + final String id = i.getArgument(0); + final SpaceInst space = mock(SpaceInst.class); + when(space.getId()).thenReturn(id); + when(space.getLocalId()).thenReturn(parseInt(id.substring(SPACE_KEY_PREFIX.length()))); + ofNullable(SPACE_SPACE.get(id)).ifPresentOrElse( + p -> when(space.getDomainFatherId()).thenReturn(p), + () -> when(space.isRoot()).thenReturn(true)); + return space; + }); + when(orgaController.getSpaceInstLightById(anyString())).thenAnswer(i -> { + final String id = i.getArgument(0); + final SpaceInstLight space = mock(SpaceInstLight.class); + when(space.getId()).thenReturn(id); + when(space.getLocalId()).thenReturn(parseInt(id.substring(SPACE_KEY_PREFIX.length()))); + ofNullable(SPACE_SPACE.get(id)).ifPresentOrElse( + p -> when(space.getFatherId()).thenReturn(p), + () -> when(space.isRoot()).thenReturn(true)); + return space; + }); + when(orgaController.getComponentInstance(anyString())).thenAnswer(i -> { + final String id = i.getArgument(0); + final SilverpeasComponentInstance instance = mock(SilverpeasComponentInstance.class); + when(instance.getId()).thenReturn(id); + ofNullable(INSTANCE_SPACE.get(id)).ifPresent(p -> when(instance.getSpaceId()).thenReturn(p)); + return of(instance); + }); + when(orgaController.getPathToSpace(anyString())).thenAnswer(i -> { + final String id = i.getArgument(0, String.class); + List<SpaceInstLight> path = new ArrayList<>(); + SpaceInstLight spaceInst = orgaController.getSpaceInstLightById(id); + if (spaceInst != null) { + if (!spaceInst.isRoot()) { + path.addAll(orgaController.getPathToSpace(spaceInst.getFatherId())); + } + path.add(0, spaceInst); + } + return path; + }); + } + + @DisplayName("User with admin access level has always granted access to spaces and component " + + "instances, even if space and instance ids does not exist") + @Test + void adminOfSpaceAndInstanceAccessGranted() { + when(controller.getUserDetail().isAccessAdmin()).thenReturn(true); + Stream.concat(Stream.of((String) null), ALL_SPACE_IDS.stream()) + .forEach(s -> Stream.concat(Stream.of((String) null), ALL_INSTANCE_IDS.stream()) + .forEach(i -> { + controller.checkAccessGranted(s, i, true); + controller.checkAccessGranted(s, i, false); + })); + } + + @DisplayName( + "User with user access level has never granted access to spaces and component instances") + @Test + void simpleUserOfSpaceAndInstanceAccessNotGranted() { + Stream.concat(Stream.of((String) null), ALL_SPACE_IDS.stream()) + .forEach(s -> Stream.concat(Stream.of((String) null), ALL_INSTANCE_IDS.stream()) + .forEach(i -> { + assertForbidden(() -> controller.checkAccessGranted(s, i, true)); + assertForbidden(() -> controller.checkAccessGranted(s, i, false)); + })); + } + + @DisplayName("Manager has granted access to aimed spaces") + @Test + void managerOfSpaceAccessGranted() { + // ON SPACE A + controller.setManageableSpaceIds(SPACE_A); + controller.checkAccessGranted(null, null, true); + assertForbidden(() -> controller.checkAccessGranted(null, null, false)); + controller.checkAccessGranted(SPACE_A, null, true); + controller.checkAccessGranted(SPACE_A, null, false); + controller.checkAccessGranted(SPACE_A_A1, null, true); + controller.checkAccessGranted(SPACE_A_A1, null, false); + controller.checkAccessGranted(SPACE_A_A1_A11, null, true); + controller.checkAccessGranted(SPACE_A_A1_A11, null, false); + controller.checkAccessGranted(SPACE_A_A2, null, true); + controller.checkAccessGranted(SPACE_A_A2, null, false); + assertForbidden(() -> controller.checkAccessGranted(SPACE_B, null, true)); + assertForbidden(() -> controller.checkAccessGranted(SPACE_B, null, false)); + // ON SPACE A_A1 + controller.setManageableSpaceIds(SPACE_A_A1); + controller.checkAccessGranted(null, null, true); + assertForbidden(() -> controller.checkAccessGranted(null, null, false)); + controller.checkAccessGranted(SPACE_A, null, true); + assertForbidden(() -> controller.checkAccessGranted(SPACE_A, null, false)); + controller.checkAccessGranted(SPACE_A_A1, null, true); + controller.checkAccessGranted(SPACE_A_A1, null, false); + controller.checkAccessGranted(SPACE_A_A1_A11, null, true); + controller.checkAccessGranted(SPACE_A_A1_A11, null, false); + assertForbidden(() -> controller.checkAccessGranted(SPACE_A_A2, null, true)); + assertForbidden(() -> controller.checkAccessGranted(SPACE_A_A2, null, false)); + assertForbidden(() -> controller.checkAccessGranted(SPACE_B, null, true)); + assertForbidden(() -> controller.checkAccessGranted(SPACE_B, null, false)); + } + + @DisplayName("Manager has granted access to aimed components") + @Test + void managerOfSpaceComponentAccessGranted() { + // ON SPACE A + controller.setManageableSpaceIds(SPACE_A); + controller.checkAccessGranted(null, null, true); + assertForbidden(() -> controller.checkAccessGranted(null, null, false)); + controller.checkAccessGranted(null, INSTANCE_AA, true); + controller.checkAccessGranted(null, INSTANCE_AA, false); + controller.checkAccessGranted(null, INSTANCE_A_A1A, true); + controller.checkAccessGranted(null, INSTANCE_A_A1A, false); + controller.checkAccessGranted(null, INSTANCE_A_A1_A11A, true); + controller.checkAccessGranted(null, INSTANCE_A_A1_A11A, false); + controller.checkAccessGranted(null, INSTANCE_A_A1B, true); + controller.checkAccessGranted(null, INSTANCE_A_A1B, false); + assertForbidden(() -> controller.checkAccessGranted(null, INSTANCE_BA, true)); + assertForbidden(() -> controller.checkAccessGranted(null, INSTANCE_BA, false)); + // ON SPACE A_A1 + controller.setManageableSpaceIds(SPACE_A_A1); + controller.checkAccessGranted(null, null, true); + assertForbidden(() -> controller.checkAccessGranted(null, null, false)); + assertForbidden(() -> controller.checkAccessGranted(null, INSTANCE_AA, true)); + assertForbidden(() -> controller.checkAccessGranted(null, INSTANCE_AA, false)); + controller.checkAccessGranted(null, INSTANCE_A_A1A, true); + controller.checkAccessGranted(null, INSTANCE_A_A1A, false); + controller.checkAccessGranted(null, INSTANCE_A_A1_A11A, true); + controller.checkAccessGranted(null, INSTANCE_A_A1_A11A, false); + controller.checkAccessGranted(null, INSTANCE_A_A1B, true); + controller.checkAccessGranted(null, INSTANCE_A_A1B, false); + assertForbidden(() -> controller.checkAccessGranted(null, INSTANCE_BA, true)); + assertForbidden(() -> controller.checkAccessGranted(null, INSTANCE_BA, false)); + } + + @DisplayName("Manager has granted access if space and instance ids can be accessed") + @Test + void managerOfSpaceAndComponentAccessGranted() { + // ON SPACE A + controller.setManageableSpaceIds(SPACE_A); + controller.checkAccessGranted(null, INSTANCE_A_A1A, false); + controller.checkAccessGranted(SPACE_A, INSTANCE_A_A1_A11A, false); + controller.checkAccessGranted(SPACE_A, INSTANCE_A_A1B, false); + assertForbidden(() -> controller.checkAccessGranted(SPACE_A, INSTANCE_BA, false)); + // ON SPACE A_A1 + controller.setManageableSpaceIds(SPACE_A_A1); + controller.checkAccessGranted(null, INSTANCE_A_A1A, false); + assertForbidden(() -> controller.checkAccessGranted(SPACE_A, INSTANCE_A_A1_A11A, false)); + assertForbidden(() -> controller.checkAccessGranted(SPACE_A, INSTANCE_A_A1B, false)); + assertForbidden(() -> controller.checkAccessGranted(SPACE_A, INSTANCE_BA, false)); + } + + private void assertForbidden(final Executable executable) { + assertThrows(ForbiddenRuntimeException.class, executable); + } + + private static class JobStartPagePeasSessionController4Test + extends JobStartPagePeasSessionController { + private static final long serialVersionUID = 101781645406476047L; + + private final UserDetail loggedUser = mock(UserDetail.class); + private String[] manageableSpaceIds = new String[]{}; + + public JobStartPagePeasSessionController4Test() { + super(null, mockedContext(), null, null, null); + } + + private static ComponentContext mockedContext() { + final ComponentContext mock = mock(ComponentContext.class); + when(mock.getCurrentComponentName()).thenReturn("unknown"); + return mock; + } + + private void setupDefaultLoggedUser() { + when(loggedUser.getId()).thenReturn("2"); + when(loggedUser.getDomainId()).thenReturn(LOGGED_USER_DOMAIN_ID); + when(loggedUser.getAccessLevel()).thenReturn(USER); + } + + public void setManageableSpaceIds(String... spaceIds) { + final List<String> allIds = new ArrayList<>(); + List<String> currents = List.of(spaceIds); + while (!currents.isEmpty()) { + allIds.addAll(currents); + currents = SPACE_SPACE.entrySet() + .stream() + .filter(e -> allIds.contains(e.getValue())) + .map(Map.Entry::getKey) + .filter(not(allIds::contains)) + .collect(Collectors.toList()); + } + manageableSpaceIds = allIds.toArray(new String[0]); + } + + @Override + public UserDetail getUserDetail() { + return loggedUser; + } + + @Override + protected String[] getUserManageableSpaceIds() { + return manageableSpaceIds; + } + } +} \ No newline at end of file
core-web/src/main/java/org/silverpeas/core/webapi/security/CipherKeyResource.java+9 −0 modified@@ -32,6 +32,7 @@ import org.silverpeas.core.util.LocalizationBundle; import org.silverpeas.core.util.ResourceLocator; import org.silverpeas.core.web.rs.RESTWebService; +import org.silverpeas.core.web.rs.UserPrivilegeValidation; import org.silverpeas.core.web.rs.annotation.Authorized; import javax.inject.Inject; @@ -40,6 +41,7 @@ import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; @@ -121,6 +123,13 @@ private ContentEncryptionService getContentEncryptionService() { return contentEncryptionService; } + @Override + public void validateUserAuthorization(final UserPrivilegeValidation validation) { + if (!getUser().isAccessAdmin()) { + throw new WebApplicationException(Response.Status.FORBIDDEN); + } + } + private static String formatMessage(String pattern, String value) { String msg = pattern; if (!pattern.endsWith("\n")) {
core-web/src/main/java/org/silverpeas/core/web/filter/MassiveWebSecurityFilter.java+3 −2 modified@@ -54,6 +54,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import static java.util.Optional.ofNullable; import static org.silverpeas.core.util.URLUtil.getCurrentServerURL; /** @@ -136,8 +137,8 @@ public void doFilter(final ServletRequest request, final ServletResponse respons } catch (WebSecurityException wse) { - logger.error("The request for path {0} isn''t valid: {1}", pathOf(httpRequest), - wse.getMessage()); + logger.error("The request for path {0} (uid={1}) isn''t valid: {2}", pathOf(httpRequest), + ofNullable(User.getCurrentRequester()).map(User::getId).orElse("N/A"), wse.getMessage()); // An HTTP error is sended to the client httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN, wse.getMessage());
core-web/src/main/java/org/silverpeas/core/web/mvc/controller/AbstractAdminComponentSessionController.java+208 −0 added@@ -0,0 +1,208 @@ +/* + * Copyright (C) 2000 - 2023 Silverpeas + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * As a special exception to the terms and conditions of version 3.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * Open Source Software ("FLOSS") applications as described in Silverpeas's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * "https://www.silverpeas.org/legal/floss_exception.html" + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +package org.silverpeas.core.web.mvc.controller; + +import org.silverpeas.core.admin.component.model.SilverpeasComponentInstance; +import org.silverpeas.core.admin.space.SpaceInst; +import org.silverpeas.core.admin.space.SpaceInstLight; +import org.silverpeas.core.security.authorization.ForbiddenRuntimeException; +import org.silverpeas.core.util.StringUtil; +import org.silverpeas.core.web.mvc.route.AdminComponentRequestRouter; + +import java.util.Objects; +import java.util.Set; + +import static java.util.Optional.ofNullable; +import static java.util.function.Predicate.not; +import static java.util.stream.Collectors.toSet; +import static java.util.stream.Stream.of; +import static org.silverpeas.core.util.StringUtil.isDefined; + +/** + * This abstraction centralizes common processes dedicated to several + * {@link AbstractComponentSessionController} implementations in charge of the management of + * administration services. + * <p> + * To be fully functional, an implementation of this abstraction MUST be used with an + * {@link AdminComponentRequestRouter} implementation. + * </p> + * @author silveryocha + */ +public abstract class AbstractAdminComponentSessionController + extends AbstractComponentSessionController { + private static final long serialVersionUID = 866731879572404115L; + + public AbstractAdminComponentSessionController(final MainSessionController controller, + final ComponentContext context, final String localizedMessagesBundleName) { + super(controller, context, localizedMessagesBundleName); + } + + public AbstractAdminComponentSessionController(final MainSessionController controller, + final ComponentContext context, final String localizedMessagesBundleName, + final String iconFileName) { + super(controller, context, localizedMessagesBundleName, iconFileName); + } + + public AbstractAdminComponentSessionController(final MainSessionController controller, + final ComponentContext context, final String localizedMessagesBundleName, + final String iconFileName, final String settingsFileName) { + super(controller, context, localizedMessagesBundleName, iconFileName, settingsFileName); + } + + @Override + public void setAppModeMaintenance(final boolean mode) { + checkAdminAccessOnly(); + super.setAppModeMaintenance(mode); + } + + @Override + public void setSpaceModeMaintenance(final String spaceId, final boolean mode) { + if (isDefined(spaceId)) { + checkAccessGranted(spaceId, null, false); + } else { + throwForbiddenError(); + } + super.setSpaceModeMaintenance(spaceId, mode); + } + + public boolean isUserAdmin() { + return getUserDetail().isAccessAdmin(); + } + + /** + * Checks the user has full admin access. + */ + public void checkAdminAccessOnly() { + if (!isUserAdmin()) { + throwForbiddenError(); + } + } + + /** + * Used mainly by {@link #checkAccessGranted()}. + * <p> + * Each implementation can precise or change this default implementation. + * </p> + * @return true if access granted, false otherwise. + */ + public boolean isAccessGranted() { + return isUserAdmin(); + } + + /** + * This method is invoked at each administration service access. + * @throws ForbiddenRuntimeException in case the user has forbidden access. + */ + public void checkAccessGranted() { + if (!isAccessGranted()) { + throwForbiddenError(); + } + } + + /** + * Indicates if the user can access administration about a space or a component instance. + * <p> + * Whatever the parameters, user having admin access right on its account is always access + * granted. + * </p> + * <p> + * If both space id and instance id are given, they are both verified. + * </p> + * <p> + * If neither space if neither instance id are given, it is verified that current user is a + * space manager. + * </p> + * @param spaceId the optional identifier of a space. + * @param instanceId the optional identifier of a component instance. + * @param readOnly true if the operation is read only, false otherwise. + * @return true if access granted, false otherwise. + */ + public boolean isAccessGranted(final String spaceId, final String instanceId, + final boolean readOnly) + throws ForbiddenRuntimeException { + boolean accessGranted = getUserDetail().isAccessAdmin(); + if (!accessGranted) { + final String[] userManageableSpaceIds = getUserManageableSpaceIds(); + accessGranted = userManageableSpaceIds.length > 0 && + (readOnly || isDefined(spaceId) || isDefined(instanceId)); + if (accessGranted) { + final Set<String> userManageableSpaceIdSet = of(userManageableSpaceIds).collect(toSet()); + if (isDefined(instanceId)) { + accessGranted = getOrganisationController().getComponentInstance(instanceId) + .map(SilverpeasComponentInstance::getSpaceId) + .map(userManageableSpaceIdSet::contains) + .orElse(false); + } + if (accessGranted && isDefined(spaceId)) { + accessGranted = ofNullable(getOrganisationController().getSpaceInstById(spaceId)) + .map(SpaceInst::getId) + .map(s -> userManageableSpaceIdSet.contains(s) || + (readOnly && of(userManageableSpaceIds) + .map(getOrganisationController()::getSpaceInstById) + .filter(Objects::nonNull) + .map(SpaceInst::getDomainFatherId) + .filter(StringUtil::isDefined) + .filter(not(userManageableSpaceIdSet::contains)) + .flatMap(m -> getOrganisationController().getPathToSpace(m).stream()) + .map(SpaceInstLight::getId) + .anyMatch(s::equals))) + .orElse(false); + } + } + } + return accessGranted; + } + + /** + * Checks the user can access administration about a space or a component instance. + * <p> + * Whatever the parameters, user having admin access right on its account is always access + * granted. + * </p> + * <p> + * If both space id and instance id are given, they are both verified. + * </p> + * <p> + * If neither space if neither instance id are given, it is verified that current user is a + * space manager. + * </p> + * @param spaceId the optional identifier of a space. + * @param instanceId the optional identifier of a component instance. + * @param readOnly true if the operation is read only, false otherwise. + * @throws ForbiddenRuntimeException in case the user has forbidden access. + */ + public void checkAccessGranted(final String spaceId, final String instanceId, + final boolean readOnly) + throws ForbiddenRuntimeException { + if (!isAccessGranted(spaceId, instanceId, readOnly)) { + throwForbiddenError(); + } + } + + protected void throwForbiddenError() { + throw new ForbiddenRuntimeException( + String.format("Forbidden admin access to user with id %s", getUserId())); + } +}
core-web/src/main/java/org/silverpeas/core/web/mvc/route/AdminComponentRequestRouter.java+84 −0 added@@ -0,0 +1,84 @@ +/* + * Copyright (C) 2000 - 2023 Silverpeas + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * As a special exception to the terms and conditions of version 3.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * Open Source Software ("FLOSS") applications as described in Silverpeas's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * "https://www.silverpeas.org/legal/floss_exception.html" + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +package org.silverpeas.core.web.mvc.route; + +import org.silverpeas.core.security.authorization.ForbiddenRuntimeException; +import org.silverpeas.core.util.Mutable; +import org.silverpeas.core.util.StringUtil; +import org.silverpeas.core.web.http.HttpRequest; +import org.silverpeas.core.web.mvc.controller.AbstractAdminComponentSessionController; + +import static java.util.Optional.ofNullable; + +/** + * This abstraction is dedicated to administration implementations of + * {@link ComponentRequestRouter}. + * <p> + * It mainly centralizes useful common processes for administration context, as the management of + * forbidden access. + * </p> + * @author silveryocha + */ +public abstract class AdminComponentRequestRouter<T extends AbstractAdminComponentSessionController> + extends ComponentRequestRouter<T> { + private static final long serialVersionUID = -1845547120139170876L; + + /** + * This method has to be implemented by the admin component request Router it has to compute a + * destination page + * @param function The entering request function (ex : "Main.jsp", when accessing + * "/RjobManager/jsp/Main.jsp") + * @param componentSC The component Session Controller, build and initialised. + * @param request The entering request. The request Router need it to get parameters + * @return The complete destination URL for a forward (ex : + * "/RjobManager/jsp/Main.jsp?flag=read") + */ + public abstract String getAdminDestination(String function, T componentSC, HttpRequest request); + + @Override + public String getDestination(final String function, final T componentSC, + final HttpRequest request) { + String destination = StringUtil.EMPTY; + final Mutable<ForbiddenRuntimeException> forbidden = Mutable.empty(); + try { + componentSC.checkAccessGranted(); + try { + destination = getAdminDestination(function, componentSC, request); + } finally { + ofNullable(request.getAttribute("javax.servlet.jsp.jspException")) + .filter(ForbiddenRuntimeException.class::isInstance) + .map(ForbiddenRuntimeException.class::cast) + .stream() + .forEach(forbidden::set); + } + } catch (final ForbiddenRuntimeException e) { + forbidden.set(e); + } + if (forbidden.isPresent()) { + throwHttpForbiddenError(forbidden.get().getMessage()); + } + return destination; + } +}
core-web/src/main/java/org/silverpeas/core/web/mvc/webcomponent/SilverpeasHttpServlet.java+8 −1 modified@@ -58,7 +58,14 @@ protected void service(final HttpServletRequest request, final HttpServletRespon super.service(request, response); } catch (HttpError httpError) { - SilverLogger.getLogger(this).debug("http error " + httpError.errorCode, httpError); + final String msg = String.format("http error %s [%s] -> %s", httpError.errorCode, + request.getRequestURI(), httpError.message); + final SilverLogger logger = SilverLogger.getLogger(this); + if (HttpServletResponse.SC_FORBIDDEN == httpError.errorCode) { + logger.error(msg); + } else { + logger.debug(msg); + } httpError.performResponse(response); } }
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5News mentions
0No linked articles in our index yet.