Skip to content

Commit f28db29

Browse files
committed
Merge branch 'develop' of [email protected]:Netcentric/accesscontroltool.git into develop
2 parents 192bd1b + 892a0c3 commit f28db29

File tree

9 files changed

+161
-157
lines changed

9 files changed

+161
-157
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ There are also some [advanced configuration options](docs/AdvancedFeatures.md) s
5050

5151
# User Interface
5252

53-
There is a Felix Web Console plugin (at `/system/console/actool`) as well as a Touch UI console (at `/mnt/overlay/netcentric/actool/content/overview.html`) to apply configurations and to inspect previous executions of the tool. Additionally there is a [JMX interface](docs/Jmx.md) for some advanced use cases.
53+
There is a [Felix Web Console plugin (at `/system/console/actool`)](docs/ApplyConfig.md#web-console) as well as a [Touch UI console (at `/mnt/overlay/netcentric/actool/content/overview.html`)](docs/ApplyConfig.md#touch-ui) to apply configurations and to inspect previous executions of the tool. Additionally there is a [JMX interface](docs/Jmx.md) for some advanced use cases.
5454

5555
# Applying AC Tool Configurations
5656

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

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,12 @@ public class HistoryUtils {
5858

5959
public static final String HISTORY_NODE_NAME_PREFIX = "history_";
6060
public static final String NODETYPE_NT_UNSTRUCTURED = "nt:unstructured";
61-
public static final String ACHISTORY_ROOT_NODE = "achistory";
62-
public static final String STATISTICS_ROOT_NODE = "var/statistics";
63-
public static final String ACHISTORY_PATH = "/"+ HistoryUtils.STATISTICS_ROOT_NODE + "/" + HistoryUtils.ACHISTORY_ROOT_NODE;
61+
public static final String ACHISTORY_ROOT_NODE_NAME = "achistory";
62+
public static final String STATISTICS_ROOT_NODE_PATH = "/var/statistics";
63+
public static final String ACHISTORY_PATH = HistoryUtils.STATISTICS_ROOT_NODE_PATH + "/" + HistoryUtils.ACHISTORY_ROOT_NODE_NAME;
6464

6565
private static final String AC_ROOT_PATH_IN_APPS = "/apps/netcentric";
66-
public static final String AC_HISTORY_PATH_IN_APPS = AC_ROOT_PATH_IN_APPS + "/" + ACHISTORY_ROOT_NODE;
66+
public static final String AC_HISTORY_PATH_IN_APPS = AC_ROOT_PATH_IN_APPS + "/" + ACHISTORY_ROOT_NODE_NAME;
6767

6868
public static final String PROPERTY_TIMESTAMP = "timestamp";
6969
private static final String PROPERTY_MESSAGES = "messages";
@@ -85,10 +85,8 @@ public class HistoryUtils {
8585

8686
public static Node getAcHistoryRootNode(final Session session)
8787
throws RepositoryException {
88-
final Node rootNode = session.getRootNode();
89-
Node statisticsRootNode = safeGetNode(rootNode, STATISTICS_ROOT_NODE, NODETYPE_NT_UNSTRUCTURED);
90-
Node acHistoryRootNode = safeGetNode(statisticsRootNode, ACHISTORY_ROOT_NODE, "sling:OrderedFolder");
91-
return acHistoryRootNode;
88+
Node statisticsRootNode = JcrUtils.getOrCreateByPath(STATISTICS_ROOT_NODE_PATH, NODETYPE_NT_UNSTRUCTURED, session);
89+
return JcrUtils.getOrAddNode(statisticsRootNode, ACHISTORY_ROOT_NODE_NAME, "sling:OrderedFolder");
9290
}
9391

9492
/**
@@ -139,7 +137,7 @@ public static Node persistHistory(final Session session,
139137
}
140138
name += AcToolExecutionImpl.TRIGGER_SEPARATOR_IN_NODE_NAME + trigger;
141139

142-
Node newHistoryNode = safeGetNode(acHistoryRootNode, name, NODETYPE_NT_UNSTRUCTURED);
140+
Node newHistoryNode = JcrUtils.getOrAddNode(acHistoryRootNode, name, NODETYPE_NT_UNSTRUCTURED);
143141
String path = newHistoryNode.getPath();
144142
setHistoryNodeProperties(newHistoryNode, installLog, trigger);
145143
saveLogs(newHistoryNode, installLog);
@@ -177,17 +175,6 @@ private static boolean isInStrackTracke(StackTraceElement[] stackTrace, String c
177175
return false;
178176
}
179177

180-
private static Node safeGetNode(final Node baseNode, final String name,
181-
final String typeToCreate) throws RepositoryException {
182-
if (!baseNode.hasNode(name)) {
183-
LOG.debug("create node: {}", name);
184-
return baseNode.addNode(name, typeToCreate);
185-
186-
} else {
187-
return baseNode.getNode(name);
188-
}
189-
}
190-
191178
public static void setHistoryNodeProperties(final Node historyNode,
192179
PersistableInstallationLogger installLog, String trigger) throws ValueFormatException,
193180
VersionException, LockException, ConstraintViolationException,

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

Lines changed: 53 additions & 49 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,25 @@
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;
48+
import org.osgi.service.component.annotations.Activate;
4949
import org.osgi.service.component.annotations.Component;
5050
import org.osgi.service.component.annotations.Reference;
5151
import org.osgi.service.component.annotations.ReferencePolicyOption;
52+
import org.osgi.service.metatype.annotations.AttributeDefinition;
53+
import org.osgi.service.metatype.annotations.Designate;
54+
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
5255
import org.slf4j.Logger;
5356
import org.slf4j.LoggerFactory;
5457

@@ -64,6 +67,7 @@
6467
import biz.netcentric.cq.tools.actool.user.UserProcessor;
6568

6669
@Component(service = { AcToolUiService.class })
70+
@Designate(ocd=biz.netcentric.cq.tools.actool.ui.AcToolUiService.Configuration.class)
6771
public class AcToolUiService {
6872

6973
private static final Logger LOG = LoggerFactory.getLogger(AcToolUiService.class);
@@ -88,15 +92,27 @@ public class AcToolUiService {
8892
@Reference(policyOption = ReferencePolicyOption.GREEDY)
8993
AcInstallationServiceInternal acInstallationService;
9094

91-
@Reference(policyOption = ReferencePolicyOption.GREEDY)
92-
private WebConsoleConfigTracker webConsoleConfig;
93-
9495
@Reference(policyOption = ReferencePolicyOption.GREEDY)
9596
private AcHistoryService acHistoryService;
9697

98+
@ObjectClassDefinition(name = "AC Tool UI Service",
99+
description="Service that allows to apply AC Tool configuration and gather status of users/groups and permissions from a Web UI (either Touch UI or Web Console Plugin).")
100+
protected static @interface Configuration {
101+
102+
@AttributeDefinition(name="Read access", description="Principal names allowed to export all users/groups and permissions in the system. Only leveraged for Touch UI but not for Web Console Plugin.")
103+
String[] readAccessPrincipalNames() default { "administrators", "admin" };
104+
105+
@AttributeDefinition(name="Write access", description="Principal names allowed to modify users/groups and permissions in the system via ACTool configuration files. Only leveraged for Touch UI but not for Web Console Plugin.")
106+
String[] writeAccessPrincipalNames() default { "administrators", "admin" };
107+
}
108+
97109
private final Map<String, String> countryCodePerName;
98110

99-
public AcToolUiService() {
111+
private final Configuration config;
112+
113+
@Activate
114+
public AcToolUiService(Configuration config) {
115+
this.config = config;
100116
countryCodePerName = new HashMap<>();
101117
for (String iso : Locale.getISOCountries()) {
102118
Locale l = new Locale(Locale.ENGLISH.getLanguage(), iso);
@@ -108,16 +124,17 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp, String pa
108124
throws ServletException, IOException {
109125

110126
if (req.getRequestURI().endsWith(SUFFIX_DUMP_YAML)) {
111-
callWhenAuthorized(req, resp, this::streamDumpToResponse);
127+
callWhenReadAccessGranted(req, resp, this::streamDumpToResponse);
112128
} else if (req.getRequestURI().endsWith(SUFFIX_USERS_CSV)) {
113-
callWhenAuthorized(req, resp, this::streamUsersCsvToResponse);
129+
callWhenReadAccessGranted(req, resp, this::streamUsersCsvToResponse);
114130
} else {
131+
// everyone is allows to see the UI in general
115132
renderUi(req, resp, path, isTouchUi);
116133
}
117134
}
118135

119-
private void callWhenAuthorized(HttpServletRequest req, HttpServletResponse resp, Consumer<HttpServletResponse> responseConsumer) throws IOException {
120-
if (!hasAccessToFelixWebConsole(req)) {
136+
private void callWhenReadAccessGranted(HttpServletRequest req, HttpServletResponse resp, Consumer<HttpServletResponse> responseConsumer) throws IOException, ServletException {
137+
if (!isOneOfPrincipalNamesBound(req, config.readAccessPrincipalNames())) {
121138
resp.sendError(HttpServletResponse.SC_FORBIDDEN, "You do not have sufficent permissions to export users/groups/permissions");
122139
return;
123140
}
@@ -127,12 +144,13 @@ private void callWhenAuthorized(HttpServletRequest req, HttpServletResponse resp
127144
throw e.getCause();
128145
}
129146
}
147+
130148
@SuppressWarnings(/* SonarCloud false positive */ {
131149
"javasecurity:S5131" /* response is sent as text/plain, it's not interpreted */,
132150
"javasecurity:S5145" /* logging the path is fine */ })
133151
protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws IOException, ServletException {
134152

135-
if (!hasAccessToFelixWebConsole(req)) {
153+
if (!isOneOfPrincipalNamesBound(req, config.writeAccessPrincipalNames())) {
136154
resp.sendError(HttpServletResponse.SC_FORBIDDEN, "You do not have sufficent permissions to apply the configuration");
137155
return;
138156
}
@@ -157,45 +175,31 @@ protected void doPost(final HttpServletRequest req, final HttpServletResponse re
157175
}
158176

159177
/**
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>.
178+
* 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
161179
* @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
180+
* @param principalNames the principal names to check against
181+
* @return {@code true} if the session bound to the given request is bound to any of the given principal names
182+
* @throws ServletException
183+
* @throws RepositoryException
163184
*/
164-
private boolean hasAccessToFelixWebConsole(HttpServletRequest req) {
165-
185+
private boolean isOneOfPrincipalNamesBound(HttpServletRequest req, String[] principalNames) throws ServletException {
166186
if (!(req instanceof SlingHttpServletRequest)) {
167187
// outside Sling this is only called by the Felix Web Console, which has its own security layer
168188
LOG.debug("Outside Sling no additional security checks are performed!");
169189
return true;
170190
}
171-
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();
191+
Session session = SlingHttpServletRequest.class.cast(req).getResourceResolver().adaptTo(Session.class);
192+
return isOneOfPrincipalNamesBound(JackrabbitSession.class.cast(session), principalNames);
193+
}
185194

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);
195+
private boolean isOneOfPrincipalNamesBound(JackrabbitSession session, String[] principalNames) throws ServletException {
196+
BoundPrincipals boundPrincipals;
197+
try {
198+
boundPrincipals = new BoundPrincipals(JackrabbitSession.class.cast(session));
199+
} catch (RepositoryException e) {
200+
throw new ServletException("Could not determine bound principals", e);
198201
}
202+
return boundPrincipals.containsOneOf(Arrays.asList(principalNames));
199203
}
200204

201205
public String getWebConsoleRoot(HttpServletRequest req) {
@@ -210,8 +214,8 @@ private void renderUi(HttpServletRequest req, HttpServletResponse resp, String p
210214

211215
printCss(isTouchUi, writer);
212216
printVersion(writer);
213-
printImportSection(writer, reqParams, path, isTouchUi, getWebConsoleRoot(req));
214-
printExportSection(writer, reqParams, path, isTouchUi, getWebConsoleRoot(req));
217+
printImportSection(writer, reqParams, path, isTouchUi, getWebConsoleRoot(req), isOneOfPrincipalNamesBound(req, config.writeAccessPrincipalNames()));
218+
printExportSection(writer, reqParams, path, isTouchUi, getWebConsoleRoot(req), isOneOfPrincipalNamesBound(req, config.readAccessPrincipalNames()));
215219

216220
try {
217221
printInstallationLogsSection(writer, reqParams, isTouchUi);
@@ -425,7 +429,7 @@ private String getExecutionStatusHtml(AcToolExecution acToolExecution) {
425429
return acToolExecution.isSuccess() ? "SUCCESS" : "<span style='color:red;font-weight: bold;'>FAILED</span>";
426430
}
427431

428-
private void printImportSection(final HtmlWriter writer, RequestParameters reqParams, String path, boolean isTouchUI, String webConsoleRoot) throws IOException {
432+
private void printImportSection(final HtmlWriter writer, RequestParameters reqParams, String path, boolean isTouchUI, String webConsoleRoot, boolean hasWritePermission) throws IOException {
429433

430434
writer.print("<form id='acForm' action='" + path + "'>");
431435
writer.openTable("acFormTable");
@@ -473,7 +477,7 @@ private void printImportSection(final HtmlWriter writer, RequestParameters reqPa
473477
writer.openTd();
474478
String onClick = "var as=$('#applySpinner');as.show(); var b=$('#applyButton');b.prop('disabled', true); oldL = b.text();b.text(' Applying AC Tool Configuration... ');var f=$('#acForm');var fd=f.serialize();$.post(f.attr('action'), fd).done(function(text){alert(text)}).fail(function(xhr){alert(xhr.status===403?'Permission Denied':'Config could not be applied - check log for errors')}).always(function(text) { "
475479
+ "as.hide();b.text(oldL);b.prop('disabled', false);location.href='" + PAGE_NAME + "?'+fd; });return false";
476-
writer.println("<button " + getCoralButtonAtts(isTouchUI) + " id='applyButton' onclick=\"" + onClick + "\"> Apply AC Tool Configuration </button>");
480+
writer.println("<button " + getCoralButtonAtts(isTouchUI) + (!hasWritePermission ? " disabled" : "") + " id='applyButton' onclick=\"" + onClick + "\"> Apply AC Tool Configuration </button>");
477481
writer.closeTd();
478482
writer.openTd();
479483
writer.println("<div id='applySpinner' style='display:none' class='spinner'><div></div><div></div><div></div></div>");
@@ -487,15 +491,15 @@ private void printImportSection(final HtmlWriter writer, RequestParameters reqPa
487491
}
488492

489493

490-
private void printExportSection(final HtmlWriter writer, RequestParameters reqParams, String path, boolean isTouchUI, String webConsoleRoot) throws IOException {
494+
private void printExportSection(final HtmlWriter writer, RequestParameters reqParams, String path, boolean isTouchUI, String webConsoleRoot, boolean hasReadPermission) throws IOException {
491495
writer.openTable("acExportTable");
492496
writer.tableHeader("Export", 2);
493497
writer.tr();
494498
writer.openTd();
495499
writer.print("Export in AC Tool YAML format. This includes groups and permissions (in form of ACEs).");
496500
writer.closeTd();
497501
writer.openTd();
498-
writer.println("<button " + getCoralButtonAtts(isTouchUI) + " id='downloadDumpButton' onclick=\"window.open('" + path + ".html/"
502+
writer.println("<button " + getCoralButtonAtts(isTouchUI) + (!hasReadPermission ? " disabled" : "") + " id='downloadDumpButton' onclick=\"window.open('" + path + ".html/"
499503
+ SUFFIX_DUMP_YAML + "', '_blank');return false;\"> Download YAML </button>");
500504
writer.closeTd();
501505
writer.closeTr();
@@ -504,7 +508,7 @@ private void printExportSection(final HtmlWriter writer, RequestParameters reqPa
504508
writer.print("Export Users in Admin Console CSV format. This includes non-system users, their profiles and their direct group memberships.");
505509
writer.closeTd();
506510
writer.openTd();
507-
writer.println("<button " + getCoralButtonAtts(isTouchUI) + " id='downloadCsvButton' onclick=\"window.open('" + path + ".html/"
511+
writer.println("<button " + getCoralButtonAtts(isTouchUI) + (!hasReadPermission ? " disabled" : "") + " id='downloadCsvButton' onclick=\"window.open('" + path + ".html/"
508512
+ SUFFIX_USERS_CSV + "', '_blank');return false;\"> Download CSV </button>");
509513
writer.closeTd();
510514
writer.closeTr();

0 commit comments

Comments
 (0)