diff --git a/accesscontroltool-bundle/pom.xml b/accesscontroltool-bundle/pom.xml index 94f51a07..a067cb59 100644 --- a/accesscontroltool-bundle/pom.xml +++ b/accesscontroltool-bundle/pom.xml @@ -11,7 +11,7 @@ biz.netcentric.cq.tools.accesscontroltool accesscontroltool - 1.6.1 + 1.6.2 diff --git a/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/helper/AcHelper.java b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/helper/AcHelper.java index b629c194..5883cc57 100644 --- a/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/helper/AcHelper.java +++ b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/helper/AcHelper.java @@ -144,18 +144,7 @@ private static void writeAcBeansToRepository(final Session session, } else { history.addVerboseMessage("starting installation of bean: \n" + bean); - // check if path exists in CRX - if (session.itemExists(bean.getJcrPath())) { - bean.writeToRepository(session, currentPrincipal, history); - } else { - String warningMessage = "path: " - + bean.getJcrPath() - + " doesn't exist in repository. Skipped installation of this ACE!"; - LOG.warn(warningMessage); - history.addWarning(warningMessage); - continue; - } - + bean.install(session, currentPrincipal, history); } } } diff --git a/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/helper/AccessControlUtils.java b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/helper/AccessControlUtils.java index 415c94b2..5724221e 100644 --- a/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/helper/AccessControlUtils.java +++ b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/helper/AccessControlUtils.java @@ -22,6 +22,7 @@ import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.UnsupportedRepositoryOperationException; +import javax.jcr.Value; import javax.jcr.security.AccessControlEntry; import javax.jcr.security.AccessControlException; import javax.jcr.security.AccessControlManager; @@ -44,8 +45,8 @@ /** * This class provides common access control related utilities. + * Mostly a copy of org.apache.jackrabbit.commons.AccessControlUtils. */ - public class AccessControlUtils { private AccessControlUtils() { @@ -258,45 +259,6 @@ public static boolean addAccessControlEntry(Session session, return false; } - public static void addActions(Session session, AceBean aceBean, - Principal principal, AcInstallationHistoryPojo history) - throws RepositoryException { - - boolean isAllow = aceBean.isAllow(); - String[] actions = aceBean.getActions(); - CqActions cqActions = new CqActions(session); - String absPath = aceBean.getJcrPath(); - String globString = aceBean.getRepGlob(); - String[] privNames = aceBean.getPrivileges(); - Map actionMap = new HashMap(); - - if (actions != null) { - for (String action : actions) { - if ("create".equals(action) || "modify".equals(action) || "delete".equals(action)) { - actionMap.put(action, isAllow); - } - } - - Collection inheritedAllows = new HashSet(); - inheritedAllows.add("read"); - - LOG.info("setting actions for path: {} and principal {}", absPath, - principal.getName()); - cqActions.installActions(absPath, principal, actionMap, - inheritedAllows); - } else { - String message = "Could not install Actions for " + aceBean - + ", no actions defined!"; - history.addWarning(message); - - } - // if(privNames != null){ - // installPermissions(session, absPath, principal, isAllow, globString, - // privNames); - // } - - } - private static Principal getEveryonePrincipal(Session session) throws RepositoryException { if (session instanceof JackrabbitSession) { @@ -399,5 +361,17 @@ static JackrabbitAccessControlList getModifiableAcl( } return null; } - + + public static void extendExistingAceWithRestrictions(JackrabbitAccessControlList accessControlList, JackrabbitAccessControlEntry accessControlEntry, Map restrictions) throws AccessControlException, UnsupportedRepositoryOperationException, RepositoryException { + // 1. add new entry + if (!accessControlList.addEntry(accessControlEntry.getPrincipal(), accessControlEntry.getPrivileges(), accessControlEntry.isAllow(), restrictions)) { + throw new IllegalStateException("Could not add entry, probably because it was already there!"); + } + // we assume the entry being added is the last one + AccessControlEntry newAccessControlEntry = accessControlList.getAccessControlEntries()[accessControlList.size() - 1]; + // 2. put it to the right position now! + accessControlList.orderBefore(newAccessControlEntry, accessControlEntry); + // 3. remove old entry + accessControlList.removeAccessControlEntry(accessControlEntry); + } } diff --git a/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/helper/AceBean.java b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/helper/AceBean.java index 104952ec..459c4d4d 100644 --- a/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/helper/AceBean.java +++ b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/helper/AceBean.java @@ -10,18 +10,24 @@ import java.security.Principal; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.jcr.RepositoryException; import javax.jcr.Session; +import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.Value; +import javax.jcr.ValueFormatException; +import javax.jcr.security.AccessControlEntry; import javax.jcr.security.AccessControlManager; import javax.jcr.security.Privilege; import org.apache.commons.lang.StringUtils; +import org.apache.jackrabbit.api.security.JackrabbitAccessControlEntry; import org.apache.jackrabbit.api.security.JackrabbitAccessControlList; import biz.netcentric.cq.tools.actool.dumpservice.AcDumpElement; @@ -49,6 +55,9 @@ public class AceBean implements AcDumpElement { private String permission; private String[] actions; private String assertedExceptionString; + + + public static final String RESTRICTION_NAME_GLOB = "rep:glob"; public String getAssertedExceptionString() { return assertedExceptionString; @@ -242,6 +251,7 @@ private static Set removeRedundantPrivileges(Session session, String[] p return cleanedPrivileges; } for (String action : actions) { + @SuppressWarnings("deprecation") Set coveredPrivileges = cqActions.getPrivileges(action); for (Privilege coveredPrivilege : coveredPrivileges) { cleanedPrivileges.remove(coveredPrivilege.getName()); @@ -249,47 +259,101 @@ private static Set removeRedundantPrivileges(Session session, String[] p } return cleanedPrivileges; } + /** - * Persists the AccessControlEntry being represented by this bean to the - * repository + * Creates a restriction map being used in {@link JackrabbitAccessControlList#addEntry(Principal, Privilege[], boolean, Map)} out of the set actions on this bean. + * + * @param session the session + * @param acl the access control list for which this restriction map should be used + * @return a map with restriction names as keys and restriction values as values. + * @throws ValueFormatException + * @throws UnsupportedRepositoryOperationException + * @throws RepositoryException */ - public void writeToRepository(final Session session, Principal principal, AcInstallationHistoryPojo history) - throws RepositoryException { - AccessControlManager acMgr = session.getAccessControlManager(); - - // Convert actions to permissions, if necessary - if (getActions() != null) { - // install actions - history.addVerboseMessage("adding action for path: " - + getJcrPath() + ", principal: " - + principal.getName() + ", actions: " - + getActionsString() + ", permission: " - + getPermission()); - AccessControlUtils.addActions(session, this, principal, - history); - removeRedundantPrivileges(session); + private Map getSingleValueRestrictions(Session session, JackrabbitAccessControlList acl) throws ValueFormatException, UnsupportedRepositoryOperationException, RepositoryException { + Collection supportedRestrictionNames = Arrays.asList(acl.getRestrictionNames()); + if (getRepGlob() != null) { + if (!supportedRestrictionNames.contains(RESTRICTION_NAME_GLOB)) { + throw new IllegalStateException("The AccessControlList at " + acl.getPath() + " does not support setting rep:glob restrictions!"); + } + Value v = session.getValueFactory().createValue(getRepGlob(), acl.getRestrictionType(RESTRICTION_NAME_GLOB)); + return Collections.singletonMap(RESTRICTION_NAME_GLOB, v); + } else { + return Collections.emptyMap(); + } + } + + /** + * Creates an action map being used in {@link CqActions#installActions(String, Principal, Map, Collection)} out of the set actions on this bean. + * @return a map containing actions as keys and booleans representing {@code true} for allow and {@code false} for deny. + */ + private Map getActionMap() { + if (actions == null) { + return Collections.emptyMap(); + } + Map actionMap = new HashMap(); + for (String action : actions) { + actionMap.put(action, isAllow()); } - - - JackrabbitAccessControlList acl = AccessControlUtils.getModifiableAcl( - acMgr, getJcrPath()); - Set privileges = AccessControlUtils.getPrivilegeSet( - getPrivileges(), acMgr); + return actionMap; + } + + /** + * Installs the CQ actions in the repository. + * + * @param principal + * @param acl + * @param session + * @param acMgr + * @return either the same acl as given in the parameter {@code acl} if no actions have been installed otherwise the new AccessControlList (comprising the entres being installed for the actions). + * @throws RepositoryException + */ + private JackrabbitAccessControlList installActions(Principal principal, JackrabbitAccessControlList acl, Session session, AccessControlManager acMgr) throws RepositoryException { + Map actionMap = getActionMap(); + if (actionMap.isEmpty()) { + return acl; + } + int previousAclSize = acl.size(); - if (!privileges.isEmpty()) { - Map restrictions = null; - if (getRepGlob() != null) { - // is rep:glob supported? - for (String rName : acl.getRestrictionNames()) { - if ("rep:glob".equals(rName)) { - Value v = session.getValueFactory().createValue( - getRepGlob(), acl.getRestrictionType(rName)); - restrictions = Collections.singletonMap(rName, v); - break; - } - } + CqActions cqActions = new CqActions(session); + Collection inheritedAllows = cqActions.getAllowedActions( + getJcrPath(), Collections.singleton(principal)); + // this does always install new entries + cqActions.installActions(getJcrPath(), principal, actionMap, + inheritedAllows); + + // since the aclist has been modified, retrieve it again + JackrabbitAccessControlList newAcl = AccessControlUtils.getAccessControlList(session, getJcrPath()); + Map restrictions = getSingleValueRestrictions(session, acl); + if (restrictions.isEmpty()) { + return newAcl; + } + // additionally set restrictions on the installed actions (this is not supported by CQ Security API) + int newAclSize = newAcl.size(); + if (previousAclSize >= newAclSize) { + throw new IllegalStateException("No new entries have been set for AccessControlList at " + getJcrPath()); + } + AccessControlEntry[] aces = newAcl.getAccessControlEntries(); + for (int acEntryIndex = previousAclSize; acEntryIndex < newAclSize; acEntryIndex++) { + if (!(aces[acEntryIndex] instanceof JackrabbitAccessControlEntry)) { + throw new IllegalStateException("Can not deal with non JackrabbitAccessControlEntrys, but entry is of type " + aces[acEntryIndex].getClass().getName()); + } + JackrabbitAccessControlEntry ace = (JackrabbitAccessControlEntry)aces[acEntryIndex]; + // only extend those AccessControlEntries which do not yet have a restriction + if (ace.getRestrictions("rep:glob") == null) { + // modify this AccessControlEntry by adding the restriction + AccessControlUtils.extendExistingAceWithRestrictions(newAcl, ace, restrictions); } - if (restrictions != null) { + } + return newAcl; + } + + private boolean installPrivileges(Principal principal, JackrabbitAccessControlList acl, Session session, AccessControlManager acMgr) throws RepositoryException { + // then install remaining privileges + Set privileges = AccessControlUtils.getPrivilegeSet(getPrivileges(), acMgr); + if (!privileges.isEmpty()) { + Map restrictions = getSingleValueRestrictions(session, acl); + if (!restrictions.isEmpty()) { acl.addEntry(principal, (Privilege[]) privileges .toArray(new Privilege[privileges.size()]), isAllow(), restrictions); @@ -297,6 +361,41 @@ public void writeToRepository(final Session session, Principal principal, AcInst acl.addEntry(principal, (Privilege[]) privileges .toArray(new Privilege[privileges.size()]), isAllow()); } + return true; + } + return false; + } + + /** + * Installs the AccessControlEntry being represented by this bean in the + * repository + */ + public void install(final Session session, Principal principal, + AcInstallationHistoryPojo history) throws RepositoryException { + AccessControlManager acMgr = session.getAccessControlManager(); + + JackrabbitAccessControlList acl = AccessControlUtils.getModifiableAcl( + acMgr, getJcrPath()); + if (acl == null) { + history.addWarning("Skipped installing privileges/actions for non existing path: " + getJcrPath()); + return; + } + + // first install actions + JackrabbitAccessControlList newAcl = installActions(principal, acl, session, acMgr); + if (acl != newAcl) { + history.addVerboseMessage("added action(s) for path: " + getJcrPath() + + ", principal: " + principal.getName() + ", actions: " + + getActionsString() + ", allow: " + isAllow()); + removeRedundantPrivileges(session); + acl = newAcl; + } + + // then install (remaining) privileges + if (installPrivileges(principal, acl, session, acMgr)) { + history.addVerboseMessage("added privilege(s) for path: " + getJcrPath() + + ", principal: " + principal.getName() + ", privileges: " + + getPrivilegesString() + ", allow: " + isAllow()); } acMgr.setPolicy(getJcrPath(), acl); } diff --git a/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/installationhistory/HistoryEntry.java b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/installationhistory/HistoryEntry.java index 1c8b9e0a..bf47982e 100644 --- a/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/installationhistory/HistoryEntry.java +++ b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/installationhistory/HistoryEntry.java @@ -47,4 +47,12 @@ public void setIndex(long index) { this.index = index; } + @Override + public String toString() { + return "HistoryEntry [timestamp=" + timestamp + ", message=" + message + + ", index=" + index + "]"; + } + + + } diff --git a/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/installhook/AcToolInstallHook.java b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/installhook/AcToolInstallHook.java index bb7a293f..4c284aa8 100644 --- a/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/installhook/AcToolInstallHook.java +++ b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/installhook/AcToolInstallHook.java @@ -4,6 +4,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import biz.netcentric.cq.tools.actool.installationhistory.AcInstallationHistoryPojo; +import biz.netcentric.cq.tools.actool.installationhistory.HistoryEntry; + import com.day.jcr.vault.packaging.InstallContext; import com.day.jcr.vault.packaging.PackageException; @@ -45,11 +48,30 @@ public void execute(InstallContext context) throws PackageException { } // try { - acService.installYamlFilesFromPackage(context.getPackage().getArchive(), context.getSession()); - log("Installed ACLs through AcToolInstallHook!", context.getOptions()); - } catch (Exception e) { - log("Exception while installing configurations: " + e, context.getOptions()); - throw new PackageException(e.getMessage(), e); + AcInstallationHistoryPojo history; + try { + history = acService.installYamlFilesFromPackage(context + .getPackage().getArchive(), context.getSession()); + + } catch (Exception e) { + log("Exception while installing configurations: " + e, + context.getOptions()); + throw new PackageException(e.getMessage(), e); + } + + if (!history.isSuccess()) { + for (HistoryEntry entry : history.getException()) { + log(entry.toString(), context.getOptions()); + } + throw new PackageException( + "Could not install configurations. Check log for detailed error message!"); + } else { + // convert to correct (HTML) linebreaks for the package manager + String log = history.toString().replaceAll("\\\n", "
"); + log(log, context.getOptions()); + log("Installed ACLs successfully through AcToolInstallHook!", + context.getOptions()); + } } finally { getBundleContext().ungetService(acToolInstallHookService); } @@ -61,6 +83,4 @@ public void execute(InstallContext context) throws PackageException { } } - - } diff --git a/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/installhook/AcToolInstallHookService.java b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/installhook/AcToolInstallHookService.java index de13afe8..fa7a057d 100644 --- a/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/installhook/AcToolInstallHookService.java +++ b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/installhook/AcToolInstallHookService.java @@ -2,11 +2,13 @@ import javax.jcr.Session; +import biz.netcentric.cq.tools.actool.installationhistory.AcInstallationHistoryPojo; + import com.day.jcr.vault.fs.io.Archive; public interface AcToolInstallHookService { - public void installYamlFilesFromPackage(Archive archive, Session session) + public AcInstallationHistoryPojo installYamlFilesFromPackage(Archive archive, Session session) throws Exception; } \ No newline at end of file diff --git a/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/installhook/AcToolInstallHookServiceImpl.java b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/installhook/AcToolInstallHookServiceImpl.java index 1bd7b134..9713f706 100644 --- a/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/installhook/AcToolInstallHookServiceImpl.java +++ b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/installhook/AcToolInstallHookServiceImpl.java @@ -32,7 +32,7 @@ public class AcToolInstallHookServiceImpl implements AcToolInstallHookService { private ConfigFilesRetriever configFilesRetriever; @Override - public void installYamlFilesFromPackage(Archive archive, Session session) + public AcInstallationHistoryPojo installYamlFilesFromPackage(Archive archive, Session session) throws Exception { AcInstallationHistoryPojo history = new AcInstallationHistoryPojo(); Set authorizableInstallationHistorySet = new LinkedHashSet(); @@ -47,5 +47,6 @@ public void installYamlFilesFromPackage(Archive archive, Session session) // TODO: acHistoryService.persistHistory(history, // this.configurationPath); } + return history; } } diff --git a/accesscontroltool-oakindex-package/pom.xml b/accesscontroltool-oakindex-package/pom.xml index 98c10c11..11f462a0 100644 --- a/accesscontroltool-oakindex-package/pom.xml +++ b/accesscontroltool-oakindex-package/pom.xml @@ -15,7 +15,7 @@ biz.netcentric.cq.tools.accesscontroltool accesscontroltool - 1.6.1 + 1.6.2 diff --git a/accesscontroltool-package/pom.xml b/accesscontroltool-package/pom.xml index d34571f0..79ef5221 100644 --- a/accesscontroltool-package/pom.xml +++ b/accesscontroltool-package/pom.xml @@ -15,7 +15,7 @@ biz.netcentric.cq.tools.accesscontroltool accesscontroltool - 1.6.1 + 1.6.2 diff --git a/pom.xml b/pom.xml index 8222070f..773810a7 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ biz.netcentric.cq.tools.accesscontroltool accesscontroltool - 1.6.1 + 1.6.2 pom Access Control Tool - Reactor Project @@ -339,7 +339,7 @@ external.atlassian.jgitflow jgitflow-maven-plugin - 1.0-m4.3 + 1.0-m5.1 true true