Skip to content

Commit 4c0bb75

Browse files
committed
Use run modes to determine server type
Deprecate other/more brittle ways Skip mutable ACL installation during image build
1 parent 5b664a8 commit 4c0bb75

15 files changed

Lines changed: 259 additions & 57 deletions

File tree

accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/aceinstaller/AceBeanInstallerClassic.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,18 @@
3535
import org.apache.commons.lang3.StringUtils;
3636
import org.apache.jackrabbit.api.security.JackrabbitAccessControlEntry;
3737
import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
38+
import org.apache.sling.settings.SlingSettingsService;
39+
import org.osgi.service.component.annotations.Activate;
3840
import org.osgi.service.component.annotations.Component;
41+
import org.osgi.service.component.annotations.Reference;
3942
import org.slf4j.Logger;
4043
import org.slf4j.LoggerFactory;
4144

4245
import biz.netcentric.cq.tools.actool.aem.AcToolCqActions;
4346
import biz.netcentric.cq.tools.actool.configmodel.AceBean;
4447
import biz.netcentric.cq.tools.actool.helper.AccessControlUtils;
4548
import biz.netcentric.cq.tools.actool.helper.RestrictionsHolder;
49+
import biz.netcentric.cq.tools.actool.helper.runtime.RuntimeHelper;
4650
import biz.netcentric.cq.tools.actool.history.InstallationLogger;
4751
import biz.netcentric.cq.tools.actool.impl.SimpleNamePrincipal;
4852

@@ -53,7 +57,11 @@ public class AceBeanInstallerClassic extends BaseAceBeanInstaller implements Ace
5357

5458
private static final Logger LOG = LoggerFactory.getLogger(AceBeanInstallerClassic.class);
5559

56-
60+
@Activate
61+
public AceBeanInstallerClassic(@Reference SlingSettingsService slingSettingsService) {
62+
super(RuntimeHelper.getServerType(slingSettingsService.getRunModes()));
63+
}
64+
5765
/** Installs a full set of ACE beans that form an ACL for the path
5866
*
5967
* @throws RepositoryException */

accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/aceinstaller/AceBeanInstallerIncremental.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
import org.apache.commons.lang3.StringUtils;
4242
import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
4343
import org.apache.sling.jcr.api.SlingRepository;
44+
import org.apache.sling.settings.SlingSettingsService;
45+
import org.osgi.service.component.annotations.Activate;
4446
import org.osgi.service.component.annotations.Component;
4547
import org.osgi.service.component.annotations.Reference;
4648
import org.osgi.service.component.annotations.ReferencePolicyOption;
@@ -52,6 +54,8 @@
5254
import biz.netcentric.cq.tools.actool.configmodel.Restriction;
5355
import biz.netcentric.cq.tools.actool.helper.AcHelper;
5456
import biz.netcentric.cq.tools.actool.helper.AccessControlUtils;
57+
import biz.netcentric.cq.tools.actool.helper.runtime.RuntimeHelper;
58+
import biz.netcentric.cq.tools.actool.helper.runtime.RuntimeHelper.ServerType;
5559
import biz.netcentric.cq.tools.actool.history.InstallationLogger;
5660
import biz.netcentric.cq.tools.actool.impl.SimpleNamePrincipal;
5761

@@ -65,6 +69,15 @@ public class AceBeanInstallerIncremental extends BaseAceBeanInstaller implements
6569

6670
private Map<String, Set<AceBean>> actionsToPrivilegesMapping = new ConcurrentHashMap<String, Set<AceBean>>();
6771

72+
@Activate
73+
public AceBeanInstallerIncremental(@Reference SlingSettingsService slingSettingsService) {
74+
this(RuntimeHelper.getServerType(slingSettingsService.getRunModes()));
75+
}
76+
77+
protected AceBeanInstallerIncremental(ServerType serverType) {
78+
super(serverType);
79+
}
80+
6881
/** Installs a full set of ACE beans that form an ACL for the path
6982
*
7083
* @throws RepositoryException */

accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/aceinstaller/BaseAceBeanInstaller.java

Lines changed: 61 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.Map;
2424
import java.util.Set;
2525
import java.util.TreeSet;
26+
import java.util.function.Predicate;
2627

2728
import javax.jcr.RepositoryException;
2829
import javax.jcr.Session;
@@ -42,11 +43,19 @@
4243
import biz.netcentric.cq.tools.actool.helper.AccessControlUtils;
4344
import biz.netcentric.cq.tools.actool.helper.ContentHelper;
4445
import biz.netcentric.cq.tools.actool.helper.RestrictionsHolder;
45-
import biz.netcentric.cq.tools.actool.helper.runtime.RuntimeHelper;
46+
import biz.netcentric.cq.tools.actool.helper.runtime.RuntimeHelper.ServerType;
4647
import biz.netcentric.cq.tools.actool.history.InstallationLogger;
4748

4849
/** Base Class */
4950
public abstract class BaseAceBeanInstaller implements AceBeanInstaller {
51+
52+
protected final ServerType serverType;
53+
54+
private static final String[] IMMUTABLE_PATH_PREFIXES = new String[] { "/apps", "/libs" };
55+
56+
protected BaseAceBeanInstaller(ServerType serverType) {
57+
this.serverType = serverType;
58+
}
5059

5160
private static final Logger LOG = LoggerFactory.getLogger(BaseAceBeanInstaller.class);
5261

@@ -65,7 +74,7 @@ public void installPathBasedACEs(
6574
history.addVerboseMessage(LOG, "Found " + paths.size() + " paths in config");
6675
LOG.trace("Paths with ACEs: {}", paths);
6776

68-
paths = filterReadOnlyPaths(paths, history, session);
77+
paths = filterRelevantPaths(paths, history, session);
6978

7079
// loop through all nodes from config
7180
for (final String path : paths) {
@@ -107,26 +116,62 @@ public void installPathBasedACEs(
107116
+ msHumanReadable(stopWatch.getTime()));
108117
}
109118

110-
private Set<String> filterReadOnlyPaths(Set<String> paths, InstallationLogger history, Session session) {
111-
112-
boolean isCompositeNodeStore = RuntimeHelper.isCompositeNodeStore(session);
113-
if (isCompositeNodeStore) {
114-
Set<String> pathsToKeep = new TreeSet<String>();
115-
Set<String> readOnlyPaths = new TreeSet<String>();
116-
for (final String path : paths) {
117-
if (path != null && (path.startsWith("/apps") || path.startsWith("/libs"))) {
118-
readOnlyPaths.add(path);
119-
} else {
120-
pathsToKeep.add(path);
121-
}
122-
}
123-
history.addMessage(LOG, "Ignoring " + readOnlyPaths.size() + " ACLs in /apps and /libs because they are ready-only (Composite NodeStore)");
119+
private Set<String> filterRelevantPaths(Set<String> paths, InstallationLogger history, Session session) {
120+
if (serverType == ServerType.AEM_CLOUD_RUN) {
121+
Set<String> pathsToKeep = removePathsWithPrefixes(paths, IMMUTABLE_PATH_PREFIXES);
122+
history.addMessage(LOG, "Ignoring " + (paths.size() - pathsToKeep.size()) + " ACLs in " + String.join(", ", IMMUTABLE_PATH_PREFIXES) + " because they are ready-only (Composite NodeStore)");
123+
return pathsToKeep;
124+
} else if (serverType == ServerType.AEM_CLOUD_IMAGE_BUILD) {
125+
Set<String> pathsToKeep = removePathsWithoutPrefixes(paths, IMMUTABLE_PATH_PREFIXES);
126+
history.addMessage(LOG, "Ignoring " + (paths.size() - pathsToKeep.size()) + " ACLs outside " + String.join(", ", IMMUTABLE_PATH_PREFIXES) + " during image build, as they are overlaid");
124127
return pathsToKeep;
125128
} else {
126129
return paths;
127130
}
128131
}
129132

133+
static final class PrefixesFilter implements java.util.function.Predicate<String> {
134+
private final String[] prefixes;
135+
136+
public PrefixesFilter(String... prefixes) {
137+
// the prefixes should be normalized (e.g. no trailing slash) to avoid issues with matching, but we do not want to enforce this on the caller, so we do it here
138+
Arrays.stream(prefixes).forEach(prefix -> {
139+
if (prefix != null && prefix.endsWith("/") && prefix.length() > 1) {
140+
throw new IllegalArgumentException("Prefixes must not end with a slash, but given prefix \"" + prefix + "\" ends with a slash");
141+
}
142+
});
143+
this.prefixes = prefixes;
144+
}
145+
146+
@Override
147+
public boolean test(String path) {
148+
for (String prefix : prefixes) {
149+
if (path != null && (path.equals(prefix) || path.startsWith(prefix + "/"))) {
150+
return true;
151+
}
152+
}
153+
return false;
154+
}
155+
}
156+
157+
static Set<String> removePathsWithPrefixes(Set<String> paths, String... prefixes) {
158+
return removePathWithPredicate(paths, new PrefixesFilter(prefixes));
159+
}
160+
161+
static Set<String> removePathsWithoutPrefixes(Set<String> paths, String... prefixes) {
162+
return removePathWithPredicate(paths, Predicate.not(new PrefixesFilter(prefixes)));
163+
}
164+
165+
static Set<String> removePathWithPredicate(Set<String> paths, Predicate<String> pathPredicate) {
166+
Set<String> filteredPaths = new TreeSet<>();
167+
for (String path : paths) {
168+
if (!pathPredicate.test(path)) {
169+
filteredPaths.add(path);
170+
}
171+
}
172+
return filteredPaths;
173+
}
174+
130175
/** Installs a full set of ACE beans that form an ACL for the path
131176
*
132177
* @throws RepositoryException */

accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/helper/runtime/RuntimeHelper.java

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package biz.netcentric.cq.tools.actool.helper.runtime;
22

3+
import java.util.Collection;
4+
import java.util.Objects;
5+
36
/*-
47
* #%L
58
* Access Control Tool Bundle
@@ -25,12 +28,19 @@
2528
import org.slf4j.Logger;
2629
import org.slf4j.LoggerFactory;
2730

28-
/** Provides basic context information about the runtime. In own package so it can be inlined with "Conditional-Package" from startup bundle */
31+
/** Provides basic context information about the runtime.
32+
* Located in its own package so it can be inlined with "Conditional-Package" from startup bundle */
2933
public class RuntimeHelper {
3034
public static final Logger LOG = LoggerFactory.getLogger(RuntimeHelper.class);
3135

3236
private static final String INSTALLER_CORE_BUNDLE_SYMBOLIC_ID = "org.apache.sling.installer.core";
3337

38+
/**
39+
*
40+
* @param session
41+
* @return
42+
* @deprecated Use {@link #getServerType(Collection)} instead
43+
*/
3444
public static boolean isCompositeNodeStore(Session session) {
3545

3646
try {
@@ -52,7 +62,7 @@ public static boolean isCompositeNodeStore(Session session) {
5262
throw new IllegalStateException("Could not check if session is connected to a composite node store: "+e, e);
5363
}
5464
}
55-
65+
5666
public static int getCurrentStartLevel() {
5767
return getCurrentStartLevel(FrameworkUtil.getBundle(RuntimeHelper.class).getBundleContext());
5868
}
@@ -61,8 +71,13 @@ public static int getCurrentStartLevel(BundleContext bundleContext) {
6171
return bundleContext.getBundle(Constants.SYSTEM_BUNDLE_ID).adapt(FrameworkStartLevel.class).getStartLevel();
6272
}
6373

74+
/**
75+
*
76+
* @return {@code true} if the installer core bundle is not present (the case for all AEMaaCS runtimes running in the cloud)
77+
* @deprecated Use {@link #getServerType(Collection)} instead
78+
*/
79+
@Deprecated
6480
public static boolean isCloudReadyInstance() {
65-
6681
boolean isCloudReadyInstance = true;
6782
Bundle[] bundles = FrameworkUtil.getBundle(RuntimeHelper.class).getBundleContext().getBundles();
6883
for (Bundle bundle : bundles) {
@@ -74,4 +89,67 @@ public static boolean isCloudReadyInstance() {
7489
return isCloudReadyInstance;
7590
}
7691

92+
/**
93+
* The server type encapsulates information about the runtime environment,
94+
* especially regarding the type of AEM instance (classic vs cloud) and whether it is running in a composite node store or not.
95+
*/
96+
public enum ServerType {
97+
/** AEM 6.5 (LTS) and older */
98+
AEM_CLASSIC(false, false),
99+
/** AEM as a Cloud Service SDK (probably running locally) */
100+
AEM_CLOUD_SDK(true, false),
101+
/** AEM as a Cloud Service during Cloud Manager image build (with composite node store seed mode) */
102+
AEM_CLOUD_IMAGE_BUILD(true, true),
103+
/** AEM as a Cloud Service running in Adobe Cloud (with composite node store) */
104+
AEM_CLOUD_RUN(true, true),
105+
/** Any other (non AEM) Sling based runtime */
106+
SLING(false, false);
107+
108+
private final boolean isAEMaaCS;
109+
private final boolean isInCloud;
110+
111+
ServerType(boolean isAEMaaCS, boolean isInCloud) {
112+
this.isAEMaaCS = isAEMaaCS;
113+
this.isInCloud = isInCloud;
114+
}
115+
116+
/**
117+
* Indicates whether this server type is an AEM as a Cloud Service runtime (either SDK, image build or cloud run).
118+
*/
119+
public boolean isAEMaaCS() {
120+
return isAEMaaCS;
121+
}
122+
123+
/**
124+
* Indicates whether this server type is running in the cloud with composite node store (either image build or cloud run).
125+
*/
126+
public boolean isInCloud() {
127+
return isInCloud;
128+
}
129+
}
130+
131+
/**
132+
* Determines the server type based on the provided run modes.
133+
* @param runModes The collection of active run modes.
134+
* @return The determined {@link ServerType}.
135+
*/
136+
public static ServerType getServerType(Collection<String> runModes) {
137+
Objects.requireNonNull(runModes, "Run modes collection cannot be null");
138+
// "crx3" is available on all AEMs
139+
// "crx3composite-seed" is only available on composite node store seed mode (which is currently only used for cloud image build)
140+
// "sdk" (next to "crx3, crx3tarmk, crx3tar) is only available on cloud sdk
141+
// "crx3composite" and "cloud-ready" in cloud run instances
142+
// everything else would be Sling
143+
if(runModes.contains("sdk")) {
144+
return ServerType.AEM_CLOUD_SDK;
145+
} else if(runModes.contains("crx3composite-seed")) {
146+
return ServerType.AEM_CLOUD_IMAGE_BUILD;
147+
} else if(runModes.contains("crx3composite")) {
148+
return ServerType.AEM_CLOUD_RUN;
149+
} else if(runModes.contains("crx3")) {
150+
return ServerType.AEM_CLASSIC;
151+
} else {
152+
return ServerType.SLING;
153+
}
154+
}
77155
}

accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/history/impl/AcHistoryServiceImpl.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.apache.jackrabbit.JcrConstants;
2929
import org.apache.jackrabbit.commons.JcrUtils;
3030
import org.apache.sling.jcr.api.SlingRepository;
31+
import org.apache.sling.settings.SlingSettingsService;
3132
import org.osgi.service.component.annotations.Activate;
3233
import org.osgi.service.component.annotations.Component;
3334
import org.osgi.service.component.annotations.Reference;
@@ -38,6 +39,7 @@
3839
import org.slf4j.Logger;
3940
import org.slf4j.LoggerFactory;
4041

42+
import biz.netcentric.cq.tools.actool.helper.runtime.RuntimeHelper;
4143
import biz.netcentric.cq.tools.actool.history.AcHistoryService;
4244
import biz.netcentric.cq.tools.actool.history.AcToolExecution;
4345
import biz.netcentric.cq.tools.actool.history.impl.AcHistoryServiceImpl.Configuration;
@@ -57,6 +59,9 @@ public class AcHistoryServiceImpl implements AcHistoryService {
5759
@Reference(policyOption = ReferencePolicyOption.GREEDY)
5860
private SlingRepository repository;
5961

62+
@Reference(policyOption = ReferencePolicyOption.GREEDY)
63+
private SlingSettingsService slingSettingsService;
64+
6065
@ObjectClassDefinition(name = "AC Tool History Service",
6166
description="Service that writes & fetches Ac installation histories.",
6267
id="biz.netcentric.cq.tools.actool.history.impl.AcHistoryServiceImpl")
@@ -83,7 +88,7 @@ public void persistHistory(PersistableInstallationLogger installLog) {
8388
try {
8489

8590
session = repository.loginService(null, null);
86-
Node historyNode = HistoryUtils.persistHistory(session, installLog, nrOfSavedHistories);
91+
Node historyNode = HistoryUtils.persistHistory(session, installLog, nrOfSavedHistories, RuntimeHelper.getServerType(slingSettingsService.getRunModes()));
8792

8893
String mergedAndProcessedConfig = installLog.getMergedAndProcessedConfig();
8994
if (StringUtils.isNotBlank(mergedAndProcessedConfig)) {

accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/history/impl/HistoryUtils.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import biz.netcentric.cq.tools.actool.api.InstallationResult;
4949
import biz.netcentric.cq.tools.actool.comparators.TimestampPropertyComparator;
5050
import biz.netcentric.cq.tools.actool.helper.runtime.RuntimeHelper;
51+
import biz.netcentric.cq.tools.actool.helper.runtime.RuntimeHelper.ServerType;
5152
import biz.netcentric.cq.tools.actool.history.AcToolExecution;
5253
import biz.netcentric.cq.tools.actool.jmx.AceServiceMBeanImpl;
5354
import biz.netcentric.cq.tools.actool.ui.AcToolWebconsolePlugin;
@@ -102,10 +103,11 @@ public static Node getAcHistoryRootNode(final Session session)
102103
* @param nrOfHistoriesToSave
103104
* number of newest histories which should be kept in CRX. older
104105
* histories get automatically deleted
106+
* @param serverType
105107
* @return the node being created
106108
*/
107109
public static Node persistHistory(final Session session,
108-
PersistableInstallationLogger installLog, final int nrOfHistoriesToSave)
110+
PersistableInstallationLogger installLog, final int nrOfHistoriesToSave, ServerType serverType)
109111
throws RepositoryException {
110112

111113
Node acHistoryRootNode = getAcHistoryRootNode(session);
@@ -126,7 +128,7 @@ public static Node persistHistory(final Session session,
126128
trigger = "startup_hook_pckmgr)";
127129
} else {
128130
// if the history is not yet copied to apps, it's the image build
129-
boolean isImageBuild = RuntimeHelper.isCloudReadyInstance() && !session.itemExists(AC_HISTORY_PATH_IN_APPS);
131+
boolean isImageBuild = serverType == ServerType.AEM_CLOUD_IMAGE_BUILD && !session.itemExists(AC_HISTORY_PATH_IN_APPS);
130132
if(isImageBuild) {
131133
trigger = "startup_hook_image_build";
132134
} else {

0 commit comments

Comments
 (0)