Skip to content

Commit ecea5eb

Browse files
committed
Dedicated OSGi configuration for granting access to UI
No longer leverage PID SlingWebConsoleSecurityProvider as defaults no longer reasonably set in AEMaaCS This closes #781
1 parent 9d1ab3d commit ecea5eb

File tree

3 files changed

+109
-126
lines changed

3 files changed

+109
-126
lines changed

accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/ui/AcToolUiService.java

Lines changed: 38 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import java.text.SimpleDateFormat;
2424
import java.util.Arrays;
2525
import java.util.HashMap;
26-
import java.util.Iterator;
2726
import java.util.LinkedHashMap;
2827
import java.util.LinkedList;
2928
import java.util.List;
@@ -34,21 +33,23 @@
3433
import java.util.stream.Collectors;
3534

3635
import javax.jcr.RepositoryException;
36+
import javax.jcr.Session;
3737
import javax.jcr.Value;
3838
import javax.servlet.ServletException;
3939
import javax.servlet.http.HttpServletRequest;
4040
import javax.servlet.http.HttpServletResponse;
4141

42-
import org.apache.commons.lang3.ArrayUtils;
4342
import org.apache.commons.lang3.StringUtils;
4443
import org.apache.felix.webconsole.WebConsoleConstants;
45-
import org.apache.jackrabbit.api.security.user.Group;
44+
import org.apache.jackrabbit.api.JackrabbitSession;
4645
import org.apache.jackrabbit.api.security.user.User;
4746
import org.apache.jackrabbit.oak.spi.security.principal.EveryonePrincipal;
4847
import org.apache.sling.api.SlingHttpServletRequest;
4948
import org.osgi.service.component.annotations.Component;
5049
import org.osgi.service.component.annotations.Reference;
5150
import org.osgi.service.component.annotations.ReferencePolicyOption;
51+
import org.osgi.service.metatype.annotations.AttributeDefinition;
52+
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
5253
import org.slf4j.Logger;
5354
import org.slf4j.LoggerFactory;
5455

@@ -88,15 +89,26 @@ public class AcToolUiService {
8889
@Reference(policyOption = ReferencePolicyOption.GREEDY)
8990
AcInstallationServiceInternal acInstallationService;
9091

91-
@Reference(policyOption = ReferencePolicyOption.GREEDY)
92-
private WebConsoleConfigTracker webConsoleConfig;
93-
9492
@Reference(policyOption = ReferencePolicyOption.GREEDY)
9593
private AcHistoryService acHistoryService;
9694

95+
@ObjectClassDefinition(name = "AC Tool UI Service",
96+
description="Service that allows to apply AC Tool configuration from the UI and gather some status about the current ACLs of a system")
97+
protected static @interface Configuration {
98+
99+
@AttributeDefinition(name="Allowed to read", description="Principal names allowed to read status about the current acls maintained in the system")
100+
String[] allowReadPrincipalNames() default { "admin" };
101+
102+
@AttributeDefinition(name="Allowed to write", description="Principal names allowed to modify the ACLs in the system via ACTool configuration files")
103+
String[] allowWritePrincipalNames() default { "administrators", "admin" };
104+
}
105+
97106
private final Map<String, String> countryCodePerName;
98107

99-
public AcToolUiService() {
108+
private final Configuration config;
109+
110+
public AcToolUiService(Configuration config) {
111+
this.config = config;
100112
countryCodePerName = new HashMap<>();
101113
for (String iso : Locale.getISOCountries()) {
102114
Locale l = new Locale(Locale.ENGLISH.getLanguage(), iso);
@@ -108,16 +120,17 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp, String pa
108120
throws ServletException, IOException {
109121

110122
if (req.getRequestURI().endsWith(SUFFIX_DUMP_YAML)) {
111-
callWhenAuthorized(req, resp, this::streamDumpToResponse);
123+
callWhenReadAccessGranted(req, resp, this::streamDumpToResponse);
112124
} else if (req.getRequestURI().endsWith(SUFFIX_USERS_CSV)) {
113-
callWhenAuthorized(req, resp, this::streamUsersCsvToResponse);
125+
callWhenReadAccessGranted(req, resp, this::streamUsersCsvToResponse);
114126
} else {
127+
// everyone is allows to see the UI in general
115128
renderUi(req, resp, path, isTouchUi);
116129
}
117130
}
118131

119-
private void callWhenAuthorized(HttpServletRequest req, HttpServletResponse resp, Consumer<HttpServletResponse> responseConsumer) throws IOException {
120-
if (!hasAccessToFelixWebConsole(req)) {
132+
private void callWhenReadAccessGranted(HttpServletRequest req, HttpServletResponse resp, Consumer<HttpServletResponse> responseConsumer) throws IOException, ServletException {
133+
if (!isOneOfPrincipalNamesBound(req, config.allowReadPrincipalNames())) {
121134
resp.sendError(HttpServletResponse.SC_FORBIDDEN, "You do not have sufficent permissions to export users/groups/permissions");
122135
return;
123136
}
@@ -127,12 +140,13 @@ private void callWhenAuthorized(HttpServletRequest req, HttpServletResponse resp
127140
throw e.getCause();
128141
}
129142
}
143+
130144
@SuppressWarnings(/* SonarCloud false positive */ {
131145
"javasecurity:S5131" /* response is sent as text/plain, it's not interpreted */,
132146
"javasecurity:S5145" /* logging the path is fine */ })
133147
protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws IOException, ServletException {
134148

135-
if (!hasAccessToFelixWebConsole(req)) {
149+
if (!isOneOfPrincipalNamesBound(req, config.allowWritePrincipalNames())) {
136150
resp.sendError(HttpServletResponse.SC_FORBIDDEN, "You do not have sufficent permissions to apply the configuration");
137151
return;
138152
}
@@ -157,45 +171,27 @@ protected void doPost(final HttpServletRequest req, final HttpServletResponse re
157171
}
158172

159173
/**
160-
* Replicates the logic of the <a href="https://sling.apache.org/documentation/bundles/web-console-extensions.html#authentication-handling">Sling Web Console Security Provider</a>.
174+
* Similar to the logic of the <a href="https://sling.apache.org/documentation/bundles/web-console-extensions.html#authentication-handling">Sling Web Console Security Provider</a> but acting on principal names
161175
* @param req the request
162-
* @return {@code true} if the user bound to the given request may also access the Felix Web Console or if we are outside of Sling, {@code false} otherwise
176+
* @param principalNames the principal names to check against
177+
* @return {@code true} if the session bound to the given request is bound to any of the given principal names
178+
* @throws ServletException
179+
* @throws RepositoryException
163180
*/
164-
private boolean hasAccessToFelixWebConsole(HttpServletRequest req) {
165-
181+
private boolean isOneOfPrincipalNamesBound(HttpServletRequest req, String[] principalNames) throws ServletException {
166182
if (!(req instanceof SlingHttpServletRequest)) {
167183
// outside Sling this is only called by the Felix Web Console, which has its own security layer
168184
LOG.debug("Outside Sling no additional security checks are performed!");
169185
return true;
170186
}
187+
Session session = SlingHttpServletRequest.class.cast(req).getResourceResolver().adaptTo(Session.class);
188+
BoundPrincipals boundPrincipals;
171189
try {
172-
User requestUser = SlingHttpServletRequest.class.cast(req).getResourceResolver().adaptTo(User.class);
173-
if (requestUser != null) {
174-
if (StringUtils.equals(requestUser.getID(), "admin")) {
175-
LOG.debug("Admin user is allowed to apply AC Tool");
176-
return true;
177-
}
178-
179-
if (ArrayUtils.contains(webConsoleConfig.getAllowedUsers(), requestUser.getID())) {
180-
LOG.debug("User {} is allowed to apply AC Tool (allowed users: {})", requestUser.getID(), ArrayUtils.toString(webConsoleConfig.getAllowedUsers()));
181-
return true;
182-
}
183-
184-
Iterator<Group> memberOfIt = requestUser.memberOf();
185-
186-
while (memberOfIt.hasNext()) {
187-
Group memberOfGroup = memberOfIt.next();
188-
if (ArrayUtils.contains(webConsoleConfig.getAllowedGroups(), memberOfGroup.getID())) {
189-
LOG.debug("Group {} is allowed to apply AC Tool (allowed groups: {})", memberOfGroup.getID(), ArrayUtils.toString(webConsoleConfig.getAllowedGroups()));
190-
return true;
191-
}
192-
}
193-
}
194-
LOG.debug("Could not get associated user for Sling request");
195-
return false;
196-
} catch (Exception e) {
197-
throw new IllegalStateException("Could not check if user may apply AC Tool configuration: " + e, e);
190+
boundPrincipals = new BoundPrincipals(JackrabbitSession.class.cast(session));
191+
} catch (RepositoryException e) {
192+
throw new ServletException("Could not determine bound principals", e);
198193
}
194+
return boundPrincipals.containsOneOf(Arrays.asList(principalNames));
199195
}
200196

201197
public String getWebConsoleRoot(HttpServletRequest req) {
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package biz.netcentric.cq.tools.actool.ui;
2+
3+
/*-
4+
* #%L
5+
* Access Control Tool Bundle
6+
* %%
7+
* Copyright (C) 2015 - 2024 Cognizant Netcentric
8+
* %%
9+
* All rights reserved. This program and the accompanying materials
10+
* are made available under the terms of the Eclipse Public License v1.0
11+
* which accompanies this distribution, and is available at
12+
* http://www.eclipse.org/legal/epl-v10.html
13+
* #L%
14+
*/
15+
16+
import java.security.Principal;
17+
import java.util.Collection;
18+
import java.util.HashSet;
19+
import java.util.Iterator;
20+
import java.util.Set;
21+
22+
import javax.jcr.RepositoryException;
23+
24+
import org.apache.jackrabbit.api.JackrabbitSession;
25+
import org.apache.jackrabbit.api.security.user.Authorizable;
26+
import org.apache.jackrabbit.api.security.user.AuthorizableTypeException;
27+
import org.apache.jackrabbit.api.security.user.Group;
28+
import org.jetbrains.annotations.NotNull;
29+
import org.slf4j.Logger;
30+
import org.slf4j.LoggerFactory;
31+
32+
33+
/**
34+
* Encapsulates all principals bound to a given session.
35+
* Natively exposed from Oak 1.40 onwards (see OAK-8611).
36+
*/
37+
class BoundPrincipals {
38+
39+
private static final Logger log = LoggerFactory.getLogger(BoundPrincipals.class);
40+
41+
private Set<Principal> boundPrincipals;
42+
43+
BoundPrincipals(@NotNull JackrabbitSession session) throws RepositoryException {
44+
final String userId = session.getUserID();
45+
// newer Oak versions expose bound principals via session attribute (https://issues.apache.org/jira/browse/OAK-9415)
46+
boundPrincipals = (Set<Principal>)session.getAttribute("oak.bound-principals");
47+
if (boundPrincipals == null) {
48+
boundPrincipals = new HashSet<>();
49+
Authorizable authorizable = session.getUserManager().getAuthorizable(userId);
50+
if (authorizable == null) {
51+
throw new AuthorizableTypeException("Could not find authorizable for session's user ID " + userId);
52+
}
53+
boundPrincipals.add(authorizable.getPrincipal());
54+
55+
Iterator<Group> groupIterator = authorizable.memberOf();
56+
while (groupIterator.hasNext()) {
57+
boundPrincipals.add(groupIterator.next().getPrincipal());
58+
}
59+
}
60+
}
61+
62+
public boolean containsOneOf(@NotNull Collection<String> principalNames) {
63+
for (Principal principal : boundPrincipals) {
64+
if (principalNames.contains(principal.getName())) {
65+
return true;
66+
}
67+
}
68+
return false;
69+
}
70+
71+
}

accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/ui/WebConsoleConfigTracker.java

Lines changed: 0 additions & 84 deletions
This file was deleted.

0 commit comments

Comments
 (0)