From 03b5b06a4b28afda1ef854a5941ce29b22939688 Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Thu, 2 May 2024 19:37:39 +0200 Subject: [PATCH] Add functionality to export users in CSV format This format is compatible with https://helpx.adobe.com/enterprise/using/bulk-upload-users.html#csv-format Fix Web Console deployment with sling-m-p This closes #703 This closes #443 This closes #707 --- .../helper/UncheckedRepositoryException.java | 46 +++ .../tools/actool/ui/AcToolTouchUiServlet.java | 42 --- .../cq/tools/actool/ui/AcToolUiService.java | 262 +++++++++++++++--- .../cq/tools/actool/user/UserProcessor.java | 18 ++ .../actool/user/impl/UserProcessorImpl.java | 61 ++++ pom.xml | 7 +- 6 files changed, 358 insertions(+), 78 deletions(-) create mode 100644 accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/helper/UncheckedRepositoryException.java create mode 100644 accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/user/UserProcessor.java create mode 100644 accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/user/impl/UserProcessorImpl.java diff --git a/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/helper/UncheckedRepositoryException.java b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/helper/UncheckedRepositoryException.java new file mode 100644 index 000000000..b5726e925 --- /dev/null +++ b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/helper/UncheckedRepositoryException.java @@ -0,0 +1,46 @@ +/* + * (C) Copyright 2024 Cognizant Netcentric. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package biz.netcentric.cq.tools.actool.helper; + +import java.util.Objects; + +import javax.jcr.RepositoryException; + +/** + * Wraps a {@link RepositoryException} with an unchecked exception. + * This is useful for usage within lambdas. + * + */ +public class UncheckedRepositoryException extends RuntimeException { + + private static final long serialVersionUID = 2727436608772501551L; + + /** + * Constructs an instance of this class. + * + * @param cause + * the {@code RepositoryException} + * + * @throws NullPointerException + * if the cause is {@code null} + */ + public UncheckedRepositoryException(RepositoryException cause) { + super(Objects.requireNonNull(cause)); + } + + /** + * Returns the cause of this exception. + * + * @return the {@code RepositoryException} which is the cause of this exception. + */ + @Override + public synchronized RepositoryException getCause() { + return (RepositoryException) super.getCause(); + } +} diff --git a/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/ui/AcToolTouchUiServlet.java b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/ui/AcToolTouchUiServlet.java index f25778a21..ffdf8da03 100644 --- a/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/ui/AcToolTouchUiServlet.java +++ b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/ui/AcToolTouchUiServlet.java @@ -37,66 +37,24 @@ public class AcToolTouchUiServlet extends SlingAllMethodsServlet { private static final Logger LOG = LoggerFactory.getLogger(AcToolTouchUiServlet.class); - @Reference(policyOption = ReferencePolicyOption.GREEDY) - private WebConsoleConfigTracker webConsoleConfigTracker; - @Reference(policyOption = ReferencePolicyOption.GREEDY) private AcToolUiService acToolUiService; @Override protected void doGet(SlingHttpServletRequest req, SlingHttpServletResponse resp) throws ServletException, IOException { - if (StringUtils.isBlank(req.getRequestPathInfo().getSuffix())) { String targetUrl = req.getResourceResolver().resolve(req.getPathInfo()).getPath() + ".html/" + AcToolUiService.PAGE_NAME; resp.getWriter().println(""); return; } - acToolUiService.doGet(req, resp, req.getRequestPathInfo().getResourcePath(), true); - } @Override protected void doPost(SlingHttpServletRequest req, SlingHttpServletResponse resp) throws ServletException, IOException { - - if (!mayApplyConfig(req.getResourceResolver().adaptTo(User.class))) { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "You do not have sufficent permissions to apply the configuration"); - return; - } - acToolUiService.doPost(req, resp); LOG.debug("Applied AC tool config via Touch UI by user {}", req.getUserPrincipal()); } - private boolean mayApplyConfig(User requestUser) { - - try { - - if (requestUser != null) { - if (StringUtils.equals(requestUser.getID(), "admin")) { - LOG.debug("Admin user is allowed to apply AC Tool"); - return true; - } - - if (ArrayUtils.contains(webConsoleConfigTracker.getAllowedUsers(), requestUser.getID())) { - LOG.debug("User {} is allowed to apply AC Tool (allowed users: {})", requestUser.getID(), ArrayUtils.toString(webConsoleConfigTracker.getAllowedUsers())); - return true; - } - - Iterator memberOfIt = requestUser.memberOf(); - - while (memberOfIt.hasNext()) { - Group memberOfGroup = memberOfIt.next(); - if (ArrayUtils.contains(webConsoleConfigTracker.getAllowedGroups(), memberOfGroup.getID())) { - LOG.debug("Group {} is allowed to apply AC Tool (allowed groups: {})", memberOfGroup.getID(), ArrayUtils.toString(webConsoleConfigTracker.getAllowedGroups())); - return true; - } - } - } - return false; - } catch (Exception e) { - throw new IllegalStateException("Could not check if user may apply AC Tool configuration: " + e, e); - } - } } diff --git a/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/ui/AcToolUiService.java b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/ui/AcToolUiService.java index 8dc42827d..17422d60d 100644 --- a/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/ui/AcToolUiService.java +++ b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/ui/AcToolUiService.java @@ -4,18 +4,33 @@ import java.io.IOException; import java.io.PrintWriter; +import java.io.UncheckedIOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import javax.jcr.RepositoryException; +import javax.jcr.Value; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.felix.webconsole.WebConsoleConstants; +import org.apache.jackrabbit.api.security.user.Group; +import org.apache.jackrabbit.api.security.user.User; +import org.apache.jackrabbit.oak.spi.security.principal.EveryonePrincipal; +import org.apache.sling.api.SlingHttpServletRequest; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.ReferencePolicyOption; @@ -26,10 +41,12 @@ import biz.netcentric.cq.tools.actool.api.InstallationLog; import biz.netcentric.cq.tools.actool.api.InstallationResult; import biz.netcentric.cq.tools.actool.dumpservice.ConfigDumpService; +import biz.netcentric.cq.tools.actool.helper.UncheckedRepositoryException; import biz.netcentric.cq.tools.actool.history.AcHistoryService; import biz.netcentric.cq.tools.actool.history.AcToolExecution; import biz.netcentric.cq.tools.actool.impl.AcInstallationServiceImpl; import biz.netcentric.cq.tools.actool.impl.AcInstallationServiceInternal; +import biz.netcentric.cq.tools.actool.user.UserProcessor; @Component(service = { AcToolUiService.class }) public class AcToolUiService { @@ -44,11 +61,15 @@ public class AcToolUiService { public static final String PAGE_NAME = "actool"; - static final String PATH_SEGMENT_DUMP = "dump.yaml"; + static final String SUFFIX_DUMP_YAML = "dump.yaml"; + static final String SUFFIX_USERS_CSV = "users.csv"; @Reference(policyOption = ReferencePolicyOption.GREEDY) private ConfigDumpService dumpService; + @Reference(policyOption = ReferencePolicyOption.GREEDY) + private UserProcessor userProcessor; + @Reference(policyOption = ReferencePolicyOption.GREEDY) AcInstallationServiceInternal acInstallationService; @@ -58,21 +79,48 @@ public class AcToolUiService { @Reference(policyOption = ReferencePolicyOption.GREEDY) private AcHistoryService acHistoryService; - protected void doGet(HttpServletRequest req, HttpServletResponse resp, String postPath, boolean isTouchUi) + private final Map countryCodePerName; + + public AcToolUiService() { + countryCodePerName = new HashMap<>(); + for (String iso : Locale.getISOCountries()) { + Locale l = new Locale(Locale.ENGLISH.getLanguage(), iso); + countryCodePerName.put(l.getDisplayCountry(), iso); + } + } + + protected void doGet(HttpServletRequest req, HttpServletResponse resp, String path, boolean isTouchUi) throws ServletException, IOException { - if (req.getRequestURI().endsWith(PATH_SEGMENT_DUMP)) { - streamDumpToResponse(resp); + if (req.getRequestURI().endsWith(SUFFIX_DUMP_YAML)) { + callWhenAuthorized(req, resp, this::streamDumpToResponse); + } else if (req.getRequestURI().endsWith(SUFFIX_USERS_CSV)) { + callWhenAuthorized(req, resp, this::streamUsersCsvToResponse); } else { - renderUi(req, resp, postPath, isTouchUi); + renderUi(req, resp, path, isTouchUi); } } + private void callWhenAuthorized(HttpServletRequest req, HttpServletResponse resp, Consumer responseConsumer) throws IOException { + if (!hasAccessToFelixWebConsole(req)) { + resp.sendError(HttpServletResponse.SC_FORBIDDEN, "You do not have sufficent permissions to export users/groups/permissions"); + return; + } + try { + responseConsumer.accept(resp); + } catch (UncheckedIOException e) { + throw e.getCause(); + } + } @SuppressWarnings(/* SonarCloud false positive */ { "javasecurity:S5131" /* response is sent as text/plain, it's not interpreted */, "javasecurity:S5145" /* logging the path is fine */ }) protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws IOException, ServletException { + if (!hasAccessToFelixWebConsole(req)) { + resp.sendError(HttpServletResponse.SC_FORBIDDEN, "You do not have sufficent permissions to apply the configuration"); + return; + } RequestParameters reqParams = RequestParameters.fromRequest(req, acInstallationService); LOG.info("Received POST request to apply AC Tool config with configurationRootPath={} basePaths={}", reqParams.configurationRootPath, reqParams.basePaths); @@ -93,18 +141,64 @@ protected void doPost(final HttpServletRequest req, final HttpServletResponse re } } + /** + * Replicates the logic of the https://sling.apache.org/documentation/bundles/web-console-extensions.html#authentication-handling. + * @param req the request + * @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 + */ + private boolean hasAccessToFelixWebConsole(HttpServletRequest req) { + + if (!(req instanceof SlingHttpServletRequest)) { + // outside Sling this is only called by the Felix Web Console, which has its own security layer + LOG.debug("Outside Sling no additional security checks are performed!"); + return true; + } + try { + User requestUser = SlingHttpServletRequest.class.cast(req).getResourceResolver().adaptTo(User.class); + if (requestUser != null) { + if (StringUtils.equals(requestUser.getID(), "admin")) { + LOG.debug("Admin user is allowed to apply AC Tool"); + return true; + } + + if (ArrayUtils.contains(webConsoleConfig.getAllowedUsers(), requestUser.getID())) { + LOG.debug("User {} is allowed to apply AC Tool (allowed users: {})", requestUser.getID(), ArrayUtils.toString(webConsoleConfig.getAllowedUsers())); + return true; + } + + Iterator memberOfIt = requestUser.memberOf(); + + while (memberOfIt.hasNext()) { + Group memberOfGroup = memberOfIt.next(); + if (ArrayUtils.contains(webConsoleConfig.getAllowedGroups(), memberOfGroup.getID())) { + LOG.debug("Group {} is allowed to apply AC Tool (allowed groups: {})", memberOfGroup.getID(), ArrayUtils.toString(webConsoleConfig.getAllowedGroups())); + return true; + } + } + } + LOG.debug("Could not get associated user for Sling request"); + return false; + } catch (Exception e) { + throw new IllegalStateException("Could not check if user may apply AC Tool configuration: " + e, e); + } + } + public String getWebConsoleRoot(HttpServletRequest req) { return (String) req.getAttribute(WebConsoleConstants.ATTR_APP_ROOT); } - - private void renderUi(HttpServletRequest req, HttpServletResponse resp, String postPath, boolean isTouchUi) throws IOException { + + private void renderUi(HttpServletRequest req, HttpServletResponse resp, String path, boolean isTouchUi) throws IOException { RequestParameters reqParams = RequestParameters.fromRequest(req, acInstallationService); final PrintWriter out = resp.getWriter(); + final HtmlWriter writer = new HtmlWriter(out, isTouchUi); + + printCss(isTouchUi, writer); + printVersion(writer); + printImportSection(writer, reqParams, path, isTouchUi, getWebConsoleRoot(req)); + printExportSection(writer, reqParams, path, isTouchUi, getWebConsoleRoot(req)); - printForm(out, reqParams, postPath, isTouchUi, getWebConsoleRoot(req)); - - printInstallationLogsSection(out, reqParams, isTouchUi); + printInstallationLogsSection(writer, reqParams, isTouchUi); if(!isTouchUi) { String jmxUrl = getWebConsoleRoot(req) + "/jmx/" @@ -113,20 +207,112 @@ private void renderUi(HttpServletRequest req, HttpServletResponse resp, String p } } - void streamDumpToResponse(final HttpServletResponse resp) throws IOException { + void streamDumpToResponse(final HttpServletResponse resp) { resp.setContentType("application/x-yaml"); resp.setHeader("Content-Disposition", "inline; filename=\"actool-dump.yaml\""); String dumpAsString = dumpService.getCompletePrincipalBasedDumpsAsString(); - PrintWriter out = resp.getWriter(); - out.println(dumpAsString); - out.flush(); + try { + PrintWriter out; + out = resp.getWriter(); + out.println(dumpAsString); + out.flush(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } } - private void printInstallationLogsSection(PrintWriter out, RequestParameters reqParams, boolean isTouchUi) { + private void streamUsersCsvToResponse(HttpServletResponse resp) { + resp.setContentType("text/csv"); + resp.setHeader("Content-Disposition", "inline; filename=\"users.csv\""); + try { + PrintWriter out = resp.getWriter(); + out.println("Identity Type,Username,Domain,Email,First Name,Last Name,Country Code,ID,Product Configurations,Admin Roles,Product Configurations Administered,User Groups,User Groups Administered,Products Administered,Developer Access"); + try { + userProcessor.forEachNonSystemUser(u -> { + try { + out.println(String.format(",%s,,%s,%s,%s,%s,,,,,%s", u.getID(), + escapeAsCsvValue(getUserPropertyAsString(u, "profile/email")), + escapeAsCsvValue(getUserPropertyAsString(u, "profile/givenName")), + escapeAsCsvValue(getUserPropertyAsString(u, "profile/familyName")), + escapeAsCsvValue(getCountyCodeFromName(getUserPropertyAsString(u, "profile/country"))), + escapeAsCsvValue(getDeclaredMemberOfAsStrings(u)))); + } catch (RepositoryException e) { + throw new UncheckedRepositoryException(e); + } + }); + } catch (UncheckedRepositoryException|RepositoryException e) { + throw new IOException("Could not access users or their properties", e); + } + out.println(); + out.flush(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private String getCountyCodeFromName(String countryName) { + String countryCode = countryCodePerName.get(countryName); + return countryCode != null ? countryCode : ""; + } + + private static String escapeAsCsvValue(String text) { + if (text.contains(",")) { + return "\"" + text.replace("\"", "\"\"") + "\""; + } else { + return text; + } + } + + private static String getDeclaredMemberOfAsStrings(User user) throws RepositoryException { + List groupNames = new LinkedList<>(); + try { + user.declaredMemberOf().forEachRemaining(g -> { + try { + if (!EveryonePrincipal.NAME.equals(g.getID())) { + groupNames.add(g.getID()); + } + } catch (RepositoryException e) { + throw new UncheckedRepositoryException(e); + } + }); + } catch (UncheckedRepositoryException e) { + throw e.getCause(); + } + return String.join(",", groupNames); + } + + private static String getUserPropertyAsString(User user, String propertyName) throws RepositoryException { + Value[] values = user.getProperty(propertyName); + if (values == null) { + return ""; + } + try { + return Arrays.stream(values).map(t -> { + try { + return t.getString(); + } catch (RepositoryException e) { + throw new UncheckedRepositoryException(new RepositoryException("Could not convert property \"" + propertyName + "\" of user \"" + user + " to string", e)); + } + }).collect(Collectors.joining(", ")); + } catch (UncheckedRepositoryException e) { + throw e.getCause(); + } + } + + private void printVersion(HtmlWriter writer) { + writer.openTable("version"); + writer.tableHeader("Version", 1); + writer.tr(); + writer.td("v" + acInstallationService.getVersion()); + writer.closeTd(); + writer.closeTr(); + writer.closeTable(); + } + + private void printInstallationLogsSection(HtmlWriter writer, RequestParameters reqParams, boolean isTouchUi) { List acToolExecutions = acHistoryService.getAcToolExecutions(); - final HtmlWriter writer = new HtmlWriter(out, isTouchUi); writer.openTable("previousLogs"); writer.tableHeader("Previous Logs", 5); @@ -208,14 +394,11 @@ private String getExecutionStatusHtml(AcToolExecution acToolExecution) { return acToolExecution.isSuccess() ? "SUCCESS" : "FAILED"; } - private void printForm(final PrintWriter out, RequestParameters reqParams, String postPath, boolean isTouchUI, String webConsoleRoot) throws IOException { - final HtmlWriter writer = new HtmlWriter(out, isTouchUI); + private void printImportSection(final HtmlWriter writer, RequestParameters reqParams, String path, boolean isTouchUI, String webConsoleRoot) throws IOException { - printCss(isTouchUI, writer); - - writer.print("
"); + writer.print(""); writer.openTable("acFormTable"); - writer.tableHeader("AC Tool v" + acInstallationService.getVersion(), 3); + writer.tableHeader("Import", 2); writer.tr(); writer.openTd(); @@ -237,10 +420,6 @@ private void printForm(final PrintWriter out, RequestParameters reqParams, Strin + (reqParams.applyOnlyIfChanged ? " checked='checked'" : "") + " /> apply only if config changed"); writer.closeTd(); - writer.openTd(); - writer.println(""); - writer.closeTd(); writer.closeTr(); @@ -257,10 +436,6 @@ private void printForm(final PrintWriter out, RequestParameters reqParams, Strin writer.println("' class='input' size='70'>"); writer.closeTd(); - writer.openTd(); - writer.println(""); - writer.closeTd(); - writer.closeTr(); writer.tr(); @@ -272,8 +447,6 @@ private void printForm(final PrintWriter out, RequestParameters reqParams, Strin writer.openTd(); writer.println(""); - writer.openTd(); - writer.println(""); writer.closeTd(); writer.closeTr(); @@ -283,6 +456,31 @@ private void printForm(final PrintWriter out, RequestParameters reqParams, Strin } + private void printExportSection(final HtmlWriter writer, RequestParameters reqParams, String path, boolean isTouchUI, String webConsoleRoot) throws IOException { + writer.openTable("acExportTable"); + writer.tableHeader("Export", 2); + writer.tr(); + writer.openTd(); + writer.print("Export in AC Tool YAML format. This includes groups and permissions (in form of ACEs)."); + writer.closeTd(); + writer.openTd(); + writer.println(""); + writer.closeTd(); + writer.closeTr(); + writer.tr(); + writer.openTd(); + writer.print("Export Users in Admin Console CSV format. This includes non-system users, their profiles and their direct group memberships."); + writer.closeTd(); + writer.openTd(); + writer.println(""); + writer.closeTd(); + writer.closeTr(); + + writer.closeTable(); + } + private void printCss(boolean isTouchUI, final HtmlWriter writer) { StringBuilder css = new StringBuilder(); // spinner css diff --git a/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/user/UserProcessor.java b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/user/UserProcessor.java new file mode 100644 index 000000000..61eb4d553 --- /dev/null +++ b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/user/UserProcessor.java @@ -0,0 +1,18 @@ +package biz.netcentric.cq.tools.actool.user; + +import java.util.function.Consumer; + +import javax.jcr.RepositoryException; + +import org.apache.jackrabbit.api.security.user.User; + +public interface UserProcessor { + + /** + * Calls the provided functional interface for each non-system enabled user on the system. + * @param userConsumer the functional interface to call for each user + * @throws RepositoryException + */ + void forEachNonSystemUser(Consumer userConsumer) throws RepositoryException; + +} diff --git a/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/user/impl/UserProcessorImpl.java b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/user/impl/UserProcessorImpl.java new file mode 100644 index 000000000..816915e30 --- /dev/null +++ b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/user/impl/UserProcessorImpl.java @@ -0,0 +1,61 @@ +package biz.netcentric.cq.tools.actool.user.impl; + +import java.util.Iterator; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Consumer; +import java.util.stream.StreamSupport; + +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.jackrabbit.api.JackrabbitSession; +import org.apache.jackrabbit.api.security.user.Authorizable; +import org.apache.jackrabbit.api.security.user.Query; +import org.apache.jackrabbit.api.security.user.QueryBuilder; +import org.apache.jackrabbit.api.security.user.QueryBuilder.Direction; +import org.apache.jackrabbit.api.security.user.User; +import org.apache.sling.jcr.api.SlingRepository; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferencePolicyOption; + +import biz.netcentric.cq.tools.actool.user.UserProcessor; + +@Component +public class UserProcessorImpl implements UserProcessor { + + @Reference(policyOption = ReferencePolicyOption.GREEDY) + private SlingRepository repository; + + @Override + public void forEachNonSystemUser(Consumer userConsumer) throws RepositoryException { + Session session = null; + try { + session = repository.loginService(null, null); + if (!(session instanceof JackrabbitSession)) { + throw new IllegalStateException("Session is no instance of JackrabbitSession"); + } + JackrabbitSession jrSession = JackrabbitSession.class.cast(session); + Iterator iterator = jrSession.getUserManager().findAuthorizables(new Query() { + public void build(QueryBuilder builder) { + // only enabled users + builder.setCondition(builder.not(builder.exists("@rep:disabled"))); + // is is not possible to add a condition for jcr:primaryType with value type name + builder.setSortOrder("@name", Direction.ASCENDING); + builder.setSelector(User.class); + } + }); + StreamSupport.stream( + Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), + false) + .map(User.class::cast) + .filter(u -> !u.isSystemUser()) + .forEach(userConsumer); + } finally { + if (session != null) { + session.logout(); + } + } + } +} diff --git a/pom.xml b/pom.xml index 33bc00f80..7c9b35e43 100644 --- a/pom.xml +++ b/pom.xml @@ -295,6 +295,8 @@ ${crx.username} ${crx.password} + http://${crx.host}:${crx.port} + /system/console @@ -452,7 +454,7 @@ 3.6.1 - ${maven.compiler.release} + 11 @@ -537,9 +539,6 @@ - - http://${crx.host}:${crx.port}/apps/netcentric/actool/install -