You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by dk...@apache.org on 2019/04/26 17:46:14 UTC

[sling-org-apache-sling-serviceuser-webconsole] branch master updated (ee3e1c0 -> 0d65809)

This is an automated email from the ASF dual-hosted git repository.

dklco pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceuser-webconsole.git.


    from ee3e1c0  Updating badges for org-apache-sling-serviceuser-webconsole
     new da9192d  Fixing SLING-8376 - Closing the administrative resource resolver (if used) and cleaning up SonarQube code quality violations
     new 2a8c0ea  Fixing SLING-7415 - Removing unnecessarily exposed package
     new 0d65809  Merge branch 'master' of git@github.com:apache/sling-org-apache-sling-serviceuser-webconsole.git

The 16 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../impl/ServiceUserWebConsolePlugin.java          | 1728 ++++++++++----------
 .../sling/serviceuser/webconsole/package-info.java |   22 -
 2 files changed, 884 insertions(+), 866 deletions(-)
 delete mode 100644 src/main/java/org/apache/sling/serviceuser/webconsole/package-info.java


[sling-org-apache-sling-serviceuser-webconsole] 11/16: trivial: added license header to *.md files

Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceuser-webconsole.git

commit 6d9a7253560be803147164c05250a0cf827b79cb
Author: Radu Cotescu <co...@adobe.com>
AuthorDate: Thu Sep 20 11:09:08 2018 +0200

    trivial: added license header to *.md files
---
 CODE_OF_CONDUCT.md | 18 ++++++++++++++++++
 CONTRIBUTING.md    | 18 ++++++++++++++++++
 2 files changed, 36 insertions(+)

diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 52f21cb..0fa18e5 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -1,3 +1,21 @@
+<!--/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/-->
 Apache Software Foundation Code of Conduct
 ====
 
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ca36072..ac82a1a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,3 +1,21 @@
+<!--/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/-->
 Contributing
 ====
 


[sling-org-apache-sling-serviceuser-webconsole] 03/16: [maven-release-plugin] prepare release org.apache.sling.serviceuser.webconsole-1.0.0

Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceuser-webconsole.git

commit 448779a426940c04d5ca80ee928da940942c3124
Author: Dan Klco <da...@gmail.com>
AuthorDate: Fri Jan 12 16:12:18 2018 -0500

    [maven-release-plugin] prepare release org.apache.sling.serviceuser.webconsole-1.0.0
---
 pom.xml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/pom.xml b/pom.xml
index 616decd..bc48140 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,7 +29,7 @@
 
     <artifactId>org.apache.sling.serviceuser.webconsole</artifactId>
     <packaging>bundle</packaging>
-    <version>1.0.0-SNAPSHOT</version>
+    <version>1.0.0</version>
 
     <name>Apache Sling Service User Web Console</name>
     <description>
@@ -40,7 +40,7 @@
         <connection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceuser-webconsole.git</connection>
         <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceuser-webconsole.git</developerConnection>
         <url>https://gitbox.apache.org/repos/asf?p=sling-org-apache-sling-serviceuser-webconsole.git</url>
-      <tag>HEAD</tag>
+      <tag>org.apache.sling.serviceuser.webconsole-1.0.0</tag>
   </scm>
 
     <build>


[sling-org-apache-sling-serviceuser-webconsole] 06/16: [maven-release-plugin] prepare release org.apache.sling.serviceuser.webconsole-1.0.0

Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceuser-webconsole.git

commit 41087a58f1be59373020a4f5273cda7474b9e6c9
Author: Dan Klco <da...@gmail.com>
AuthorDate: Fri Jan 12 17:55:34 2018 -0500

    [maven-release-plugin] prepare release org.apache.sling.serviceuser.webconsole-1.0.0
---
 pom.xml | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/pom.xml b/pom.xml
index 97e097e..a5a02a2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -9,8 +9,7 @@
 	License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 
 	OF ANY KIND, either express or implied. See the License for the specific 
 	language governing permissions and limitations under the License. -->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 
 	<modelVersion>4.0.0</modelVersion>
 	<parent>
@@ -22,7 +21,7 @@
 
 	<artifactId>org.apache.sling.serviceuser.webconsole</artifactId>
 	<packaging>bundle</packaging>
-	<version>1.0.0-SNAPSHOT</version>
+	<version>1.0.0</version>
 
 	<name>Apache Sling Service User Web Console</name>
 	<description>
@@ -33,7 +32,7 @@
 		<connection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceuser-webconsole.git</connection>
 		<developerConnection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceuser-webconsole.git</developerConnection>
 		<url>https://gitbox.apache.org/repos/asf?p=sling-org-apache-sling-serviceuser-webconsole.git</url>
-		<tag>HEAD</tag>
+		<tag>org.apache.sling.serviceuser.webconsole-1.0.0</tag>
 	</scm>
 
 	<build>


[sling-org-apache-sling-serviceuser-webconsole] 15/16: Fixing SLING-7415 - Removing unnecessarily exposed package

Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceuser-webconsole.git

commit 2a8c0eaaa23d63a8323304a69c303a24ba1dbd7d
Author: Dan Klco <dk...@apache.org>
AuthorDate: Fri Apr 26 13:45:46 2019 -0400

    Fixing SLING-7415 - Removing unnecessarily exposed package
---
 .../sling/serviceuser/webconsole/package-info.java | 22 ----------------------
 1 file changed, 22 deletions(-)

diff --git a/src/main/java/org/apache/sling/serviceuser/webconsole/package-info.java b/src/main/java/org/apache/sling/serviceuser/webconsole/package-info.java
deleted file mode 100644
index 452b0ba..0000000
--- a/src/main/java/org/apache/sling/serviceuser/webconsole/package-info.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-@org.osgi.annotation.versioning.Version("1.0.0")
-package org.apache.sling.serviceuser.webconsole;
-


[sling-org-apache-sling-serviceuser-webconsole] 14/16: Fixing SLING-8376 - Closing the administrative resource resolver (if used) and cleaning up SonarQube code quality violations

Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceuser-webconsole.git

commit da9192d93425be677b94c0b9c7d257e332136569
Author: Dan Klco <dk...@apache.org>
AuthorDate: Fri Apr 26 13:45:25 2019 -0400

    Fixing SLING-8376 - Closing the administrative resource resolver (if
    used) and cleaning up SonarQube code quality violations
---
 .../impl/ServiceUserWebConsolePlugin.java          | 1728 ++++++++++----------
 1 file changed, 884 insertions(+), 844 deletions(-)

diff --git a/src/main/java/org/apache/sling/serviceuser/webconsole/impl/ServiceUserWebConsolePlugin.java b/src/main/java/org/apache/sling/serviceuser/webconsole/impl/ServiceUserWebConsolePlugin.java
index 5d952fa..ae5eba6 100644
--- a/src/main/java/org/apache/sling/serviceuser/webconsole/impl/ServiceUserWebConsolePlugin.java
+++ b/src/main/java/org/apache/sling/serviceuser/webconsole/impl/ServiceUserWebConsolePlugin.java
@@ -23,6 +23,7 @@ import java.io.PrintWriter;
 import java.lang.reflect.Array;
 import java.net.URL;
 import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
 import java.security.Principal;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -35,10 +36,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 
-import javax.jcr.AccessDeniedException;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
-import javax.jcr.UnsupportedRepositoryOperationException;
 import javax.jcr.nodetype.NodeType;
 import javax.jcr.query.Query;
 import javax.jcr.security.AccessControlEntry;
@@ -91,858 +90,899 @@ import org.slf4j.LoggerFactory;
  * Web console plugin to test configuration resolution.
  */
 @Component(service = Servlet.class, property = {
-		Constants.SERVICE_DESCRIPTION + "=Apache Sling Service User Manager Web Console Plugin",
-		WebConsoleConstants.PLUGIN_LABEL + "=" + ServiceUserWebConsolePlugin.LABEL,
-		WebConsoleConstants.PLUGIN_TITLE + "=" + ServiceUserWebConsolePlugin.TITLE,
-		WebConsoleConstants.PLUGIN_CATEGORY + "=Sling" })
+        Constants.SERVICE_DESCRIPTION + "=Apache Sling Service User Manager Web Console Plugin",
+        WebConsoleConstants.PLUGIN_LABEL + "=" + ServiceUserWebConsolePlugin.LABEL,
+        WebConsoleConstants.PLUGIN_TITLE + "=" + ServiceUserWebConsolePlugin.TITLE,
+        WebConsoleConstants.PLUGIN_CATEGORY + "=Sling" })
 @SuppressWarnings("serial")
 public class ServiceUserWebConsolePlugin extends AbstractWebConsolePlugin {
 
-	public static final String COMPONENT_NAME = "org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended";
-	public static final String LABEL = "serviceusers";
-	public static final String TITLE = "Service Users";
-
-	public static final String PN_ACTION = "action";
-	public static final String PN_ALERT = "alert";
-	public static final String PN_APP_PATH = "appPath";
-	public static final String PN_BUNDLE = "bundle";
-	public static final String PN_NAME = "name";
-	public static final String PN_SUB_SERVICE = "subService";
-	public static final String PN_USER = "user";
-	public static final String PN_USER_PATH = "userPath";
-
-	private static final Logger log = LoggerFactory.getLogger(ServiceUserWebConsolePlugin.class);
-
-	private BundleContext bundleContext;
-
-	@Reference(policyOption = ReferencePolicyOption.GREEDY)
-	private XSSAPI xss;
-
-	@Reference(policyOption = ReferencePolicyOption.GREEDY)
-	private ResourceResolverFactory resolverFactory;
-
-	@Reference
-	private ServiceUserMapper mapper;
-
-	private boolean createOrUpdateMapping(HttpServletRequest request, ResourceResolver resolver) {
-
-		String appPath = getParameter(request, PN_APP_PATH, "");
-
-		Iterator<Resource> configs = resolver.findResources("SELECT * FROM [sling:OsgiConfig] WHERE ISDESCENDANTNODE(["
-				+ appPath + "]) AND NAME() LIKE '" + COMPONENT_NAME + "%'", Query.JCR_SQL2);
-
-		try {
-			boolean dirty = false;
-			Resource config = null;
-			if (configs.hasNext()) {
-
-				config = configs.next();
-				log.debug("Using existing configuration {}", config);
-			} else {
-				String path = appPath + "/config/" + COMPONENT_NAME + "-" + appPath.substring(appPath.lastIndexOf('/') + 1);
-				log.debug("Creating new configuration {}", path);
-				config = ResourceUtil.getOrCreateResource(resolver, path, new HashMap<String, Object>() {
-					{
-						put(JcrConstants.JCR_PRIMARYTYPE, "sling:OsgiConfig");
-					}
-				}, NodeType.NT_FOLDER, false);
-				dirty = true;
-			}
-
-			String bundle = getParameter(request, PN_BUNDLE, "");
-			String subService = getParameter(request, PN_SUB_SERVICE, "");
-			String name = getParameter(request, PN_NAME, "");
-			String mapping = bundle + (StringUtils.isNotBlank(subService) ? ":" + subService : "") + "=" + name;
-
-			ModifiableValueMap properties = config.adaptTo(ModifiableValueMap.class);
-			String[] mappings = properties.get("user.mapping", new String[0]);
-			if (!ArrayUtils.contains(mappings, mapping)) {
-				log.debug("Adding {} into service user mapping", mapping);
-				List<String> m = new ArrayList<String>();
-				m.addAll(Arrays.asList(mappings));
-				m.add(mapping);
-				properties.put("user.mapping", m.toArray(new String[m.size()]));
-				dirty = true;
-			} else {
-				log.debug("Already found {} in service user mapping", mapping);
-			}
-			if (dirty) {
-				log.debug("Saving changes to osgi config");
-				resolver.commit();
-			}
-		} catch (PersistenceException e) {
-			log.warn("Exception creating service mapping", e);
-			return false;
-		}
-
-		return true;
-	}
-
-	@Override
-	protected void doPost(HttpServletRequest request, HttpServletResponse response)
-			throws ServletException, IOException {
-		log.debug("Creating service user");
-
-		if (StringUtils.isBlank(getParameter(request, PN_NAME, ""))
-				|| StringUtils.isBlank(getParameter(request, PN_BUNDLE, ""))
-				|| StringUtils.isBlank(getParameter(request, PN_APP_PATH, ""))) {
-			sendErrorRedirect(request, response, "Missing required parameters!");
-			return;
-		}
-
-		ResourceResolver resolver = getResourceResolver(request);
-		if (resolver == null) {
-			log.warn("Unable to get serviceresolver from request!");
-			sendErrorRedirect(request, response, "Unable to get serviceresolver from request!");
-			return;
-		} else {
-			Resource userResource = getOrCreateServiceUser(request, resolver);
-			if (userResource == null) {
-				log.warn("Unable to create service user!");
-				sendErrorRedirect(request, response, "Unable to create service user!");
-				return;
-			} else {
-				if (createOrUpdateMapping(request, resolver)) {
-					if (updatePrivileges(request, resolver)) {
-						List<String> params = new ArrayList<String>();
-						params.add(PN_ACTION + "=" + "details");
-						params.add(PN_ALERT + "="
-								+ URLEncoder.encode(
-										"Service user " + userResource.getName() + " created / updated successfully!",
-										"UTF-8"));
-						params.add(PN_USER + "=" + URLEncoder.encode(userResource.getName(), "UTF-8"));
-
-						WebConsoleUtil.sendRedirect(request, response,
-								"/system/console/" + LABEL + "?" + StringUtils.join(params, "&"));
-					} else {
-						sendErrorRedirect(request, response, "Unable to update service user permissions!");
-					}
-				} else {
-					sendErrorRedirect(request, response, "Unable to create service user mapping!");
-				}
-			}
-		}
-
-	}
-
-	private List<String> extractPrincipals(Mapping mapping) {
-		List<String> principals = new ArrayList<String>();
-		String userName = mapping.map(mapping.getServiceName(), mapping.getSubServiceName());
-		if (StringUtils.isNotBlank(userName)) {
-			principals.add(userName);
-		}
-		Iterable<String> ps = mapping.mapPrincipals(mapping.getServiceName(), mapping.getSubServiceName());
-		if (ps != null) {
-			for (String principal : ps) {
-				principals.add(principal);
-			}
-		}
-		return principals;
-	}
-
-	private String[] findACLs(ResourceResolver resolver, String name, List<String> affectedPaths) {
-		List<String> acls = new ArrayList<String>();
-
-		Iterator<Resource> aclResources = resolver.findResources(
-				"SELECT * FROM [rep:GrantACE] AS s WHERE  [rep:principalName] = '" + name + "'", Query.JCR_SQL2);
-		while (aclResources.hasNext()) {
-			Resource aclResource = aclResources.next();
-			affectedPaths.add(aclResource.getPath());
-			ValueMap properties = aclResource.adaptTo(ValueMap.class);
-			String acl = aclResource.getPath().substring(0, aclResource.getPath().indexOf("/rep:policy")) + "="
-					+ StringUtils.join(properties.get("rep:privileges", String[].class), ",");
-			acls.add(acl);
-		}
-		return acls.toArray(new String[acls.size()]);
-	}
-
-	private Bundle findBundle(String symbolicName, Map<String, Bundle> bundles) {
-		if (bundles.isEmpty()) {
-			for (Bundle bundle : bundleContext.getBundles()) {
-				bundles.put(bundle.getSymbolicName(), bundle);
-			}
-		}
-		return bundles.get(symbolicName);
-	}
-
-	private Object findConfigurations(ResourceResolver resolver, String name, List<String> affectedPaths) {
-		List<String> configurations = new ArrayList<String>();
-
-		Iterator<Resource> configResources = resolver.findResources(
-				"SELECT * FROM [sling:OsgiConfig] AS s WHERE (ISDESCENDANTNODE([/apps]) OR ISDESCENDANTNODE([/libs])) AND NAME(s) LIKE 'org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended%' AND [user.mapping] LIKE '%="
-						+ name + "'",
-				Query.JCR_SQL2);
-		while (configResources.hasNext()) {
-			Resource configResource = configResources.next();
-			affectedPaths.add(configResource.getPath());
-			configurations.add(configResource.getPath());
-		}
-		configResources = resolver.findResources(
-				"SELECT * FROM [nt:file] AS s WHERE (ISDESCENDANTNODE([/apps]) OR ISDESCENDANTNODE([/libs])) AND NAME(s) LIKE 'org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended%' AND [jcr:content/jcr:data] LIKE '%="
-						+ name + "%'",
-				Query.JCR_SQL2);
-		while (configResources.hasNext()) {
-			Resource configResource = configResources.next();
-			affectedPaths.add(configResource.getPath());
-			configurations.add(configResource.getPath());
-		}
-
-		return configurations.toArray();
-	}
-
-	private String[] findMappings(ResourceResolver resolver, String name) {
-		List<String> mappings = new ArrayList<String>();
-		for (Mapping map : mapper.getActiveMappings()) {
-			if (name.equals(map.map(map.getServiceName(), map.getSubServiceName())) || hasPrincipal(map, name)) {
-				mappings.add(map.getServiceName()
-						+ (map.getSubServiceName() != null ? (":" + map.getSubServiceName()) : ""));
-			}
-		}
-		return mappings.toArray(new String[mappings.size()]);
-	}
-
-	private Collection<String> getBundles() {
-		List<String> bundles = new ArrayList<String>();
-		for (Bundle bundle : bundleContext.getBundles()) {
-			bundles.add(bundle.getSymbolicName());
-		}
-		Collections.sort(bundles);
-		return bundles;
-	}
-
-	@Override
-	public String getLabel() {
-		return LABEL;
-	}
-
-	private Resource getOrCreateServiceUser(HttpServletRequest request, ResourceResolver resolver) {
-
-		final String name = getParameter(request, PN_NAME, "");
-
-		Session session = resolver.adaptTo(Session.class);
-		try {
-			UserManager userManager = AccessControlUtil.getUserManager(session);
-			if (userManager.getAuthorizable(name) != null) {
-				Authorizable user = userManager.getAuthorizable(name);
-				log.debug("Using existing user: {}", user);
-				return resolver.getResource(user.getPath());
-			} else {
-
-				final String userPath = getParameter(request, PN_USER_PATH, "system");
-
-				log.debug("Creating new user with name {} and intermediate path {}", name, userPath);
-
-				User user = userManager.createSystemUser(name, userPath);
-				session.save();
-
-				String path = "/home/users/" + userPath + "/" + name;
-				log.debug("Moving {} to {}", user.getPath(), path);
-				session.getWorkspace().move(user.getPath(), path);
-				session.save();
-
-				return resolver.getResource(path);
-			}
-		} catch (RepositoryException e) {
-			log.warn("Exception getting / creating service user {}", name, e);
-			try {
-				session.refresh(false);
-			} catch (RepositoryException e1) {
-				log.error("Unexpected exception reverting changes", e1);
-			}
-		}
-		return null;
-	}
-
-	private String getParameter(final HttpServletRequest request, final String name, final String defaultValue) {
-		String value = request.getParameter(name);
-		if (value != null && !value.trim().isEmpty()) {
-			return value.trim();
-		}
-		return defaultValue;
-	}
-
-	private List<Pair<String, String>> getPrivileges(HttpServletRequest request) {
-		List<Pair<String, String>> privileges = new ArrayList<Pair<String, String>>();
-		List<String> params = Collections.list(request.getParameterNames());
-
-		for (String param : params) {
-			if (param.startsWith("acl-path-")) {
-				String path = request.getParameter(param);
-				String privilege = request.getParameter(param.replace("-path-", "-privilege-"));
-				if (StringUtils.isNotBlank(path) && StringUtils.isNotBlank(privilege)) {
-					privileges.add(new ImmutablePair<String, String>(path, privilege));
-				} else {
-					log.warn("Unable to load ACL due to missing value {}={}", path, privilege);
-				}
-			}
-		}
-
-		return privileges;
-	}
-
-	@SuppressWarnings("deprecation")
-	private ResourceResolver getResourceResolver(HttpServletRequest request) {
-		ResourceResolver resolver = null;
-		try {
-			resolver = (ResourceResolver) request.getAttribute("org.apache.sling.auth.core.ResourceResolver");
-			if (resolver == null) {
-				log.warn("Resource resolver not available in request, falling back to adminstrative resource resolver");
-				resolver = resolverFactory.getAdministrativeResourceResolver(null);
-			}
-		} catch (LoginException le) {
-			throw new RuntimeException(
-					"Unable to get Administrative Resource Resolver, add the bundle org.apache.sling.serviceuser.webconsole in the Apache Sling Login Admin Whitelist",
-					le);
-		}
-		return resolver;
-	}
-
-	/**
-	 * Called internally by {@link AbstractWebConsolePlugin} to load resources.
-	 *
-	 * This particular implementation depends on the label. As example, if the
-	 * plugin is accessed as <code>/system/console/abc</code>, and the plugin
-	 * resources are accessed like
-	 * <code>/system/console/abc/res/logo.gif</code>, the code here will try
-	 * load resource <code>/res/logo.gif</code> from the bundle, providing the
-	 * plugin.
-	 *
-	 *
-	 * @param path
-	 *            the path to read.
-	 * @return the URL of the resource or <code>null</code> if not found.
-	 */
-	protected URL getResource(String path) {
-		String base = "/" + LABEL + "/";
-		return (path != null && path.startsWith(base)) ? getClass().getResource(path.substring(base.length() - 1))
-				: null;
-	}
-
-	private String[] getSupportedPrivileges(HttpServletRequest request) {
-		String[] names = null;
-		try {
-			ResourceResolver resolver = getResourceResolver(request);
-			Session session = resolver.adaptTo(Session.class);
-			AccessControlManager accessControl = session.getAccessControlManager();
-			Privilege[] privileges = accessControl.getSupportedPrivileges("/");
-			names = new String[privileges.length];
-			for (int i = 0; i < privileges.length; i++) {
-				names[i] = privileges[i].getName();
-			}
-			Arrays.sort(names);
-		} catch (RepositoryException re) {
-			log.error("Exception loading Supported Privileges", re);
-		}
-		return names;
-	}
-
-	@Override
-	public String getTitle() {
-		return TITLE;
-	}
-
-	private boolean hasPrincipal(Mapping map, String name) {
-		Iterable<String> principals = map.mapPrincipals(map.getServiceName(), map.getSubServiceName());
-		if (principals != null) {
-			for (String principal : principals) {
-				if (principal.equals(name)) {
-					return true;
-				}
-			}
-		}
-		return false;
-	}
-
-	private void info(PrintWriter pw, String text) {
-		pw.print("<p class='statline ui-state-highlight'>");
-		pw.print(xss.encodeForHTML(text));
-		pw.println("</p>");
-	}
-
-	private void infoDiv(PrintWriter pw, String text) {
-		if (StringUtils.isBlank(text)) {
-			return;
-		}
-		pw.println("<div>");
-		pw.print("<span style='float:left'>");
-		pw.print(xss.encodeForHTML(text));
-		pw.println("</span>");
-		pw.println("</div>");
-	}
-
-	@Activate
-	protected void init(ComponentContext context) {
-		this.bundleContext = context.getBundleContext();
-	}
-
-	private void printPrincipals(List<Mapping> activeMappings, PrintWriter pw) {
-		List<Pair<String, Mapping>> mappings = new ArrayList<Pair<String, Mapping>>();
-		for (Mapping mapping : activeMappings) {
-			for (String principal : extractPrincipals(mapping)) {
-				mappings.add(new ImmutablePair<String, Mapping>(principal, mapping));
-			}
-		}
-		Collections.sort(mappings, new Comparator<Pair<String, Mapping>>() {
-			@Override
-			public int compare(Pair<String, Mapping> o1, Pair<String, Mapping> o2) {
-				if (o1.getKey().equals(o2.getKey())) {
-					return o1.getValue().getServiceName().compareTo(o2.getValue().getServiceName());
-				} else {
-					return o1.getKey().compareTo(o2.getKey());
-				}
-			}
-		});
-
-		for (Pair<String, Mapping> mapping : mappings) {
-			tableRows(pw);
-			pw.println("<td><a href=\"/system/console/serviceusers?action=details&amp;user="
-					+ xss.encodeForHTML(mapping.getKey()) + "\">" + xss.encodeForHTML(mapping.getKey()) + "</a></td>");
-
-			Map<String, Bundle> bundles = new HashMap<String, Bundle>();
-			Bundle bundle = findBundle(mapping.getValue().getServiceName(), bundles);
-			if (bundle != null) {
-				bundleContext.getBundle();
-				pw.println("<td><a href=\"/system/console/bundles/" + bundle.getBundleId() + "\">"
-						+ xss.encodeForHTML(
-								bundle.getHeaders().get(Constants.BUNDLE_NAME) + " (" + bundle.getSymbolicName())
-						+ ")</a></td>");
-				pw.println("<td>" + xss.encodeForHTML(mapping.getValue().getSubServiceName()) + "</td>");
-			} else {
-				bundleContext.getBundle();
-				pw.println("<td>" + xss.encodeForHTML(mapping.getValue().getServiceName()) + "</td>");
-				pw.println("<td>" + xss.encodeForHTML(
-						mapping.getValue().getSubServiceName() != null ? mapping.getValue().getSubServiceName() : "")
-						+ "</td>");
-			}
-		}
-
-	}
-
-	private void printPrivilegeSelect(PrintWriter pw, String label, List<Pair<String, String>> privileges,
-			String[] supportedPrivileges, String alertMessage) {
-		pw.print("<td style='width:20%'>");
-		pw.print(xss.encodeForHTMLAttr(label));
-		pw.println("</td>");
-		pw.print("<td><table class=\"repeating-container\" style=\"width: 100%\" data-length=\"" + privileges.size()
-				+ "\"><tr><td>Path</td><td>Privilege</td><td></td>");
-
-		int idx = 0;
-		for (Pair<String, String> privilege : privileges) {
-			pw.print("</tr><tr class=\"repeating-item\"><td>");
-
-			pw.print("<input type=\"text\"  name=\"acl-path-" + idx + "\" value='");
-			pw.print(xss.encodeForHTMLAttr(StringUtils.defaultString(privilege.getKey())));
-			pw.print("' style='width:100%' />");
-
-			pw.print("</td><td>");
-
-			pw.print("<input type=\"text\" list=\"data-privileges\" name=\"acl-privilege-" + idx + "\" value='");
-			pw.print(xss.encodeForHTMLAttr(StringUtils.defaultString(privilege.getValue())));
-			pw.print("' style='width:100%' />");
-
-			pw.print("</td><td>");
-
-			pw.print("<input type=\"button\" value=\"&nbsp;-&nbsp;\" class=\"repeating-remove\" /></td>");
-		}
-		pw.print("</tr></table>");
-
-		pw.print("<input type=\"button\" value=\"&nbsp;+&nbsp;\" class=\"repeating-add\" />");
-
-		pw.print("<datalist id=\"data-privileges\">");
-		for (String option : supportedPrivileges) {
-			pw.print("<option");
-			pw.print(">");
-			pw.print(xss.encodeForHTMLAttr(option));
-			pw.print("</option>");
-		}
-		pw.print("</datalist><script src=\"/system/console/serviceusers/res/ui/serviceusermanager.js\"></script>");
-		infoDiv(pw, alertMessage);
-		pw.println("</td>");
-	}
-
-	private void printServiceUserDetails(HttpServletRequest request, PrintWriter pw)
-			throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
-		String name = getParameter(request, PN_USER, "");
-
-		tableStart(pw, "Details for " + name, 2);
-
-		ResourceResolver resolver = getResourceResolver(request);
-
-		List<String> affectedPaths = new ArrayList<String>();
-		td(pw, "Service User Name");
-		td(pw, name);
-
-		tableRows(pw);
-
-		td(pw, "User Path");
-		Session session = resolver.adaptTo(Session.class);
-		UserManager userManager = AccessControlUtil.getUserManager(session);
-		if (userManager.getAuthorizable(name) != null) {
-			Authorizable user = userManager.getAuthorizable(name);
-			td(pw, user.getPath());
-			affectedPaths.add(user.getPath());
-		}
-
-		tableRows(pw);
-
-		String[] mappings = findMappings(resolver, name);
-		td(pw, "Mappings");
-		td(pw, mappings);
+    private static final String TD = "</td>";
+    private static final String BR = "<br/>";
+    private static final String TR = "</tr>";
+    private static final String STYLE_WIDTH_100 = "' style='width:100%' />";
+    private static final String TD_STYLE_WIDTH_20 = "<td style='width:20%'>";
+    public static final String COMPONENT_NAME = "org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended";
+    public static final String LABEL = "serviceusers";
+    public static final String TITLE = "Service Users";
+
+    public static final String PN_ACTION = "action";
+    public static final String PN_ALERT = "alert";
+    public static final String PN_APP_PATH = "appPath";
+    public static final String PN_BUNDLE = "bundle";
+    public static final String PN_NAME = "name";
+    public static final String PN_SUB_SERVICE = "subService";
+    public static final String PN_USER = "user";
+    public static final String PN_USER_PATH = "userPath";
+
+    private static final Logger log = LoggerFactory.getLogger(ServiceUserWebConsolePlugin.class);
+
+    private BundleContext bundleContext;
+
+    @Reference(policyOption = ReferencePolicyOption.GREEDY)
+    private XSSAPI xss;
+
+    @Reference(policyOption = ReferencePolicyOption.GREEDY)
+    private ResourceResolverFactory resolverFactory;
+
+    @Reference
+    private ServiceUserMapper mapper;
+
+    private boolean createOrUpdateMapping(HttpServletRequest request, ResourceResolver resolver) {
+
+        String appPath = getParameter(request, PN_APP_PATH, "");
+
+        Iterator<Resource> configs = resolver.findResources("SELECT * FROM [sling:OsgiConfig] WHERE ISDESCENDANTNODE(["
+                + appPath + "]) AND NAME() LIKE '" + COMPONENT_NAME + "%'", Query.JCR_SQL2);
+
+        try {
+            boolean dirty = false;
+            Resource config = null;
+            if (configs.hasNext()) {
+
+                config = configs.next();
+                log.debug("Using existing configuration {}", config);
+            } else {
+                String path = appPath + "/config/" + COMPONENT_NAME + "-"
+                        + appPath.substring(appPath.lastIndexOf('/') + 1);
+                log.debug("Creating new configuration {}", path);
+
+                config = ResourceUtil.getOrCreateResource(resolver, path,
+                        Collections.singletonMap(JcrConstants.JCR_PRIMARYTYPE, (Object) "sling:OsgiConfig"),
+                        NodeType.NT_FOLDER, false);
+                dirty = true;
+            }
+
+            String bundle = getParameter(request, PN_BUNDLE, "");
+            String subService = getParameter(request, PN_SUB_SERVICE, "");
+            String name = getParameter(request, PN_NAME, "");
+            String mapping = bundle + (StringUtils.isNotBlank(subService) ? ":" + subService : "") + "=" + name;
+
+            ModifiableValueMap properties = config.adaptTo(ModifiableValueMap.class);
+            String[] mappings = properties.get("user.mapping", new String[0]);
+            if (!ArrayUtils.contains(mappings, mapping)) {
+                log.debug("Adding {} into service user mapping", mapping);
+                List<String> m = new ArrayList<>();
+                m.addAll(Arrays.asList(mappings));
+                m.add(mapping);
+                properties.put("user.mapping", m.toArray(new String[m.size()]));
+                dirty = true;
+            } else {
+                log.debug("Already found {} in service user mapping", mapping);
+            }
+            if (dirty) {
+                log.debug("Saving changes to osgi config");
+                resolver.commit();
+            }
+        } catch (PersistenceException e) {
+            log.warn("Exception creating service mapping", e);
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    protected void doPost(HttpServletRequest request, HttpServletResponse response)
+            throws ServletException, IOException {
+        log.debug("Creating service user");
+
+        ResourceResolver resolver = null;
+        try {
+            if (StringUtils.isBlank(getParameter(request, PN_NAME, ""))
+                    || StringUtils.isBlank(getParameter(request, PN_BUNDLE, ""))
+                    || StringUtils.isBlank(getParameter(request, PN_APP_PATH, ""))) {
+                sendErrorRedirect(request, response, "Missing required parameters!");
+                return;
+            }
+
+            resolver = getResourceResolver(request);
+            if (resolver == null) {
+                log.warn("Unable to get serviceresolver from request!");
+                sendErrorRedirect(request, response, "Unable to get serviceresolver from request!");
+            } else {
+                processRequest(request, response, resolver);
+            }
+        } catch (LoginException | IOException e) {
+            try {
+                sendErrorRedirect(request, response, "Unexpected exception: " + e);
+            } catch (IOException e2) {
+                throw new IOException("Failed to send error response", e2);
+            }
+        } finally {
+            if (needsAdministrativeResolver(request) && resolver != null) {
+                resolver.close();
+            }
+        }
+
+    }
+
+    private void processRequest(HttpServletRequest request, HttpServletResponse response, ResourceResolver resolver)
+            throws IOException {
+        Resource userResource = getOrCreateServiceUser(request, resolver);
+        if (userResource == null) {
+            log.warn("Unable to create service user!");
+            sendErrorRedirect(request, response, "Unable to create service user!");
+        } else {
+            if (createOrUpdateMapping(request, resolver)) {
+                if (updatePrivileges(request, resolver)) {
+                    List<String> params = new ArrayList<>();
+                    params.add(PN_ACTION + "=" + "details");
+                    params.add(PN_ALERT + "="
+                            + URLEncoder.encode(
+                                    "Service user " + userResource.getName() + " created / updated successfully!",
+                                    StandardCharsets.UTF_8.toString()));
+                    params.add(PN_USER + "="
+                            + URLEncoder.encode(userResource.getName(), StandardCharsets.UTF_8.toString()));
+
+                    WebConsoleUtil.sendRedirect(request, response,
+                            "/system/console/" + LABEL + "?" + StringUtils.join(params, "&"));
+                } else {
+                    sendErrorRedirect(request, response, "Unable to update service user permissions!");
+                }
+            } else {
+                sendErrorRedirect(request, response, "Unable to create service user mapping!");
+            }
+        }
+    }
+
+    private List<String> extractPrincipals(Mapping mapping) {
+        List<String> principals = new ArrayList<>();
+        String userName = mapping.map(mapping.getServiceName(), mapping.getSubServiceName());
+        if (StringUtils.isNotBlank(userName)) {
+            principals.add(userName);
+        }
+        Iterable<String> ps = mapping.mapPrincipals(mapping.getServiceName(), mapping.getSubServiceName());
+        if (ps != null) {
+            for (String principal : ps) {
+                principals.add(principal);
+            }
+        }
+        return principals;
+    }
+
+    private String[] findACLs(ResourceResolver resolver, String name, List<String> affectedPaths) {
+        List<String> acls = new ArrayList<>();
+
+        Iterator<Resource> aclResources = resolver.findResources(
+                "SELECT * FROM [rep:GrantACE] AS s WHERE  [rep:principalName] = '" + name + "'", Query.JCR_SQL2);
+        while (aclResources.hasNext()) {
+            Resource aclResource = aclResources.next();
+            affectedPaths.add(aclResource.getPath());
+            ValueMap properties = aclResource.adaptTo(ValueMap.class);
+            String acl = aclResource.getPath().substring(0, aclResource.getPath().indexOf("/rep:policy")) + "="
+                    + StringUtils.join(properties.get("rep:privileges", String[].class), ",");
+            acls.add(acl);
+        }
+        return acls.toArray(new String[acls.size()]);
+    }
+
+    private Bundle findBundle(String symbolicName, Map<String, Bundle> bundles) {
+        if (bundles.isEmpty()) {
+            for (Bundle bundle : bundleContext.getBundles()) {
+                bundles.put(bundle.getSymbolicName(), bundle);
+            }
+        }
+        return bundles.get(symbolicName);
+    }
+
+    private Object findConfigurations(ResourceResolver resolver, String name, List<String> affectedPaths) {
+        List<String> configurations = new ArrayList<>();
+
+        Iterator<Resource> configResources = resolver.findResources(
+                "SELECT * FROM [sling:OsgiConfig] AS s WHERE (ISDESCENDANTNODE([/apps]) OR ISDESCENDANTNODE([/libs])) AND NAME(s) LIKE 'org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended%' AND [user.mapping] LIKE '%="
+                        + name + "'",
+                Query.JCR_SQL2);
+        while (configResources.hasNext()) {
+            Resource configResource = configResources.next();
+            affectedPaths.add(configResource.getPath());
+            configurations.add(configResource.getPath());
+        }
+        configResources = resolver.findResources(
+                "SELECT * FROM [nt:file] AS s WHERE (ISDESCENDANTNODE([/apps]) OR ISDESCENDANTNODE([/libs])) AND NAME(s) LIKE 'org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended%' AND [jcr:content/jcr:data] LIKE '%="
+                        + name + "%'",
+                Query.JCR_SQL2);
+        while (configResources.hasNext()) {
+            Resource configResource = configResources.next();
+            affectedPaths.add(configResource.getPath());
+            configurations.add(configResource.getPath());
+        }
+
+        return configurations.toArray();
+    }
+
+    private String[] findMappings(String name) {
+        List<String> mappings = new ArrayList<>();
+        for (Mapping map : mapper.getActiveMappings()) {
+            if (name.equals(map.map(map.getServiceName(), map.getSubServiceName())) || hasPrincipal(map, name)) {
+                mappings.add(map.getServiceName()
+                        + (map.getSubServiceName() != null ? (":" + map.getSubServiceName()) : ""));
+            }
+        }
+        return mappings.toArray(new String[mappings.size()]);
+    }
+
+    private Collection<String> getBundles() {
+        List<String> bundles = new ArrayList<>();
+        for (Bundle bundle : bundleContext.getBundles()) {
+            bundles.add(bundle.getSymbolicName());
+        }
+        Collections.sort(bundles);
+        return bundles;
+    }
+
+    @Override
+    public String getLabel() {
+        return LABEL;
+    }
+
+    private Resource getOrCreateServiceUser(HttpServletRequest request, ResourceResolver resolver) {
+
+        final String name = getParameter(request, PN_NAME, "");
+
+        Session session = resolver.adaptTo(Session.class);
+        try {
+            UserManager userManager = AccessControlUtil.getUserManager(session);
+            if (userManager.getAuthorizable(name) != null) {
+                Authorizable user = userManager.getAuthorizable(name);
+                log.debug("Using existing user: {}", user);
+                return resolver.getResource(user.getPath());
+            } else {
+
+                final String userPath = getParameter(request, PN_USER_PATH, "system");
+
+                log.debug("Creating new user with name {} and intermediate path {}", name, userPath);
+
+                User user = userManager.createSystemUser(name, userPath);
+                session.save();
+
+                String path = "/home/users/" + userPath + "/" + name;
+                log.debug("Moving {} to {}", user.getPath(), path);
+                session.getWorkspace().move(user.getPath(), path);
+                session.save();
+
+                return resolver.getResource(path);
+            }
+        } catch (RepositoryException e) {
+            log.warn("Exception getting / creating service user {}", name, e);
+            try {
+                session.refresh(false);
+            } catch (RepositoryException e1) {
+                log.error("Unexpected exception reverting changes", e1);
+            }
+        }
+        return null;
+    }
+
+    private String getParameter(final HttpServletRequest request, final String name, final String defaultValue) {
+        String value = request.getParameter(name);
+        if (value != null && !value.trim().isEmpty()) {
+            return value.trim();
+        }
+        return defaultValue;
+    }
+
+    private List<Pair<String, String>> getPrivileges(HttpServletRequest request) {
+        List<Pair<String, String>> privileges = new ArrayList<>();
+        List<String> params = Collections.list(request.getParameterNames());
+
+        for (String param : params) {
+            if (param.startsWith("acl-path-")) {
+                String path = request.getParameter(param);
+                String privilege = request.getParameter(param.replace("-path-", "-privilege-"));
+                if (StringUtils.isNotBlank(path) && StringUtils.isNotBlank(privilege)) {
+                    privileges.add(new ImmutablePair<String, String>(path, privilege));
+                } else {
+                    log.warn("Unable to load ACL due to missing value {}={}", path, privilege);
+                }
+            }
+        }
+
+        return privileges;
+    }
+
+    private boolean needsAdministrativeResolver(HttpServletRequest request) {
+        Object resolver = request.getAttribute("org.apache.sling.auth.core.ResourceResolver");
+        return !(resolver instanceof ResourceResolver);
+    }
+
+    @SuppressWarnings("deprecation")
+    private ResourceResolver getResourceResolver(HttpServletRequest request) throws LoginException {
+        ResourceResolver resolver = null;
+        if (needsAdministrativeResolver(request)) {
+            try {
+                log.warn("Resource resolver not available in request, falling back to adminstrative resource resolver");
+                resolver = resolverFactory.getAdministrativeResourceResolver(null);
+            } catch (LoginException le) {
+                throw new LoginException(
+                        "Unable to get Administrative Resource Resolver, add the bundle org.apache.sling.serviceuser.webconsole in the Apache Sling Login Admin Whitelist",
+                        le);
+            }
+        } else {
+            resolver = (ResourceResolver) request.getAttribute("org.apache.sling.auth.core.ResourceResolver");
+        }
+
+        return resolver;
+    }
+
+    /**
+     * Called internally by {@link AbstractWebConsolePlugin} to load resources.
+     *
+     * This particular implementation depends on the label. As example, if the
+     * plugin is accessed as <code>/system/console/abc</code>, and the plugin
+     * resources are accessed like <code>/system/console/abc/res/logo.gif</code>,
+     * the code here will try load resource <code>/res/logo.gif</code> from the
+     * bundle, providing the plugin.
+     *
+     *
+     * @param path the path to read.
+     * @return the URL of the resource or <code>null</code> if not found.
+     */
+    protected URL getResource(String path) {
+        String base = "/" + LABEL + "/";
+        return (path != null && path.startsWith(base)) ? getClass().getResource(path.substring(base.length() - 1))
+                : null;
+    }
+
+    private String[] getSupportedPrivileges(HttpServletRequest request) throws LoginException {
+        String[] names = null;
+        ResourceResolver resolver = null;
+        try {
+            resolver = getResourceResolver(request);
+            Session session = resolver.adaptTo(Session.class);
+            AccessControlManager accessControl = session.getAccessControlManager();
+            Privilege[] privileges = accessControl.getSupportedPrivileges("/");
+            names = new String[privileges.length];
+            for (int i = 0; i < privileges.length; i++) {
+                names[i] = privileges[i].getName();
+            }
+            Arrays.sort(names);
+        } catch (RepositoryException re) {
+            log.error("Exception loading Supported Privileges", re);
+        } finally {
+            if (needsAdministrativeResolver(request) && resolver != null) {
+                resolver.close();
+            }
+        }
+        return names;
+    }
+
+    @Override
+    public String getTitle() {
+        return TITLE;
+    }
+
+    private boolean hasPrincipal(Mapping map, String name) {
+        Iterable<String> principals = map.mapPrincipals(map.getServiceName(), map.getSubServiceName());
+        if (principals != null) {
+            for (String principal : principals) {
+                if (principal.equals(name)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private void info(PrintWriter pw, String text) {
+        pw.print("<p class='statline ui-state-highlight'>");
+        pw.print(xss.encodeForHTML(text));
+        pw.println("</p>");
+    }
+
+    private void infoDiv(PrintWriter pw, String text) {
+        if (StringUtils.isBlank(text)) {
+            return;
+        }
+        pw.println("<div>");
+        pw.print("<span style='float:left'>");
+        pw.print(xss.encodeForHTML(text));
+        pw.println("</span>");
+        pw.println("</div>");
+    }
+
+    @Activate
+    protected void init(ComponentContext context) {
+        this.bundleContext = context.getBundleContext();
+    }
+
+    private void printPrincipals(List<Mapping> activeMappings, PrintWriter pw) {
+        List<Pair<String, Mapping>> mappings = new ArrayList<>();
+        for (Mapping mapping : activeMappings) {
+            for (String principal : extractPrincipals(mapping)) {
+                mappings.add(new ImmutablePair<String, Mapping>(principal, mapping));
+            }
+        }
+        Collections.sort(mappings, new Comparator<Pair<String, Mapping>>() {
+            @Override
+            public int compare(Pair<String, Mapping> o1, Pair<String, Mapping> o2) {
+                if (o1.getKey().equals(o2.getKey())) {
+                    return o1.getValue().getServiceName().compareTo(o2.getValue().getServiceName());
+                } else {
+                    return o1.getKey().compareTo(o2.getKey());
+                }
+            }
+        });
+
+        for (Pair<String, Mapping> mapping : mappings) {
+            tableRows(pw);
+            pw.println("<td><a href=\"/system/console/serviceusers?action=details&amp;user="
+                    + xss.encodeForHTML(mapping.getKey()) + "\">" + xss.encodeForHTML(mapping.getKey()) + "</a></td>");
+
+            Map<String, Bundle> bundles = new HashMap<>();
+            Bundle bundle = findBundle(mapping.getValue().getServiceName(), bundles);
+            if (bundle != null) {
+                bundleContext.getBundle();
+                pw.println("<td><a href=\"/system/console/bundles/" + bundle.getBundleId() + "\">"
+                        + xss.encodeForHTML(
+                                bundle.getHeaders().get(Constants.BUNDLE_NAME) + " (" + bundle.getSymbolicName())
+                        + ")</a></td>");
+                pw.println("<td>" + xss.encodeForHTML(mapping.getValue().getSubServiceName()) + TD);
+            } else {
+                bundleContext.getBundle();
+                pw.println("<td>" + xss.encodeForHTML(mapping.getValue().getServiceName()) + TD);
+                pw.println("<td>" + xss.encodeForHTML(
+                        mapping.getValue().getSubServiceName() != null ? mapping.getValue().getSubServiceName() : "")
+                        + TD);
+            }
+        }
+
+    }
+
+    private void printPrivilegeSelect(PrintWriter pw, String label, List<Pair<String, String>> privileges,
+            String[] supportedPrivileges, String alertMessage) {
+        pw.print(TD_STYLE_WIDTH_20);
+        pw.print(xss.encodeForHTMLAttr(label));
+        pw.println(TD);
+        pw.print("<td><table class=\"repeating-container\" style=\"width: 100%\" data-length=\"" + privileges.size()
+                + "\"><tr><td>Path</td><td>Privilege</td><td></td>");
+
+        int idx = 0;
+        for (Pair<String, String> privilege : privileges) {
+            pw.print("</tr><tr class=\"repeating-item\"><td>");
+
+            pw.print("<input type=\"text\"  name=\"acl-path-" + idx + "\" value='");
+            pw.print(xss.encodeForHTMLAttr(StringUtils.defaultString(privilege.getKey())));
+            pw.print(STYLE_WIDTH_100);
+
+            pw.print("</td><td>");
+
+            pw.print("<input type=\"text\" list=\"data-privileges\" name=\"acl-privilege-" + idx + "\" value='");
+            pw.print(xss.encodeForHTMLAttr(StringUtils.defaultString(privilege.getValue())));
+            pw.print(STYLE_WIDTH_100);
+
+            pw.print("</td><td>");
+
+            pw.print("<input type=\"button\" value=\"&nbsp;-&nbsp;\" class=\"repeating-remove\" /></td>");
+        }
+        pw.print("</tr></table>");
+
+        pw.print("<input type=\"button\" value=\"&nbsp;+&nbsp;\" class=\"repeating-add\" />");
+
+        pw.print("<datalist id=\"data-privileges\">");
+        for (String option : supportedPrivileges) {
+            pw.print("<option");
+            pw.print(">");
+            pw.print(xss.encodeForHTMLAttr(option));
+            pw.print("</option>");
+        }
+        pw.print("</datalist><script src=\"/system/console/serviceusers/res/ui/serviceusermanager.js\"></script>");
+        infoDiv(pw, alertMessage);
+        pw.println(TD);
+    }
+
+    private void printServiceUserDetails(HttpServletRequest request, PrintWriter pw)
+            throws RepositoryException, LoginException {
+        String name = getParameter(request, PN_USER, "");
+
+        tableStart(pw, "Details for " + name, 2);
+
+        ResourceResolver resolver = null;
+        try {
+            resolver = getResourceResolver(request);
+
+            List<String> affectedPaths = new ArrayList<>();
+            td(pw, "Service User Name");
+            td(pw, name);
+
+            tableRows(pw);
+
+            td(pw, "User Path");
+            Session session = resolver.adaptTo(Session.class);
+            UserManager userManager = AccessControlUtil.getUserManager(session);
+            if (userManager.getAuthorizable(name) != null) {
+                Authorizable user = userManager.getAuthorizable(name);
+                td(pw, user.getPath());
+                affectedPaths.add(user.getPath());
+            }
+
+            tableRows(pw);
+
+            String[] mappings = findMappings(name);
+            td(pw, "Mappings");
+            td(pw, mappings);
+
+            tableRows(pw);
+
+            td(pw, "OSGi Configurations");
+            td(pw, findConfigurations(resolver, name, affectedPaths));
+
+            tableRows(pw);
+
+            td(pw, "ACLs");
+            td(pw, findACLs(resolver, name, affectedPaths));
+
+            tableEnd(pw);
+
+            pw.write(BR);
 
-		tableRows(pw);
+            pw.write("<h3>Example Filter</h3>");
 
-		td(pw, "OSGi Configurations");
-		td(pw, findConfigurations(resolver, name, affectedPaths));
-
-		tableRows(pw);
+            pw.write(BR);
 
-		td(pw, "ACLs");
-		td(pw, findACLs(resolver, name, affectedPaths));
+            pw.write("<pre><code>&lt;workspaceFilter version=\"1.0\"&gt;<br/>");
+            for (String affectedPath : affectedPaths) {
+                pw.write("  &lt;filter root=\"" + affectedPath + "\" /&gt;<br/>");
+            }
+            pw.write("&lt;/workspaceFilter\"&gt</code></pre>");
 
-		tableEnd(pw);
+            pw.write(BR);
 
-		pw.write("<br/>");
+            pw.write("<h3>Use Example(s)</h3>");
 
-		pw.write("<h3>Example Filter</h3>");
+            pw.write(BR);
 
-		pw.write("<br/>");
+            pw.write("<pre><code>");
 
-		pw.write("<pre><code>&lt;workspaceFilter version=\"1.0\"&gt;<br/>");
-		for (String affectedPath : affectedPaths) {
-			pw.write("  &lt;filter root=\"" + affectedPath + "\" /&gt;<br/>");
-		}
-		pw.write("&lt;/workspaceFilter\"&gt</code></pre>");
-
-		pw.write("<br/>");
-
-		pw.write("<h3>Use Example(s)</h3>");
-
-		pw.write("<br/>");
-
-		pw.write("<pre><code>");
-
-		boolean includeNonSubService = false;
-		for (String mapping : mappings) {
-			if (mapping.contains(":")) {
-				String subService = StringUtils.substringAfter(mapping, ":");
-				pw.write("// Example using Sub Service " + subService
-						+ "<br/>ResourceResolver resolver = resolverFactory.getServiceResourceResolver(new HashMap<String, Object>() {<br/>  private static final long serialVersionUID = 1L;<br/>  {<br/>    put(ResourceResolverFactory.SUBSERVICE,\""
-						+ subService + "\");<br/>  }<br/>});<br/><br/>");
-			} else {
-				includeNonSubService = true;
-			}
-		}
-		if (includeNonSubService) {
-			pw.write(
-					"// Example using bundle authentication<br/>ResourceResolver resolver = resolverFactory.getServiceResourceResolver(null);");
-		}
-		pw.write("</code></pre>");
-	}
-
-	private void printServiceUsers(HttpServletRequest request, PrintWriter pw) {
-
-		try {
-
-			pw.println("<form method='post' action='/system/console/serviceusers'>");
-
-			tableStart(pw, "Create Service User", 2);
-
-			String name = getParameter(request, PN_NAME, "");
-			textField(pw, "Service User Name", PN_NAME, name,
-					"The name of the service user to create, can already exist");
-
-			tableRows(pw);
-			String userContextPath = getParameter(request, PN_USER_PATH, "");
-			textField(pw, "Intermediate Path", PN_USER_PATH, userContextPath,
-					"Optional: The intermediate path under which to create the user. Should start with system, e.g. system/myapp");
-
-			tableRows(pw);
-			String bundle = getParameter(request, PN_BUNDLE, "");
-			selectField(pw, "Bundle", PN_BUNDLE, bundle, getBundles(),
-					"The bundle from which this service user will be useable");
-
-			tableRows(pw);
-			String serviceName = getParameter(request, PN_SUB_SERVICE, "");
-			textField(pw, "Sub Service Name", PN_SUB_SERVICE, serviceName,
-					"Optional: Allows for different permissions for different services within a bundle");
-
-			tableRows(pw);
-			String appPath = getParameter(request, PN_APP_PATH, "");
-			textField(pw, "Application Path", PN_APP_PATH, appPath,
-					"The application under which to create the OSGi Configuration for the Service User Mapping, e.g. /apps/myapp");
-
-			tableRows(pw);
-
-			List<Pair<String, String>> privileges = getPrivileges(request);
-			printPrivilegeSelect(pw, "ACLs", privileges, getSupportedPrivileges(request),
-					"Set the privileges for this service user");
-
-			tableRows(pw);
-
-			pw.println("<td></td>");
-			pw.println("<td><input type='submit' value='Create / Update'/></td>");
-			tableEnd(pw);
-
-			pw.println("</form>");
-
-			pw.println("<br/><br/>");
-
-			// Service Users
-			List<Mapping> activeMappings = mapper.getActiveMappings();
-			tableStart(pw, "Active Service Users", 3);
-			pw.println("<th>Name</th>");
-			pw.println("<th>Bundle</th>");
-			pw.println("<th>SubService</th>");
-			printPrincipals(activeMappings, pw);
-
-			tableEnd(pw);
-
-			pw.println("<br/>");
-
-		} finally {
-		}
-	}
-
-	@Override
-	protected void renderContent(HttpServletRequest request, HttpServletResponse response)
-			throws ServletException, IOException {
-
-		final PrintWriter pw = response.getWriter();
-
-		pw.println("<br/>");
-
-		String alert = getParameter(request, "alert", "");
-		if (StringUtils.isNotBlank(alert)) {
-			info(pw, alert);
-		}
-
-		String action = getParameter(request, "action", "");
-		if (StringUtils.isBlank(action)) {
-			log.debug("Rendering service users page");
-			info(pw, "Service users are used by OSGi Services to access the Sling repository. Use this form to find and create service users.");
-			printServiceUsers(request, pw);
-		} else if ("details".equals(action)) {
-			log.debug("Rendering service user details page");
-			try {
-				printServiceUserDetails(request, pw);
-			} catch (RepositoryException e) {
-				log.warn("Exception rendering details for user", e);
-				info(pw, "Exception rendering details for user");
-			}
-		} else {
-			info(pw, "Unknown action: " + action);
-		}
-	}
-
-	private void selectField(PrintWriter pw, String label, String fieldName, String value, Collection<String> options,
-			String... alertMessages) {
-		pw.print("<td style='width:20%'>");
-		pw.print(xss.encodeForHTMLAttr(label));
-		pw.println("</td>");
-		pw.print("<td><input type=\"text\" list=\"data-" + xss.encodeForHTMLAttr(fieldName) + "\" name='");
-		pw.print(xss.encodeForHTMLAttr(fieldName));
-		pw.print("' value='");
-		pw.print(xss.encodeForHTMLAttr(StringUtils.defaultString(value)));
-		pw.print("' style='width:100%' />");
-		pw.print("<datalist id=\"data-" + xss.encodeForHTMLAttr(fieldName) + "\">");
-		for (String option : options) {
-			pw.print("<option");
-			pw.print(">");
-			pw.print(xss.encodeForHTMLAttr(option));
-			pw.print("</option>");
-		}
-		pw.print("</datalist>");
-		for (String alertMessage : alertMessages) {
-			infoDiv(pw, alertMessage);
-		}
-		pw.println("</td>");
-	}
-
-	private void sendErrorRedirect(HttpServletRequest request, HttpServletResponse response, String alert)
-			throws IOException {
-		List<String> params = new ArrayList<String>();
-		for (String param : new String[] { PN_APP_PATH, PN_BUNDLE, PN_NAME, PN_SUB_SERVICE, PN_USER_PATH }) {
-			params.add(param + "=" + URLEncoder.encode(this.getParameter(request, param, ""), "UTF-8"));
-		}
-
-		int idx = 0;
-		List<Pair<String, String>> privs = getPrivileges(request);
-		for (Pair<String, String> priv : privs) {
-			params.add("acl-path-" + idx + "=" + URLEncoder.encode(priv.getKey(), "UTF-8"));
-			params.add("acl-privilege-" + idx + "=" + URLEncoder.encode(priv.getValue(), "UTF-8"));
-			idx++;
-		}
-
-		if (StringUtils.isNotBlank(alert)) {
-			params.add(PN_ALERT + "=" + URLEncoder.encode(alert, "UTF-8"));
-		}
-
-		WebConsoleUtil.sendRedirect(request, response,
-				"/system/console/" + LABEL + "?" + StringUtils.join(params, "&"));
-	}
-
-	private void tableEnd(PrintWriter pw) {
-		pw.println("</tr>");
-		pw.println("</tbody>");
-		pw.println("</table>");
-	}
-
-	private void tableRows(PrintWriter pw) {
-		pw.println("</tr>");
-		pw.println("<tr>");
-	}
-
-	private void tableStart(PrintWriter pw, String title, int colspan) {
-		pw.println("<table class='nicetable ui-widget'>");
-		pw.println("<thead class='ui-widget-header'>");
-		pw.println("<tr>");
-		pw.print("<th colspan=");
-		pw.print(String.valueOf(colspan));
-		pw.print(">");
-		pw.print(xss.encodeForHTML(title));
-		pw.println("</th>");
-		pw.println("</tr>");
-		pw.println("</thead>");
-		pw.println("<tbody class='ui-widget-content'>");
-		pw.println("<tr>");
-	}
-
-	private void td(PrintWriter pw, Object value, String... title) {
-		pw.print("<td");
-		if (title.length > 0 && !StringUtils.isBlank(title[0])) {
-			pw.print(" title='");
-			pw.print(xss.encodeForHTML(title[0]));
-			pw.print("'");
-		}
-		pw.print(">");
-
-		if (value != null) {
-			if (value.getClass().isArray()) {
-				for (int i = 0; i < Array.getLength(value); i++) {
-					Object itemValue = Array.get(value, i);
-					pw.print(xss.encodeForHTML(ObjectUtils.defaultIfNull(itemValue, "").toString()));
-					pw.println("<br>");
-				}
-			} else {
-				pw.print(xss.encodeForHTML(value.toString()));
-			}
-		}
-
-		if (title.length > 0 && !StringUtils.isBlank(title[0])) {
-			pw.print("<span class='ui-icon ui-icon-info' style='float:left'></span>");
-		}
-		pw.print("</td>");
-	}
-
-	private void textField(PrintWriter pw, String label, String fieldName, String value, String... alertMessages) {
-		pw.print("<td style='width:20%'>");
-		pw.print(xss.encodeForHTMLAttr(label));
-		pw.println("</td>");
-		pw.print("<td><input name='");
-		pw.print(xss.encodeForHTMLAttr(fieldName));
-		pw.print("' value='");
-		pw.print(xss.encodeForHTMLAttr(StringUtils.defaultString(value)));
-		pw.print("' style='width:100%'/>");
-		for (String alertMessage : alertMessages) {
-			infoDiv(pw, alertMessage);
-		}
-		pw.println("</td>");
-	}
-
-	private boolean updatePrivileges(HttpServletRequest request, ResourceResolver resolver) {
-
-		List<Pair<String, String>> privileges = this.getPrivileges(request);
-		String name = getParameter(request, PN_NAME, "");
-
-		List<String> currentPolicies = new ArrayList<String>();
-		findACLs(resolver, name, currentPolicies);
-		for (int i = 0; i < currentPolicies.size(); i++) {
-			String path = StringUtils.substringBefore(currentPolicies.get(i), "/rep:policy");
-			currentPolicies.set(i, StringUtils.isNotBlank(path) ? path : "/");
-		}
-		log.debug("Loaded current policy paths: {}", currentPolicies);
-
-		Map<String, List<String>> toSet = new HashMap<String, List<String>>();
-		for (Pair<String, String> privilege : privileges) {
-			if (!toSet.containsKey(privilege.getKey())) {
-				toSet.put(privilege.getKey(), new ArrayList<String>());
-			}
-			toSet.get(privilege.getKey()).add(privilege.getValue());
-		}
-		log.debug("Loaded updated policy paths: {}", currentPolicies);
-
-		String lastEntry = null;
-
-		try {
-
-			Session session = resolver.adaptTo(Session.class);
-			AccessControlManager accessManager = session.getAccessControlManager();
-			PrincipalManager principalManager = AccessControlUtil.getPrincipalManager(session);
-
-			for (Entry<String, List<String>> pol : toSet.entrySet()) {
-				lastEntry = pol.getKey();
-				currentPolicies.remove(pol.getKey());
-				log.debug("Updating policies for {}", pol.getKey());
-
-				AccessControlPolicy[] policies = accessManager.getPolicies(pol.getKey());
-				List<String> toRemove = new ArrayList<String>();
-				for (AccessControlPolicy p : policies) {
-					if (p instanceof AccessControlList) {
-						AccessControlList policy = (AccessControlList) p;
-						for (AccessControlEntry entry : policy.getAccessControlEntries()) {
-							Principal prin = entry.getPrincipal();
-							if (prin.getName().equals(name)) {
-								for (Privilege privilege : entry.getPrivileges()) {
-									if (!pol.getValue().contains(privilege.getName())) {
-										log.debug("Removing privilege {}", privilege);
-										toRemove.add(privilege.getName());
-									}
-								}
-							}
-						}
-					}
-				}
-				Principal principal = principalManager.getPrincipal(name);
-				AccessControlUtil.replaceAccessControlEntry(session, pol.getKey(), principal,
-						pol.getValue().toArray(new String[pol.getValue().size()]), new String[0],
-						toRemove.toArray(new String[toRemove.size()]), null);
-			}
-			session.save();
-
-			for (String oldPolicy : currentPolicies) {
-				boolean removed = false;
-				log.debug("Removing policy for {}", oldPolicy);
-				AccessControlPolicy[] policies = accessManager.getPolicies(oldPolicy);
-				AccessControlEntry toRemove = null;
-				for (AccessControlPolicy p : policies) {
-					if (p instanceof AccessControlList) {
-						AccessControlList policy = (AccessControlList) p;
-						for (AccessControlEntry entry : policy.getAccessControlEntries()) {
-							Principal prin = entry.getPrincipal();
-							if (prin.getName().equals(name)) {
-								toRemove = entry;
-								break;
-							}
-						}
-						if (toRemove != null) {
-							removed = true;
-							policy.removeAccessControlEntry(toRemove);
-							accessManager.setPolicy(oldPolicy, policy);
-							session.save();
-							log.debug("Removed access control entry {}", toRemove);
-						}
-					}
-				}
-				if (!removed) {
-					log.warn("No policy found for {}", oldPolicy);
-				}
-			}
-		} catch (RepositoryException e) {
-			log.error("Exception updating principals with {}, failed on {}", toSet, lastEntry, e);
-			return false;
-		}
-
-		return true;
-	}
+            boolean includeNonSubService = false;
+            for (String mapping : mappings) {
+                if (mapping.contains(":")) {
+                    String subService = StringUtils.substringAfter(mapping, ":");
+                    pw.write("// Example using Sub Service " + subService
+                            + "<br/>ResourceResolver resolver = resolverFactory.getServiceResourceResolver(new HashMap<String, Object>() {<br/>  private static final long serialVersionUID = 1L;<br/>  {<br/>    put(ResourceResolverFactory.SUBSERVICE,\""
+                            + subService + "\");<br/>  }<br/>});<br/><br/>");
+                } else {
+                    includeNonSubService = true;
+                }
+            }
+            if (includeNonSubService) {
+                pw.write(
+                        "// Example using bundle authentication<br/>ResourceResolver resolver = resolverFactory.getServiceResourceResolver(null);");
+            }
+            pw.write("</code></pre>");
+        } finally {
+            if (this.needsAdministrativeResolver(request) && resolver != null) {
+                resolver.close();
+            }
+        }
+    }
+
+    private void printServiceUsers(HttpServletRequest request, PrintWriter pw) throws LoginException {
+
+        pw.println("<form method='post' action='/system/console/serviceusers'>");
+
+        tableStart(pw, "Create Service User", 2);
+
+        String name = getParameter(request, PN_NAME, "");
+        textField(pw, "Service User Name", PN_NAME, name, "The name of the service user to create, can already exist");
+
+        tableRows(pw);
+        String userContextPath = getParameter(request, PN_USER_PATH, "");
+        textField(pw, "Intermediate Path", PN_USER_PATH, userContextPath,
+                "Optional: The intermediate path under which to create the user. Should start with system, e.g. system/myapp");
+
+        tableRows(pw);
+        String bundle = getParameter(request, PN_BUNDLE, "");
+        selectField(pw, "Bundle", PN_BUNDLE, bundle, getBundles(),
+                "The bundle from which this service user will be useable");
+
+        tableRows(pw);
+        String serviceName = getParameter(request, PN_SUB_SERVICE, "");
+        textField(pw, "Sub Service Name", PN_SUB_SERVICE, serviceName,
+                "Optional: Allows for different permissions for different services within a bundle");
+
+        tableRows(pw);
+        String appPath = getParameter(request, PN_APP_PATH, "");
+        textField(pw, "Application Path", PN_APP_PATH, appPath,
+                "The application under which to create the OSGi Configuration for the Service User Mapping, e.g. /apps/myapp");
+
+        tableRows(pw);
+
+        List<Pair<String, String>> privileges = getPrivileges(request);
+        printPrivilegeSelect(pw, "ACLs", privileges, getSupportedPrivileges(request),
+                "Set the privileges for this service user");
+
+        tableRows(pw);
+
+        pw.println("<td></td>");
+        pw.println("<td><input type='submit' value='Create / Update'/></td>");
+        tableEnd(pw);
+
+        pw.println("</form>");
+
+        pw.println("<br/><br/>");
+
+        // Service Users
+        List<Mapping> activeMappings = mapper.getActiveMappings();
+        tableStart(pw, "Active Service Users", 3);
+        pw.println("<th>Name</th>");
+        pw.println("<th>Bundle</th>");
+        pw.println("<th>SubService</th>");
+        printPrincipals(activeMappings, pw);
+
+        tableEnd(pw);
+
+        pw.println(BR);
+    }
+
+    @Override
+    protected void renderContent(HttpServletRequest request, HttpServletResponse response)
+            throws ServletException, IOException {
+
+        final PrintWriter pw = response.getWriter();
+
+        pw.println(BR);
+
+        String alert = getParameter(request, PN_ALERT, "");
+        if (StringUtils.isNotBlank(alert)) {
+            info(pw, alert);
+        }
+
+        String action = getParameter(request, PN_ACTION, "");
+        if (StringUtils.isBlank(action)) {
+            log.debug("Rendering service users page");
+            info(pw, "Service users are used by OSGi Services to access the Sling repository. Use this form to find and create service users.");
+
+            try {
+                printServiceUsers(request, pw);
+            } catch (LoginException e) {
+                log.warn("Exception rendering service users", e);
+                info(pw, "Exception rendering service users");
+            }
+        } else if ("details".equals(action)) {
+            log.debug("Rendering service user details page");
+            try {
+                printServiceUserDetails(request, pw);
+            } catch (RepositoryException | LoginException e) {
+                log.warn("Exception rendering details for user", e);
+                info(pw, "Exception rendering details for user");
+            }
+        } else {
+            info(pw, "Unknown action: " + action);
+        }
+    }
+
+    private void selectField(PrintWriter pw, String label, String fieldName, String value, Collection<String> options,
+            String... alertMessages) {
+        pw.print(TD_STYLE_WIDTH_20);
+        pw.print(xss.encodeForHTMLAttr(label));
+        pw.println(TD);
+        pw.print("<td><input type=\"text\" list=\"data-" + xss.encodeForHTMLAttr(fieldName) + "\" name='");
+        pw.print(xss.encodeForHTMLAttr(fieldName));
+        pw.print("' value='");
+        pw.print(xss.encodeForHTMLAttr(StringUtils.defaultString(value)));
+        pw.print(STYLE_WIDTH_100);
+        pw.print("<datalist id=\"data-" + xss.encodeForHTMLAttr(fieldName) + "\">");
+        for (String option : options) {
+            pw.print("<option");
+            pw.print(">");
+            pw.print(xss.encodeForHTMLAttr(option));
+            pw.print("</option>");
+        }
+        pw.print("</datalist>");
+        for (String alertMessage : alertMessages) {
+            infoDiv(pw, alertMessage);
+        }
+        pw.println(TD);
+    }
+
+    private void sendErrorRedirect(HttpServletRequest request, HttpServletResponse response, String alert)
+            throws IOException {
+        List<String> params = new ArrayList<>();
+        for (String param : new String[] { PN_APP_PATH, PN_BUNDLE, PN_NAME, PN_SUB_SERVICE, PN_USER_PATH }) {
+            params.add(param + "="
+                    + URLEncoder.encode(this.getParameter(request, param, ""), StandardCharsets.UTF_8.toString()));
+        }
+
+        int idx = 0;
+        List<Pair<String, String>> privs = getPrivileges(request);
+        for (Pair<String, String> priv : privs) {
+            params.add("acl-path-" + idx + "=" + URLEncoder.encode(priv.getKey(), StandardCharsets.UTF_8.toString()));
+            params.add("acl-privilege-" + idx + "="
+                    + URLEncoder.encode(priv.getValue(), StandardCharsets.UTF_8.toString()));
+            idx++;
+        }
+
+        if (StringUtils.isNotBlank(alert)) {
+            params.add(PN_ALERT + "=" + URLEncoder.encode(alert, "UTF-8"));
+        }
+
+        WebConsoleUtil.sendRedirect(request, response,
+                "/system/console/" + LABEL + "?" + StringUtils.join(params, "&"));
+    }
+
+    private void tableEnd(PrintWriter pw) {
+        pw.println(TR);
+        pw.println("</tbody>");
+        pw.println("</table>");
+    }
+
+    private void tableRows(PrintWriter pw) {
+        pw.println(TR);
+        pw.println("<tr>");
+    }
+
+    private void tableStart(PrintWriter pw, String title, int colspan) {
+        pw.println("<table class='nicetable ui-widget'>");
+        pw.println("<thead class='ui-widget-header'>");
+        pw.println("<tr>");
+        pw.print("<th colspan=");
+        pw.print(String.valueOf(colspan));
+        pw.print(">");
+        pw.print(xss.encodeForHTML(title));
+        pw.println("</th>");
+        pw.println(TR);
+        pw.println("</thead>");
+        pw.println("<tbody class='ui-widget-content'>");
+        pw.println("<tr>");
+    }
+
+    private void td(PrintWriter pw, Object value, String... title) {
+        pw.print("<td");
+        if (title.length > 0 && !StringUtils.isBlank(title[0])) {
+            pw.print(" title='");
+            pw.print(xss.encodeForHTML(title[0]));
+            pw.print("'");
+        }
+        pw.print(">");
+
+        if (value != null) {
+            if (value.getClass().isArray()) {
+                for (int i = 0; i < Array.getLength(value); i++) {
+                    Object itemValue = Array.get(value, i);
+                    pw.print(xss.encodeForHTML(ObjectUtils.defaultIfNull(itemValue, "").toString()));
+                    pw.println("<br>");
+                }
+            } else {
+                pw.print(xss.encodeForHTML(value.toString()));
+            }
+        }
+
+        if (title.length > 0 && !StringUtils.isBlank(title[0])) {
+            pw.print("<span class='ui-icon ui-icon-info' style='float:left'></span>");
+        }
+        pw.print(TD);
+    }
+
+    private void textField(PrintWriter pw, String label, String fieldName, String value, String... alertMessages) {
+        pw.print(TD_STYLE_WIDTH_20);
+        pw.print(xss.encodeForHTMLAttr(label));
+        pw.println(TD);
+        pw.print("<td><input name='");
+        pw.print(xss.encodeForHTMLAttr(fieldName));
+        pw.print("' value='");
+        pw.print(xss.encodeForHTMLAttr(StringUtils.defaultString(value)));
+        pw.print("' style='width:100%'/>");
+        for (String alertMessage : alertMessages) {
+            infoDiv(pw, alertMessage);
+        }
+        pw.println(TD);
+    }
+
+    private boolean updatePrivileges(HttpServletRequest request, ResourceResolver resolver) {
+
+        List<Pair<String, String>> privileges = this.getPrivileges(request);
+        String name = getParameter(request, PN_NAME, "");
+
+        List<String> currentPolicies = new ArrayList<>();
+        findACLs(resolver, name, currentPolicies);
+        for (int i = 0; i < currentPolicies.size(); i++) {
+            String path = StringUtils.substringBefore(currentPolicies.get(i), "/rep:policy");
+            currentPolicies.set(i, StringUtils.isNotBlank(path) ? path : "/");
+        }
+        log.debug("Loaded current policy paths: {}", currentPolicies);
+
+        Map<String, List<String>> toSet = new HashMap<>();
+        for (Pair<String, String> privilege : privileges) {
+            if (!toSet.containsKey(privilege.getKey())) {
+                toSet.put(privilege.getKey(), new ArrayList<String>());
+            }
+            toSet.get(privilege.getKey()).add(privilege.getValue());
+        }
+        log.debug("Loaded updated policy paths: {}", currentPolicies);
+
+        String lastEntry = null;
+
+        try {
+
+            Session session = resolver.adaptTo(Session.class);
+            AccessControlManager accessManager = session.getAccessControlManager();
+            PrincipalManager principalManager = AccessControlUtil.getPrincipalManager(session);
+
+            for (Entry<String, List<String>> pol : toSet.entrySet()) {
+                lastEntry = pol.getKey();
+                currentPolicies.remove(pol.getKey());
+                log.debug("Updating policies for {}", pol.getKey());
+
+                AccessControlPolicy[] policies = accessManager.getPolicies(pol.getKey());
+                List<String> toRemove = new ArrayList<>();
+                for (AccessControlPolicy p : policies) {
+                    if (p instanceof AccessControlList) {
+                        AccessControlList policy = (AccessControlList) p;
+                        for (AccessControlEntry entry : policy.getAccessControlEntries()) {
+                            Principal prin = entry.getPrincipal();
+                            if (prin.getName().equals(name)) {
+                                for (Privilege privilege : entry.getPrivileges()) {
+                                    if (!pol.getValue().contains(privilege.getName())) {
+                                        log.debug("Removing privilege {}", privilege);
+                                        toRemove.add(privilege.getName());
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+                Principal principal = principalManager.getPrincipal(name);
+                AccessControlUtil.replaceAccessControlEntry(session, pol.getKey(), principal,
+                        pol.getValue().toArray(new String[pol.getValue().size()]), new String[0],
+                        toRemove.toArray(new String[toRemove.size()]), null);
+            }
+            session.save();
+
+            for (String oldPolicy : currentPolicies) {
+                boolean removed = false;
+                log.debug("Removing policy for {}", oldPolicy);
+                AccessControlPolicy[] policies = accessManager.getPolicies(oldPolicy);
+                AccessControlEntry toRemove = null;
+                for (AccessControlPolicy p : policies) {
+                    if (p instanceof AccessControlList) {
+                        AccessControlList policy = (AccessControlList) p;
+                        for (AccessControlEntry entry : policy.getAccessControlEntries()) {
+                            Principal prin = entry.getPrincipal();
+                            if (prin.getName().equals(name)) {
+                                toRemove = entry;
+                                break;
+                            }
+                        }
+                        if (toRemove != null) {
+                            removed = true;
+                            policy.removeAccessControlEntry(toRemove);
+                            accessManager.setPolicy(oldPolicy, policy);
+                            session.save();
+                            log.debug("Removed access control entry {}", toRemove);
+                        }
+                    }
+                }
+                if (!removed) {
+                    log.warn("No policy found for {}", oldPolicy);
+                }
+            }
+        } catch (RepositoryException e) {
+            log.error("Exception updating principals with {}, failed on {}", toSet, lastEntry, e);
+            return false;
+        }
+
+        return true;
+    }
 
 }


[sling-org-apache-sling-serviceuser-webconsole] 02/16: Updating to released version of Service User Mapper

Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceuser-webconsole.git

commit cf451cd5c8af1972e1e74f0957cfd3a25dcdb9ee
Author: Dan Klco <da...@gmail.com>
AuthorDate: Fri Jan 12 16:05:19 2018 -0500

    Updating to released version of Service User Mapper
---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index 1b39b13..616decd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -73,7 +73,7 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
 			<artifactId>org.apache.sling.serviceusermapper</artifactId>
-			<version>1.3.7-SNAPSHOT</version>
+			<version>1.4.0</version>
             <scope>provided</scope>
         </dependency>
         


[sling-org-apache-sling-serviceuser-webconsole] 01/16: Initial code commit for the ServiceUser WebConsole for SLING-7368

Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceuser-webconsole.git

commit 7e9d6eb0f10b6897219f62901e55b4cc97e7a84d
Author: Dan Klco <da...@gmail.com>
AuthorDate: Fri Jan 12 14:47:28 2018 -0500

    Initial code commit for the ServiceUser WebConsole for SLING-7368
---
 .gitignore                                         |  17 +
 LICENSE                                            | 202 +++++
 README.md                                          |   3 +
 pom.xml                                            | 154 ++++
 .../impl/ServiceUserWebConsolePlugin.java          | 905 +++++++++++++++++++++
 .../sling/serviceuser/webconsole/package-info.java |  22 +
 src/main/resources/res/ui/serviceusermanager.js    |  31 +
 7 files changed, 1334 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5b783ed
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,17 @@
+/target
+.idea
+.classpath
+.metadata
+.project
+.settings
+.externalToolBuilders
+maven-eclipse.xml
+*.swp
+*.iml
+*.ipr
+*.iws
+*.bak
+.vlt
+.DS_Store
+jcr.log
+atlassian-ide-plugin.xml
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ea34da6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+# Apache Sling Service User Console
+
+This module is part of the [Apache Sling](https://sling.apache.org) project.
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..1b39b13
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,154 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+    
+    http://www.apache.org/licenses/LICENSE-2.0
+    
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling</artifactId>
+        <version>30</version>
+        <relativePath />
+    </parent>
+
+    <artifactId>org.apache.sling.serviceuser.webconsole</artifactId>
+    <packaging>bundle</packaging>
+    <version>1.0.0-SNAPSHOT</version>
+
+    <name>Apache Sling Service User Web Console</name>
+    <description>
+        Provides an OSGi Web Console for creating, updating and viewing Service Users.
+    </description>
+
+    <scm>
+        <connection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceuser-webconsole.git</connection>
+        <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceuser-webconsole.git</developerConnection>
+        <url>https://gitbox.apache.org/repos/asf?p=sling-org-apache-sling-serviceuser-webconsole.git</url>
+      <tag>HEAD</tag>
+  </scm>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <configuration>
+                    <excludePackageNames>
+                        org.apache.sling.serviceuser.console.impl
+                    </excludePackageNames>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.api</artifactId>
+            <version>2.5.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+			<artifactId>org.apache.sling.serviceusermapper</artifactId>
+			<version>1.3.7-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
+        
+        <!-- JCR Specific items -->
+        <dependency>
+            <groupId>org.apache.jackrabbit</groupId>
+            <artifactId>jackrabbit-api</artifactId>
+            <version>2.10.6</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.jcr</groupId>
+            <artifactId>jcr</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.jcr.base</artifactId>
+            <version>2.1.0</version>
+            <scope>provided</scope>
+        </dependency>
+  
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.core</artifactId>
+        </dependency>
+        <dependency>
+        	<groupId>org.osgi</groupId>
+        	<artifactId>org.osgi.compendium</artifactId>
+        	<version>4.2.0</version>
+        	<scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.3.2</version>
+            <scope>provided</scope>
+        </dependency>
+        
+        <!-- Webconsole Dependencies -->
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.webconsole</artifactId>
+            <version>4.2.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.xss</artifactId>
+            <version>1.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- Testing -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <version>1.9.5</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/src/main/java/org/apache/sling/serviceuser/webconsole/impl/ServiceUserWebConsolePlugin.java b/src/main/java/org/apache/sling/serviceuser/webconsole/impl/ServiceUserWebConsolePlugin.java
new file mode 100644
index 0000000..fd9ab5f
--- /dev/null
+++ b/src/main/java/org/apache/sling/serviceuser/webconsole/impl/ServiceUserWebConsolePlugin.java
@@ -0,0 +1,905 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.serviceuser.webconsole.impl;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.reflect.Array;
+import java.net.URLEncoder;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.query.Query;
+import javax.jcr.security.AccessControlEntry;
+import javax.jcr.security.AccessControlList;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.AccessControlPolicy;
+import javax.jcr.security.Privilege;
+import javax.servlet.Servlet;
+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.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.felix.webconsole.SimpleWebConsolePlugin;
+import org.apache.felix.webconsole.WebConsoleConstants;
+import org.apache.felix.webconsole.WebConsoleUtil;
+import org.apache.jackrabbit.api.security.principal.PrincipalManager;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.jcr.base.util.AccessControlUtil;
+import org.apache.sling.serviceusermapping.Mapping;
+import org.apache.sling.serviceusermapping.ServiceUserMapper;
+import org.apache.sling.xss.XSSAPI;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferencePolicyOption;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Web console plugin to test configuration resolution.
+ */
+@Component(service = Servlet.class, property = {
+		Constants.SERVICE_DESCRIPTION + "=Apache Sling Service User Manager Web Console Plugin",
+		WebConsoleConstants.PLUGIN_LABEL + "=" + ServiceUserWebConsolePlugin.LABEL,
+		WebConsoleConstants.PLUGIN_TITLE + "=" + ServiceUserWebConsolePlugin.TITLE,
+		WebConsoleConstants.PLUGIN_CATEGORY + "=Sling" })
+@SuppressWarnings("serial")
+public class ServiceUserWebConsolePlugin extends SimpleWebConsolePlugin {
+
+	public ServiceUserWebConsolePlugin() {
+		super(LABEL, TITLE, "Sling", new String[0]);
+	}
+
+	public static final String COMPONENT_NAME = "org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended";
+	public static final String LABEL = "serviceusers";
+	public static final String TITLE = "Service Users";
+
+	public static final String PN_ACTION = "action";
+	public static final String PN_ALERT = "alert";
+	public static final String PN_APP_PATH = "appPath";
+	public static final String PN_BUNDLE = "bundle";
+	public static final String PN_NAME = "name";
+	public static final String PN_SUB_SERVICE = "subService";
+	public static final String PN_USER = "user";
+	public static final String PN_USER_PATH = "userPath";
+
+	private static final Logger log = LoggerFactory.getLogger(ServiceUserWebConsolePlugin.class);
+
+	private BundleContext bundleContext;
+
+	@Reference(policyOption = ReferencePolicyOption.GREEDY)
+	private XSSAPI xss;
+
+	@Reference
+	private ServiceUserMapper mapper;
+
+	private boolean createOrUpdateMapping(HttpServletRequest request, ResourceResolver resolver) {
+
+		String appPath = getParameter(request, PN_APP_PATH, "");
+
+		Iterator<Resource> configs = resolver.findResources("SELECT * FROM [sling:OsgiConfig] WHERE ISDESCENDANTNODE(["
+				+ appPath + "]) AND NAME() LIKE '" + COMPONENT_NAME + "%'", Query.JCR_SQL2);
+
+		try {
+			boolean dirty = false;
+			Resource config = null;
+			if (configs.hasNext()) {
+
+				config = configs.next();
+				log.debug("Using existing configuration {}", config);
+			} else {
+				String path = appPath + "/config/" + COMPONENT_NAME + "-" + appPath.substring(appPath.lastIndexOf('/'));
+				log.debug("Creating new configuration {}", path);
+				config = ResourceUtil.getOrCreateResource(resolver, path, new HashMap<String, Object>() {
+					{
+						put(Property.JCR_PRIMARY_TYPE, "sling:OsgiConfig");
+					}
+				}, NodeType.NT_FOLDER, false);
+				dirty = true;
+			}
+
+			String bundle = getParameter(request, PN_BUNDLE, "");
+			String subService = getParameter(request, PN_SUB_SERVICE, "");
+			String name = getParameter(request, PN_NAME, "");
+			String mapping = bundle + (StringUtils.isNotBlank(subService) ? ":" + subService : "") + "=" + name;
+
+			ModifiableValueMap properties = config.adaptTo(ModifiableValueMap.class);
+			String[] mappings = properties.get("user.mapping", new String[0]);
+			if (!ArrayUtils.contains(mappings, mapping)) {
+				log.debug("Adding {} into service user mapping", mapping);
+				List<String> m = new ArrayList<String>();
+				m.addAll(Arrays.asList(mappings));
+				m.add(mapping);
+				properties.put("user.mapping", m.toArray(new String[m.size()]));
+				dirty = true;
+			} else {
+				log.debug("Already found {} in service user mapping", mapping);
+			}
+			if (dirty) {
+				log.debug("Saving changes to osgi config");
+				resolver.commit();
+			}
+		} catch (PersistenceException e) {
+			log.warn("Exception creating service mapping", e);
+			return false;
+		}
+
+		return true;
+	}
+
+	@Override
+	protected void doPost(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException {
+		log.debug("Creating service user");
+
+		if (StringUtils.isBlank(getParameter(request, PN_NAME, ""))
+				|| StringUtils.isBlank(getParameter(request, PN_BUNDLE, ""))
+				|| StringUtils.isBlank(getParameter(request, PN_APP_PATH, ""))) {
+			sendErrorRedirect(request, response, "Missing required parameters!");
+			return;
+		}
+
+		ResourceResolver resolver = getResourceResolver(request);
+		if (resolver == null) {
+			log.warn("Unable to get serviceresolver from request!");
+			sendErrorRedirect(request, response, "Unable to get serviceresolver from request!");
+			return;
+		} else {
+			Resource userResource = getOrCreateServiceUser(request, resolver);
+			if (userResource == null) {
+				log.warn("Unable to create service user!");
+				sendErrorRedirect(request, response, "Unable to create service user!");
+				return;
+			} else {
+				if (createOrUpdateMapping(request, resolver)) {
+					if (updatePrivileges(request, resolver)) {
+						List<String> params = new ArrayList<String>();
+						params.add(PN_ACTION + "=" + "details");
+						params.add(PN_ALERT + "="
+								+ URLEncoder.encode(
+										"Service user " + userResource.getName() + " created / updated successfully!",
+										"UTF-8"));
+						params.add(PN_USER + "=" + URLEncoder.encode(userResource.getName(), "UTF-8"));
+
+						WebConsoleUtil.sendRedirect(request, response,
+								"/system/console/" + LABEL + "?" + StringUtils.join(params, "&"));
+					} else {
+						sendErrorRedirect(request, response, "Unable to update service user permissions!");
+					}
+				} else {
+					sendErrorRedirect(request, response, "Unable to create service user mapping!");
+				}
+			}
+		}
+
+	}
+
+	private boolean updatePrivileges(HttpServletRequest request, ResourceResolver resolver) {
+
+		List<Pair<String, String>> privileges = this.getPrivileges(request);
+		String name = getParameter(request, PN_NAME, "");
+
+		List<String> currentPolicies = new ArrayList<String>();
+		findACLs(resolver, name, currentPolicies);
+		for (int i = 0; i < currentPolicies.size(); i++) {
+			String path = StringUtils.substringBefore(currentPolicies.get(i), "/rep:policy");
+			currentPolicies.set(i, StringUtils.isNotBlank(path) ? path : "/");
+		}
+		log.debug("Loaded current policy paths: {}", currentPolicies);
+
+		Map<String, List<String>> toSet = new HashMap<String, List<String>>();
+		for (Pair<String, String> privilege : privileges) {
+			if (!toSet.containsKey(privilege.getKey())) {
+				toSet.put(privilege.getKey(), new ArrayList<String>());
+			}
+			toSet.get(privilege.getKey()).add(privilege.getValue());
+		}
+		log.debug("Loaded updated policy paths: {}", currentPolicies);
+
+		String lastEntry = null;
+
+		try {
+
+			Session session = resolver.adaptTo(Session.class);
+			AccessControlManager accessManager = session.getAccessControlManager();
+			PrincipalManager principalManager = AccessControlUtil.getPrincipalManager(session);
+
+			for (Entry<String, List<String>> pol : toSet.entrySet()) {
+				lastEntry = pol.getKey();
+				currentPolicies.remove(pol.getKey());
+				log.debug("Updating policies for {}", pol.getKey());
+
+				AccessControlPolicy[] policies = accessManager.getPolicies(pol.getKey());
+				List<String> toRemove = new ArrayList<String>();
+				for (AccessControlPolicy p : policies) {
+					if (p instanceof AccessControlList) {
+						AccessControlList policy = (AccessControlList) p;
+						for (AccessControlEntry entry : policy.getAccessControlEntries()) {
+							Principal prin = entry.getPrincipal();
+							if (prin.getName().equals(name)) {
+								for (Privilege privilege : entry.getPrivileges()) {
+									if (!pol.getValue().contains(privilege.getName())) {
+										log.debug("Removing privilege {}", privilege);
+										toRemove.add(privilege.getName());
+									}
+								}
+							}
+						}
+					}
+				}
+				Principal principal = principalManager.getPrincipal(name);
+				AccessControlUtil.replaceAccessControlEntry(session, pol.getKey(), principal,
+						pol.getValue().toArray(new String[pol.getValue().size()]), new String[0],
+						toRemove.toArray(new String[toRemove.size()]), null);
+			}
+			session.save();
+
+			for (String oldPolicy : currentPolicies) {
+				boolean removed = false;
+				log.debug("Removing policy for {}", oldPolicy);
+				AccessControlPolicy[] policies = accessManager.getPolicies(oldPolicy);
+				AccessControlEntry toRemove = null;
+				for (AccessControlPolicy p : policies) {
+					if (p instanceof AccessControlList) {
+						AccessControlList policy = (AccessControlList) p;
+						for (AccessControlEntry entry : policy.getAccessControlEntries()) {
+							Principal prin = entry.getPrincipal();
+							if (prin.getName().equals(name)) {
+								toRemove = entry;
+								break;
+							}
+						}
+						if (toRemove != null) {
+							removed = true;
+							policy.removeAccessControlEntry(toRemove);
+							accessManager.setPolicy(oldPolicy, policy);
+							session.save();
+							log.debug("Removed access control entry {}", toRemove);
+						}
+					}
+				}
+				if (!removed) {
+					log.warn("No policy found for {}", oldPolicy);
+				}
+			}
+		} catch (RepositoryException e) {
+			log.error("Exception updating principals with {}, failed on {}", toSet, lastEntry, e);
+			return false;
+		}
+
+		return true;
+	}
+
+	private List<String> extractPrincipals(Mapping mapping) {
+		List<String> principals = new ArrayList<String>();
+		String userName = mapping.map(mapping.getServiceName(), mapping.getSubServiceName());
+		if (StringUtils.isNotBlank(userName)) {
+			principals.add(userName);
+		}
+		Iterable<String> ps = mapping.mapPrincipals(mapping.getServiceName(), mapping.getSubServiceName());
+		if (ps != null) {
+			for (String principal : ps) {
+				principals.add(principal);
+			}
+		}
+		return principals;
+	}
+
+	private String[] findACLs(ResourceResolver resolver, String name, List<String> affectedPaths) {
+		List<String> acls = new ArrayList<String>();
+
+		Iterator<Resource> aclResources = resolver.findResources(
+				"SELECT * FROM [rep:GrantACE] AS s WHERE  [rep:principalName] = '" + name + "'", Query.JCR_SQL2);
+		while (aclResources.hasNext()) {
+			Resource aclResource = aclResources.next();
+			affectedPaths.add(aclResource.getPath());
+			ValueMap properties = aclResource.adaptTo(ValueMap.class);
+			String acl = aclResource.getPath().substring(0, aclResource.getPath().indexOf("/rep:policy")) + "="
+					+ StringUtils.join(properties.get("rep:privileges", String[].class), ",");
+			acls.add(acl);
+		}
+		return acls.toArray(new String[acls.size()]);
+	}
+
+	private Bundle findBundle(String symbolicName, Map<String, Bundle> bundles) {
+		if (bundles.isEmpty()) {
+			for (Bundle bundle : bundleContext.getBundles()) {
+				bundles.put(bundle.getSymbolicName(), bundle);
+			}
+		}
+		return bundles.get(symbolicName);
+	}
+
+	private Object findConfigurations(ResourceResolver resolver, String name, List<String> affectedPaths) {
+		List<String> configurations = new ArrayList<String>();
+
+		Iterator<Resource> configResources = resolver.findResources(
+				"SELECT * FROM [sling:OsgiConfig] AS s WHERE (ISDESCENDANTNODE([/apps]) OR ISDESCENDANTNODE([/libs])) AND NAME(s) LIKE 'org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended%' AND [user.mapping] LIKE '%="
+						+ name + "'",
+				Query.JCR_SQL2);
+		while (configResources.hasNext()) {
+			Resource configResource = configResources.next();
+			affectedPaths.add(configResource.getPath());
+			configurations.add(configResource.getPath());
+		}
+		configResources = resolver.findResources(
+				"SELECT * FROM [nt:file] AS s WHERE (ISDESCENDANTNODE([/apps]) OR ISDESCENDANTNODE([/libs])) AND NAME(s) LIKE 'org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended%' AND [jcr:content/jcr:data] LIKE '%="
+						+ name + "%'",
+				Query.JCR_SQL2);
+		while (configResources.hasNext()) {
+			Resource configResource = configResources.next();
+			affectedPaths.add(configResource.getPath());
+			configurations.add(configResource.getPath());
+		}
+
+		return configurations.toArray();
+	}
+
+	private String[] findMappings(ResourceResolver resolver, String name) {
+		List<String> mappings = new ArrayList<String>();
+		for (Mapping map : mapper.getActiveMappings()) {
+			if (name.equals(map.map(map.getServiceName(), map.getSubServiceName())) || hasPrincipal(map, name)) {
+				mappings.add(map.getServiceName()
+						+ (map.getSubServiceName() != null ? (":" + map.getSubServiceName()) : ""));
+			}
+		}
+		return mappings.toArray(new String[mappings.size()]);
+	}
+
+	private Collection<String> getBundles() {
+		List<String> bundles = new ArrayList<String>();
+		for (Bundle bundle : bundleContext.getBundles()) {
+			bundles.add(bundle.getSymbolicName());
+		}
+		Collections.sort(bundles);
+		return bundles;
+	}
+
+	private Resource getOrCreateServiceUser(HttpServletRequest request, ResourceResolver resolver) {
+
+		final String name = getParameter(request, PN_NAME, "");
+
+		Session session = resolver.adaptTo(Session.class);
+		try {
+			UserManager userManager = AccessControlUtil.getUserManager(session);
+			if (userManager.getAuthorizable(name) != null) {
+				Authorizable user = userManager.getAuthorizable(name);
+				log.debug("Using existing user: {}", user);
+				return resolver.getResource(user.getPath());
+			} else {
+
+				final String userPath = getParameter(request, PN_USER_PATH, "system");
+
+				log.debug("Creating new user with name {} and intermediate path {}", name, userPath);
+
+				User user = userManager.createSystemUser(name, userPath);
+				session.save();
+
+				String path = "/home/users/" + userPath + "/" + name;
+				log.debug("Moving {} to {}", user.getPath(), path);
+				session.getWorkspace().move(user.getPath(), path);
+				session.save();
+
+				return resolver.getResource(path);
+			}
+		} catch (RepositoryException e) {
+			log.warn("Exception getting / creating service user {}", name, e);
+			try {
+				session.refresh(false);
+			} catch (RepositoryException e1) {
+				log.error("Unexpected exception reverting changes", e1);
+			}
+		}
+		return null;
+	}
+
+	private String getParameter(final HttpServletRequest request, final String name, final String defaultValue) {
+		String value = request.getParameter(name);
+		if (value != null && !value.trim().isEmpty()) {
+			return value.trim();
+		}
+		return defaultValue;
+	}
+
+	private ResourceResolver getResourceResolver(HttpServletRequest request) {
+		ResourceResolver resolver = (ResourceResolver) request
+				.getAttribute("org.apache.sling.auth.core.ResourceResolver");
+		return resolver;
+	}
+
+	private boolean hasPrincipal(Mapping map, String name) {
+		Iterable<String> principals = map.mapPrincipals(map.getServiceName(), map.getSubServiceName());
+		if (principals != null) {
+			for (String principal : principals) {
+				if (principal.equals(name)) {
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+
+	private void info(PrintWriter pw, String text) {
+		pw.print("<p class='statline ui-state-highlight'>");
+		pw.print(xss.encodeForHTML(text));
+		pw.println("</p>");
+	}
+
+	private void infoDiv(PrintWriter pw, String text) {
+		if (StringUtils.isBlank(text)) {
+			return;
+		}
+		pw.println("<div>");
+		pw.print("<span style='float:left'>");
+		pw.print(xss.encodeForHTML(text));
+		pw.println("</span>");
+		pw.println("</div>");
+	}
+
+	@Activate
+	protected void init(ComponentContext context) {
+		this.bundleContext = context.getBundleContext();
+	}
+
+	private void printPrincipals(List<Mapping> activeMappings, PrintWriter pw) {
+		List<Pair<String, Mapping>> mappings = new ArrayList<Pair<String, Mapping>>();
+		for (Mapping mapping : activeMappings) {
+			for (String principal : extractPrincipals(mapping)) {
+				mappings.add(new ImmutablePair<String, Mapping>(principal, mapping));
+			}
+		}
+		Collections.sort(mappings, new Comparator<Pair<String, Mapping>>() {
+			@Override
+			public int compare(Pair<String, Mapping> o1, Pair<String, Mapping> o2) {
+				if (o1.getKey().equals(o2.getKey())) {
+					return o1.getValue().getServiceName().compareTo(o2.getValue().getServiceName());
+				} else {
+					return o1.getKey().compareTo(o2.getKey());
+				}
+			}
+		});
+
+		for (Pair<String, Mapping> mapping : mappings) {
+			tableRows(pw);
+			pw.println("<td><a href=\"/system/console/serviceusers?action=details&amp;user="
+					+ xss.encodeForHTML(mapping.getKey()) + "\">" + xss.encodeForHTML(mapping.getKey()) + "</a></td>");
+
+			Map<String, Bundle> bundles = new HashMap<String, Bundle>();
+			Bundle bundle = findBundle(mapping.getValue().getServiceName(), bundles);
+			if (bundle != null) {
+				bundleContext.getBundle();
+				pw.println("<td><a href=\"/system/console/bundles/" + bundle.getBundleId() + "\">"
+						+ xss.encodeForHTML(
+								bundle.getHeaders().get(Constants.BUNDLE_NAME) + " (" + bundle.getSymbolicName())
+						+ ")</a></td>");
+				pw.println("<td>" + xss.encodeForHTML(mapping.getValue().getSubServiceName()) + "</td>");
+			} else {
+				bundleContext.getBundle();
+				pw.println("<td>" + xss.encodeForHTML(mapping.getValue().getServiceName()) + "</td>");
+				pw.println("<td>" + xss.encodeForHTML(
+						mapping.getValue().getSubServiceName() != null ? mapping.getValue().getSubServiceName() : "")
+						+ "</td>");
+			}
+		}
+
+	}
+
+	private void printServiceUserDetails(HttpServletRequest request, PrintWriter pw)
+			throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
+		String name = getParameter(request, PN_USER, "");
+
+		tableStart(pw, "Details for " + name, 2);
+
+		ResourceResolver resolver = getResourceResolver(request);
+
+		List<String> affectedPaths = new ArrayList<String>();
+		td(pw, "Service User Name");
+		td(pw, name);
+
+		tableRows(pw);
+
+		td(pw, "User Path");
+		Session session = resolver.adaptTo(Session.class);
+		UserManager userManager = AccessControlUtil.getUserManager(session);
+		if (userManager.getAuthorizable(name) != null) {
+			Authorizable user = userManager.getAuthorizable(name);
+			td(pw, user.getPath());
+			affectedPaths.add(user.getPath());
+		}
+
+		tableRows(pw);
+
+		String[] mappings = findMappings(resolver, name);
+		td(pw, "Mappings");
+		td(pw, mappings);
+
+		tableRows(pw);
+
+		td(pw, "OSGi Configurations");
+		td(pw, findConfigurations(resolver, name, affectedPaths));
+
+		tableRows(pw);
+
+		td(pw, "ACLs");
+		td(pw, findACLs(resolver, name, affectedPaths));
+
+		tableEnd(pw);
+
+		pw.write("<br/>");
+
+		pw.write("<h3>Example Filter</h3>");
+
+		pw.write("<br/>");
+
+		pw.write("<pre><code>&lt;workspaceFilter version=\"1.0\"&gt;<br/>");
+		for (String affectedPath : affectedPaths) {
+			pw.write("  &lt;filter root=\"" + affectedPath + "\" /&gt;<br/>");
+		}
+		pw.write("&lt;/workspaceFilter\"&gt</code></pre>");
+
+		pw.write("<br/>");
+
+		pw.write("<h3>Use Example(s)</h3>");
+
+		pw.write("<br/>");
+
+		pw.write("<pre><code>");
+
+		boolean includeNonSubService = false;
+		for (String mapping : mappings) {
+			if (mapping.contains(":")) {
+				String subService = StringUtils.substringAfter(mapping, ":");
+				pw.write("// Example using Sub Service " + subService
+						+ "<br/>ResourceResolver resolver = resolverFactory.getServiceResourceResolver(new HashMap<String, Object>() {<br/>  private static final long serialVersionUID = 1L;<br/>  {<br/>    put(ResourceResolverFactory.SUBSERVICE,\""
+						+ subService + "\");<br/>  }<br/>});<br/><br/>");
+			} else {
+				includeNonSubService = true;
+			}
+		}
+		if (includeNonSubService) {
+			pw.write(
+					"// Example using bundle authentication<br/>ResourceResolver resolver = resolverFactory.getServiceResourceResolver(null);");
+		}
+		pw.write("</code></pre>");
+	}
+
+	private void printServiceUsers(HttpServletRequest request, PrintWriter pw) {
+
+		try {
+
+			pw.println("<form method='post' action='/system/console/serviceusers'>");
+
+			tableStart(pw, "Create Service User", 2);
+
+			String name = getParameter(request, PN_NAME, "");
+			textField(pw, "Service User Name", PN_NAME, name,
+					"The name of the service user to create, can already exist");
+
+			tableRows(pw);
+			String userContextPath = getParameter(request, PN_USER_PATH, "");
+			textField(pw, "Intermediate Path", PN_USER_PATH, userContextPath,
+					"Optional: The intermediate path under which to create the user. Should start with system, e.g. system/myapp");
+
+			tableRows(pw);
+			String bundle = getParameter(request, PN_BUNDLE, "");
+			selectField(pw, "Bundle", PN_BUNDLE, bundle, getBundles(),
+					"The bundle from which this service user will be useable");
+
+			tableRows(pw);
+			String serviceName = getParameter(request, PN_SUB_SERVICE, "");
+			textField(pw, "Sub Service Name", PN_SUB_SERVICE, serviceName,
+					"Optional: Allows for different permissions for different services within a bundle");
+
+			tableRows(pw);
+			String appPath = getParameter(request, PN_APP_PATH, "");
+			textField(pw, "Application Path", PN_APP_PATH, appPath,
+					"The application under which to create the OSGi Configuration for the Service User Mapping, e.g. /apps/myapp");
+
+			tableRows(pw);
+
+			List<Pair<String, String>> privileges = getPrivileges(request);
+			printPrivilegeSelect(pw, "ACLs", privileges, getSupportedPrivileges(request),
+					"Set the privileges for this service user");
+
+			tableRows(pw);
+
+			pw.println("<td></td>");
+			pw.println("<td><input type='submit' value='Create / Update'/></td>");
+			tableEnd(pw);
+
+			pw.println("</form>");
+
+			pw.println("<br/><br/>");
+
+			// Service Users
+			List<Mapping> activeMappings = mapper.getActiveMappings();
+			tableStart(pw, "Active Service Users", 3);
+			pw.println("<th>Name</th>");
+			pw.println("<th>Bundle</th>");
+			pw.println("<th>SubService</th>");
+			printPrincipals(activeMappings, pw);
+
+			tableEnd(pw);
+
+			pw.println("<br/>");
+
+		} finally {
+		}
+	}
+
+	private List<Pair<String, String>> getPrivileges(HttpServletRequest request) {
+		List<Pair<String, String>> privileges = new ArrayList<Pair<String, String>>();
+		List<String> params = Collections.list(request.getParameterNames());
+
+		for (String param : params) {
+			if (param.startsWith("acl-path-")) {
+				String path = request.getParameter(param);
+				String privilege = request.getParameter(param.replace("-path-", "-privilege-"));
+				if (StringUtils.isNotBlank(path) && StringUtils.isNotBlank(privilege)) {
+					privileges.add(new ImmutablePair<String, String>(path, privilege));
+				} else {
+					log.warn("Unable to load ACL due to missing value {}={}", path, privilege);
+				}
+			}
+		}
+
+		return privileges;
+	}
+
+	private String[] getSupportedPrivileges(HttpServletRequest request) {
+		String[] names = null;
+		try {
+			ResourceResolver resolver = getResourceResolver(request);
+			Session session = resolver.adaptTo(Session.class);
+			AccessControlManager accessControl = session.getAccessControlManager();
+			Privilege[] privileges = accessControl.getSupportedPrivileges("/");
+			names = new String[privileges.length];
+			for (int i = 0; i < privileges.length; i++) {
+				names[i] = privileges[i].getName();
+			}
+			Arrays.sort(names);
+		} catch (RepositoryException re) {
+			log.error("Exception loading Supported Privileges", re);
+		}
+		return names;
+
+	}
+
+	@Override
+	protected void renderContent(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException {
+
+		final PrintWriter pw = response.getWriter();
+
+		pw.println("<br/>");
+
+		String alert = getParameter(request, "alert", "");
+		if (StringUtils.isNotBlank(alert)) {
+			info(pw, alert);
+		}
+
+		String action = getParameter(request, "action", "");
+		if (StringUtils.isBlank(action)) {
+			log.debug("Rendering service users page");
+			info(pw, "Service users are used by OSGi Services to access the Sling repository. Use this form to find and create service users.");
+			printServiceUsers(request, pw);
+		} else if ("details".equals(action)) {
+			log.debug("Rendering service user details page");
+			try {
+				printServiceUserDetails(request, pw);
+			} catch (RepositoryException e) {
+				log.warn("Exception rendering details for user", e);
+				info(pw, "Exception rendering details for user");
+			}
+		} else {
+			info(pw, "Unknown action: " + action);
+		}
+	}
+
+	private void printPrivilegeSelect(PrintWriter pw, String label, List<Pair<String, String>> privileges,
+			String[] supportedPrivileges, String alertMessage) {
+		pw.print("<td style='width:20%'>");
+		pw.print(xss.encodeForHTMLAttr(label));
+		pw.println("</td>");
+		pw.print("<td><table class=\"repeating-container\" style=\"width: 100%\" data-length=\"" + privileges.size()
+				+ "\"><tr><td>Path</td><td>Privilege</td><td></td>");
+
+		int idx = 0;
+		for (Pair<String, String> privilege : privileges) {
+			pw.print("</tr><tr class=\"repeating-item\"><td>");
+
+			pw.print("<input type=\"text\"  name=\"acl-path-" + idx + "\" value='");
+			pw.print(xss.encodeForHTMLAttr(StringUtils.defaultString(privilege.getKey())));
+			pw.print("' style='width:100%' />");
+
+			pw.print("</td><td>");
+
+			pw.print("<input type=\"text\" list=\"data-privileges\" name=\"acl-privilege-" + idx + "\" value='");
+			pw.print(xss.encodeForHTMLAttr(StringUtils.defaultString(privilege.getValue())));
+			pw.print("' style='width:100%' />");
+
+			pw.print("</td><td>");
+
+			pw.print("<input type=\"button\" value=\"&nbsp;-&nbsp;\" class=\"repeating-remove\" /></td>");
+		}
+		pw.print("</tr></table>");
+
+		pw.print("<input type=\"button\" value=\"&nbsp;+&nbsp;\" class=\"repeating-add\" />");
+
+		pw.print("<datalist id=\"data-privileges\">");
+		for (String option : supportedPrivileges) {
+			pw.print("<option");
+			pw.print(">");
+			pw.print(xss.encodeForHTMLAttr(option));
+			pw.print("</option>");
+		}
+		pw.print("</datalist><script src=\"/system/console/serviceusers/res/ui/serviceusermanager.js\"></script>");
+		infoDiv(pw, alertMessage);
+		pw.println("</td>");
+	}
+
+	private void selectField(PrintWriter pw, String label, String fieldName, String value, Collection<String> options,
+			String... alertMessages) {
+		pw.print("<td style='width:20%'>");
+		pw.print(xss.encodeForHTMLAttr(label));
+		pw.println("</td>");
+		pw.print("<td><input type=\"text\" list=\"data-" + xss.encodeForHTMLAttr(fieldName) + "\" name='");
+		pw.print(xss.encodeForHTMLAttr(fieldName));
+		pw.print("' value='");
+		pw.print(xss.encodeForHTMLAttr(StringUtils.defaultString(value)));
+		pw.print("' style='width:100%' />");
+		pw.print("<datalist id=\"data-" + xss.encodeForHTMLAttr(fieldName) + "\">");
+		for (String option : options) {
+			pw.print("<option");
+			pw.print(">");
+			pw.print(xss.encodeForHTMLAttr(option));
+			pw.print("</option>");
+		}
+		pw.print("</datalist>");
+		for (String alertMessage : alertMessages) {
+			infoDiv(pw, alertMessage);
+		}
+		pw.println("</td>");
+	}
+
+	private void sendErrorRedirect(HttpServletRequest request, HttpServletResponse response, String alert)
+			throws IOException {
+		List<String> params = new ArrayList<String>();
+		for (String param : new String[] { PN_APP_PATH, PN_BUNDLE, PN_NAME, PN_SUB_SERVICE, PN_USER_PATH }) {
+			params.add(param + "=" + URLEncoder.encode(this.getParameter(request, param, ""), "UTF-8"));
+		}
+
+		int idx = 0;
+		List<Pair<String, String>> privs = getPrivileges(request);
+		for (Pair<String, String> priv : privs) {
+			params.add("acl-path-" + idx + "=" + URLEncoder.encode(priv.getKey(), "UTF-8"));
+			params.add("acl-privilege-" + idx + "=" + URLEncoder.encode(priv.getValue(), "UTF-8"));
+			idx++;
+		}
+
+		if (StringUtils.isNotBlank(alert)) {
+			params.add(PN_ALERT + "=" + URLEncoder.encode(alert, "UTF-8"));
+		}
+
+		WebConsoleUtil.sendRedirect(request, response,
+				"/system/console/" + LABEL + "?" + StringUtils.join(params, "&"));
+	}
+
+	private void tableEnd(PrintWriter pw) {
+		pw.println("</tr>");
+		pw.println("</tbody>");
+		pw.println("</table>");
+	}
+
+	private void tableRows(PrintWriter pw) {
+		pw.println("</tr>");
+		pw.println("<tr>");
+	}
+
+	private void tableStart(PrintWriter pw, String title, int colspan) {
+		pw.println("<table class='nicetable ui-widget'>");
+		pw.println("<thead class='ui-widget-header'>");
+		pw.println("<tr>");
+		pw.print("<th colspan=");
+		pw.print(String.valueOf(colspan));
+		pw.print(">");
+		pw.print(xss.encodeForHTML(title));
+		pw.println("</th>");
+		pw.println("</tr>");
+		pw.println("</thead>");
+		pw.println("<tbody class='ui-widget-content'>");
+		pw.println("<tr>");
+	}
+
+	private void td(PrintWriter pw, Object value, String... title) {
+		pw.print("<td");
+		if (title.length > 0 && !StringUtils.isBlank(title[0])) {
+			pw.print(" title='");
+			pw.print(xss.encodeForHTML(title[0]));
+			pw.print("'");
+		}
+		pw.print(">");
+
+		if (value != null) {
+			if (value.getClass().isArray()) {
+				for (int i = 0; i < Array.getLength(value); i++) {
+					Object itemValue = Array.get(value, i);
+					pw.print(xss.encodeForHTML(ObjectUtils.defaultIfNull(itemValue, "").toString()));
+					pw.println("<br>");
+				}
+			} else {
+				pw.print(xss.encodeForHTML(value.toString()));
+			}
+		}
+
+		if (title.length > 0 && !StringUtils.isBlank(title[0])) {
+			pw.print("<span class='ui-icon ui-icon-info' style='float:left'></span>");
+		}
+		pw.print("</td>");
+	}
+
+	private void textField(PrintWriter pw, String label, String fieldName, String value, String... alertMessages) {
+		pw.print("<td style='width:20%'>");
+		pw.print(xss.encodeForHTMLAttr(label));
+		pw.println("</td>");
+		pw.print("<td><input name='");
+		pw.print(xss.encodeForHTMLAttr(fieldName));
+		pw.print("' value='");
+		pw.print(xss.encodeForHTMLAttr(StringUtils.defaultString(value)));
+		pw.print("' style='width:100%'/>");
+		for (String alertMessage : alertMessages) {
+			infoDiv(pw, alertMessage);
+		}
+		pw.println("</td>");
+	}
+
+}
diff --git a/src/main/java/org/apache/sling/serviceuser/webconsole/package-info.java b/src/main/java/org/apache/sling/serviceuser/webconsole/package-info.java
new file mode 100644
index 0000000..452b0ba
--- /dev/null
+++ b/src/main/java/org/apache/sling/serviceuser/webconsole/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+@org.osgi.annotation.versioning.Version("1.0.0")
+package org.apache.sling.serviceuser.webconsole;
+
diff --git a/src/main/resources/res/ui/serviceusermanager.js b/src/main/resources/res/ui/serviceusermanager.js
new file mode 100644
index 0000000..c830f5d
--- /dev/null
+++ b/src/main/resources/res/ui/serviceusermanager.js
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *	  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var repeatingRemove = function(){
+	$(this).parents('.repeating-item').remove();
+	return false;
+}
+$('.repeating-remove').click(repeatingRemove);
+$('.repeating-add').click(function(){
+	var idx = $('.repeating-container').data('length');
+	var div = $('.repeating-container').append('<tr class="repeating-item"><td>'+'<input type="text"  name="acl-path-'
+			+ idx + '"  style="width:100%" /></td><td>'+
+			'<input type="text" list="data-privileges" name="acl-privilege-' + idx + '" style="width:100%" />'+
+			'</td><td><input type="button" value="-" class="repeating-remove" /></td></tr>');
+	$('.repeating-container').data('length', idx + 1);
+	$(div).find('.repeating-remove').click(repeatingRemove);
+	return false;
+});
\ No newline at end of file


[sling-org-apache-sling-serviceuser-webconsole] 16/16: Merge branch 'master' of git@github.com:apache/sling-org-apache-sling-serviceuser-webconsole.git

Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceuser-webconsole.git

commit 0d65809b4a6083edf80eff9eabb45a3f7760d90b
Merge: 2a8c0ea ee3e1c0
Author: Dan Klco <dk...@apache.org>
AuthorDate: Fri Apr 26 13:45:55 2019 -0400

    Merge branch 'master' of git@github.com:apache/sling-org-apache-sling-serviceuser-webconsole.git

 Jenkinsfile | 20 ++++++++++++++++++++
 README.md   |  4 ++--
 2 files changed, 22 insertions(+), 2 deletions(-)


[sling-org-apache-sling-serviceuser-webconsole] 07/16: [maven-release-plugin] prepare for next development iteration

Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceuser-webconsole.git

commit c2c5e9bb67d180a39e421bdf7b8a4dae205ff541
Author: Dan Klco <da...@gmail.com>
AuthorDate: Fri Jan 12 17:55:51 2018 -0500

    [maven-release-plugin] prepare for next development iteration
---
 pom.xml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/pom.xml b/pom.xml
index a5a02a2..844b9fe 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
 
 	<artifactId>org.apache.sling.serviceuser.webconsole</artifactId>
 	<packaging>bundle</packaging>
-	<version>1.0.0</version>
+	<version>1.0.1-SNAPSHOT</version>
 
 	<name>Apache Sling Service User Web Console</name>
 	<description>
@@ -32,7 +32,7 @@
 		<connection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceuser-webconsole.git</connection>
 		<developerConnection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceuser-webconsole.git</developerConnection>
 		<url>https://gitbox.apache.org/repos/asf?p=sling-org-apache-sling-serviceuser-webconsole.git</url>
-		<tag>org.apache.sling.serviceuser.webconsole-1.0.0</tag>
+		<tag>HEAD</tag>
 	</scm>
 
 	<build>


[sling-org-apache-sling-serviceuser-webconsole] 08/16: Updating badges for org-apache-sling-serviceuser-webconsole

Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceuser-webconsole.git

commit 80ed9bbb4698997133cc77e21784518a6153580c
Author: Dan Klco <dk...@apache.org>
AuthorDate: Fri Jun 8 15:54:07 2018 -0400

    Updating badges for org-apache-sling-serviceuser-webconsole
---
 README.md | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index ea34da6..e6600b8 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,9 @@
-# Apache Sling Service User Console
+[<img src="http://sling.apache.org/res/logos/sling.png"/>](http://sling.apache.org)
+
+ [![Build Status](https://builds.apache.org/buildStatus/icon?job=sling-org-apache-sling-serviceuser-webconsole-1.8)](https://builds.apache.org/view/S-Z/view/Sling/job/sling-org-apache-sling-serviceuser-webconsole-1.8) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.apache.sling/org.apache.sling.serviceuser.webconsole/badge.svg)](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.apache.sling%22%20a%3A%22org.apache.sling.serviceuser.webconsole%22) [![JavaDocs](ht [...]
+
+# Apache Sling Service User Web Console
 
 This module is part of the [Apache Sling](https://sling.apache.org) project.
+
+Provides an OSGi Web Console for creating, updating and viewing Service Users.


[sling-org-apache-sling-serviceuser-webconsole] 13/16: Updating badges for org-apache-sling-serviceuser-webconsole

Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceuser-webconsole.git

commit ee3e1c0a4ab037e8c1965a8eeee27f360ca86ec1
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Thu Jan 31 13:23:03 2019 +0100

    Updating badges for org-apache-sling-serviceuser-webconsole
---
 README.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index e6600b8..e45681c 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-[<img src="http://sling.apache.org/res/logos/sling.png"/>](http://sling.apache.org)
+[<img src="https://sling.apache.org/res/logos/sling.png"/>](https://sling.apache.org)
 
- [![Build Status](https://builds.apache.org/buildStatus/icon?job=sling-org-apache-sling-serviceuser-webconsole-1.8)](https://builds.apache.org/view/S-Z/view/Sling/job/sling-org-apache-sling-serviceuser-webconsole-1.8) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.apache.sling/org.apache.sling.serviceuser.webconsole/badge.svg)](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.apache.sling%22%20a%3A%22org.apache.sling.serviceuser.webconsole%22) [![JavaDocs](ht [...]
+ [![Build Status](https://builds.apache.org/buildStatus/icon?job=Sling/sling-org-apache-sling-serviceuser-webconsole/master)](https://builds.apache.org/job/Sling/job/sling-org-apache-sling-serviceuser-webconsole/job/master) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.apache.sling/org.apache.sling.serviceuser.webconsole/badge.svg)](https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.apache.sling%22%20a%3A%22org.apache.sling.serviceuser.webconsole%22) [![JavaD [...]
 
 # Apache Sling Service User Web Console
 


[sling-org-apache-sling-serviceuser-webconsole] 09/16: SLING-7216 - [nice-to-have] Add a CODE_OF_CONDUCT file to every module

Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceuser-webconsole.git

commit 490bbed9101c21149c153f96e080ccc5fd5a1d36
Author: Radu Cotescu <co...@adobe.com>
AuthorDate: Wed Sep 19 18:04:03 2018 +0200

    SLING-7216 - [nice-to-have] Add a CODE_OF_CONDUCT file to every module
---
 CODE_OF_CONDUCT.md | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..52f21cb
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,4 @@
+Apache Software Foundation Code of Conduct
+====
+
+Being an Apache project, Apache Sling adheres to the Apache Software Foundation's [Code of Conduct](https://www.apache.org/foundation/policies/conduct.html).


[sling-org-apache-sling-serviceuser-webconsole] 05/16: Fixing an issue where it failed to start without xss compat, wasn't serving the js file and didn't create the app folder correctly

Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceuser-webconsole.git

commit 26b6663275b7f636f0eaaa397a82c3c51c602cea
Author: Dan Klco <da...@gmail.com>
AuthorDate: Fri Jan 12 17:44:11 2018 -0500

    Fixing an issue where it failed to start without xss compat, wasn't serving the js file and didn't create the app folder correctly
---
 pom.xml                                            | 290 +++++++-------
 .../impl/ServiceUserWebConsolePlugin.java          | 417 ++++++++++++---------
 2 files changed, 379 insertions(+), 328 deletions(-)

diff --git a/pom.xml b/pom.xml
index aa6e3d4..97e097e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,154 +1,162 @@
 <?xml version="1.0" encoding="ISO-8859-1"?>
-<!--
-    Licensed to the Apache Software Foundation (ASF) under one
-    or more contributor license agreements.  See the NOTICE file
-    distributed with this work for additional information
-    regarding copyright ownership.  The ASF licenses this file
-    to you under the Apache License, Version 2.0 (the
-    "License"); you may not use this file except in compliance
-    with the License.  You may obtain a copy of the License at
-    
-    http://www.apache.org/licenses/LICENSE-2.0
-    
-    Unless required by applicable law or agreed to in writing,
-    software distributed under the License is distributed on an
-    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-    KIND, either express or implied.  See the License for the
-    specific language governing permissions and limitations
-    under the License.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor 
+	license agreements. See the NOTICE file distributed with this work for additional 
+	information regarding copyright ownership. The ASF licenses this file to 
+	you under the Apache License, Version 2.0 (the "License"); you may not use 
+	this file except in compliance with the License. You may obtain a copy of 
+	the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required 
+	by applicable law or agreed to in writing, software distributed under the 
+	License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 
+	OF ANY KIND, either express or implied. See the License for the specific 
+	language governing permissions and limitations under the License. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 
-    <modelVersion>4.0.0</modelVersion>
-    <parent>
-        <groupId>org.apache.sling</groupId>
-        <artifactId>sling</artifactId>
-        <version>30</version>
-        <relativePath />
-    </parent>
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>org.apache.sling</groupId>
+		<artifactId>sling</artifactId>
+		<version>30</version>
+		<relativePath />
+	</parent>
 
-    <artifactId>org.apache.sling.serviceuser.webconsole</artifactId>
-    <packaging>bundle</packaging>
-    <version>1.0.1-SNAPSHOT</version>
+	<artifactId>org.apache.sling.serviceuser.webconsole</artifactId>
+	<packaging>bundle</packaging>
+	<version>1.0.0-SNAPSHOT</version>
 
-    <name>Apache Sling Service User Web Console</name>
-    <description>
+	<name>Apache Sling Service User Web Console</name>
+	<description>
         Provides an OSGi Web Console for creating, updating and viewing Service Users.
     </description>
 
-    <scm>
-        <connection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceuser-webconsole.git</connection>
-        <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceuser-webconsole.git</developerConnection>
-        <url>https://gitbox.apache.org/repos/asf?p=sling-org-apache-sling-serviceuser-webconsole.git</url>
-      <tag>HEAD</tag>
-  </scm>
+	<scm>
+		<connection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceuser-webconsole.git</connection>
+		<developerConnection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceuser-webconsole.git</developerConnection>
+		<url>https://gitbox.apache.org/repos/asf?p=sling-org-apache-sling-serviceuser-webconsole.git</url>
+		<tag>HEAD</tag>
+	</scm>
 
-    <build>
-        <plugins>
-            <plugin>
-                <groupId>org.apache.felix</groupId>
-                <artifactId>maven-bundle-plugin</artifactId>
-                <extensions>true</extensions>
-                <configuration>
-                </configuration>
-            </plugin>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-javadoc-plugin</artifactId>
-                <configuration>
-                    <excludePackageNames>
-                        org.apache.sling.serviceuser.console.impl
-                    </excludePackageNames>
-                </configuration>
-            </plugin>
-        </plugins>
-    </build>
-    <dependencies>
-        <dependency>
-            <groupId>org.apache.sling</groupId>
-            <artifactId>org.apache.sling.api</artifactId>
-            <version>2.5.0</version>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.sling</groupId>
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.felix</groupId>
+				<artifactId>maven-bundle-plugin</artifactId>
+				<extensions>true</extensions>
+				<configuration>
+					<instructions>
+						<Import-Package>
+							<!-- Support XSS API 1.x and 2.x - we use only classes from the API 
+								with same signature in both versions -->
+							org.apache.sling.xss;version="[1.0.0,3)",
+							*
+						</Import-Package>
+					</instructions>
+				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-javadoc-plugin</artifactId>
+				<configuration>
+					<excludePackageNames>
+						org.apache.sling.serviceuser.console.impl
+					</excludePackageNames>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+	<dependencies>
+		<dependency>
+			<groupId>org.apache.sling</groupId>
+			<artifactId>org.apache.sling.api</artifactId>
+			<version>2.5.0</version>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.sling</groupId>
 			<artifactId>org.apache.sling.serviceusermapper</artifactId>
 			<version>1.4.0</version>
-            <scope>provided</scope>
-        </dependency>
-        
-        <!-- JCR Specific items -->
-        <dependency>
-            <groupId>org.apache.jackrabbit</groupId>
-            <artifactId>jackrabbit-api</artifactId>
-            <version>2.10.6</version>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>javax.jcr</groupId>
-            <artifactId>jcr</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.sling</groupId>
-            <artifactId>org.apache.sling.jcr.base</artifactId>
-            <version>2.1.0</version>
-            <scope>provided</scope>
-        </dependency>
-  
-        <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>slf4j-api</artifactId>
-        </dependency>
+			<scope>provided</scope>
+		</dependency>
 
-        <dependency>
-            <groupId>org.osgi</groupId>
-            <artifactId>osgi.core</artifactId>
-        </dependency>
-        <dependency>
-        	<groupId>org.osgi</groupId>
-        	<artifactId>org.osgi.compendium</artifactId>
-        	<version>4.2.0</version>
-        	<scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>javax.servlet</groupId>
-            <artifactId>javax.servlet-api</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.commons</groupId>
-            <artifactId>commons-lang3</artifactId>
-            <version>3.3.2</version>
-            <scope>provided</scope>
-        </dependency>
-        
-        <!-- Webconsole Dependencies -->
-        <dependency>
-            <groupId>org.apache.felix</groupId>
-            <artifactId>org.apache.felix.webconsole</artifactId>
-            <version>4.2.0</version>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.sling</groupId>
-            <artifactId>org.apache.sling.xss</artifactId>
-            <version>1.0.0</version>
-            <scope>provided</scope>
-        </dependency>
+		<!-- JCR Specific items -->
+		<dependency>
+			<groupId>org.apache.jackrabbit</groupId>
+			<artifactId>jackrabbit-api</artifactId>
+			<version>2.10.6</version>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>javax.jcr</groupId>
+			<artifactId>jcr</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.sling</groupId>
+			<artifactId>org.apache.sling.jcr.base</artifactId>
+			<version>2.1.0</version>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.jackrabbit</groupId>
+			<artifactId>jackrabbit-jcr-commons</artifactId>
+			<version>2.0.0</version>
+			<scope>provided</scope>
+		</dependency>
 
-        <!-- Testing -->
-        <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>slf4j-simple</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.mockito</groupId>
-            <artifactId>mockito-all</artifactId>
-            <version>1.9.5</version>
-            <scope>test</scope>
-        </dependency>
-    </dependencies>
+
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-api</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>osgi.core</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>org.osgi.compendium</artifactId>
+			<version>4.2.0</version>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>javax.servlet</groupId>
+			<artifactId>javax.servlet-api</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.commons</groupId>
+			<artifactId>commons-lang3</artifactId>
+			<version>3.3.2</version>
+			<scope>provided</scope>
+		</dependency>
+
+		<!-- Webconsole Dependencies -->
+		<dependency>
+			<groupId>org.apache.felix</groupId>
+			<artifactId>org.apache.felix.webconsole</artifactId>
+			<version>4.2.0</version>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.sling</groupId>
+			<artifactId>org.apache.sling.xss</artifactId>
+			<version>1.0.0</version>
+			<scope>provided</scope>
+		</dependency>
+
+		<!-- Testing -->
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-simple</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.mockito</groupId>
+			<artifactId>mockito-all</artifactId>
+			<version>1.9.5</version>
+			<scope>test</scope>
+		</dependency>
+	</dependencies>
 </project>
diff --git a/src/main/java/org/apache/sling/serviceuser/webconsole/impl/ServiceUserWebConsolePlugin.java b/src/main/java/org/apache/sling/serviceuser/webconsole/impl/ServiceUserWebConsolePlugin.java
index fd9ab5f..5d952fa 100644
--- a/src/main/java/org/apache/sling/serviceuser/webconsole/impl/ServiceUserWebConsolePlugin.java
+++ b/src/main/java/org/apache/sling/serviceuser/webconsole/impl/ServiceUserWebConsolePlugin.java
@@ -21,6 +21,7 @@ package org.apache.sling.serviceuser.webconsole.impl;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.lang.reflect.Array;
+import java.net.URL;
 import java.net.URLEncoder;
 import java.security.Principal;
 import java.util.ArrayList;
@@ -35,7 +36,6 @@ import java.util.Map;
 import java.util.Map.Entry;
 
 import javax.jcr.AccessDeniedException;
-import javax.jcr.Property;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.UnsupportedRepositoryOperationException;
@@ -56,17 +56,20 @@ import org.apache.commons.lang3.ObjectUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.ImmutablePair;
 import org.apache.commons.lang3.tuple.Pair;
-import org.apache.felix.webconsole.SimpleWebConsolePlugin;
+import org.apache.felix.webconsole.AbstractWebConsolePlugin;
 import org.apache.felix.webconsole.WebConsoleConstants;
 import org.apache.felix.webconsole.WebConsoleUtil;
+import org.apache.jackrabbit.JcrConstants;
 import org.apache.jackrabbit.api.security.principal.PrincipalManager;
 import org.apache.jackrabbit.api.security.user.Authorizable;
 import org.apache.jackrabbit.api.security.user.User;
 import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.sling.api.resource.LoginException;
 import org.apache.sling.api.resource.ModifiableValueMap;
 import org.apache.sling.api.resource.PersistenceException;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
 import org.apache.sling.api.resource.ResourceUtil;
 import org.apache.sling.api.resource.ValueMap;
 import org.apache.sling.jcr.base.util.AccessControlUtil;
@@ -93,11 +96,7 @@ import org.slf4j.LoggerFactory;
 		WebConsoleConstants.PLUGIN_TITLE + "=" + ServiceUserWebConsolePlugin.TITLE,
 		WebConsoleConstants.PLUGIN_CATEGORY + "=Sling" })
 @SuppressWarnings("serial")
-public class ServiceUserWebConsolePlugin extends SimpleWebConsolePlugin {
-
-	public ServiceUserWebConsolePlugin() {
-		super(LABEL, TITLE, "Sling", new String[0]);
-	}
+public class ServiceUserWebConsolePlugin extends AbstractWebConsolePlugin {
 
 	public static final String COMPONENT_NAME = "org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended";
 	public static final String LABEL = "serviceusers";
@@ -119,6 +118,9 @@ public class ServiceUserWebConsolePlugin extends SimpleWebConsolePlugin {
 	@Reference(policyOption = ReferencePolicyOption.GREEDY)
 	private XSSAPI xss;
 
+	@Reference(policyOption = ReferencePolicyOption.GREEDY)
+	private ResourceResolverFactory resolverFactory;
+
 	@Reference
 	private ServiceUserMapper mapper;
 
@@ -137,11 +139,11 @@ public class ServiceUserWebConsolePlugin extends SimpleWebConsolePlugin {
 				config = configs.next();
 				log.debug("Using existing configuration {}", config);
 			} else {
-				String path = appPath + "/config/" + COMPONENT_NAME + "-" + appPath.substring(appPath.lastIndexOf('/'));
+				String path = appPath + "/config/" + COMPONENT_NAME + "-" + appPath.substring(appPath.lastIndexOf('/') + 1);
 				log.debug("Creating new configuration {}", path);
 				config = ResourceUtil.getOrCreateResource(resolver, path, new HashMap<String, Object>() {
 					{
-						put(Property.JCR_PRIMARY_TYPE, "sling:OsgiConfig");
+						put(JcrConstants.JCR_PRIMARYTYPE, "sling:OsgiConfig");
 					}
 				}, NodeType.NT_FOLDER, false);
 				dirty = true;
@@ -223,102 +225,6 @@ public class ServiceUserWebConsolePlugin extends SimpleWebConsolePlugin {
 
 	}
 
-	private boolean updatePrivileges(HttpServletRequest request, ResourceResolver resolver) {
-
-		List<Pair<String, String>> privileges = this.getPrivileges(request);
-		String name = getParameter(request, PN_NAME, "");
-
-		List<String> currentPolicies = new ArrayList<String>();
-		findACLs(resolver, name, currentPolicies);
-		for (int i = 0; i < currentPolicies.size(); i++) {
-			String path = StringUtils.substringBefore(currentPolicies.get(i), "/rep:policy");
-			currentPolicies.set(i, StringUtils.isNotBlank(path) ? path : "/");
-		}
-		log.debug("Loaded current policy paths: {}", currentPolicies);
-
-		Map<String, List<String>> toSet = new HashMap<String, List<String>>();
-		for (Pair<String, String> privilege : privileges) {
-			if (!toSet.containsKey(privilege.getKey())) {
-				toSet.put(privilege.getKey(), new ArrayList<String>());
-			}
-			toSet.get(privilege.getKey()).add(privilege.getValue());
-		}
-		log.debug("Loaded updated policy paths: {}", currentPolicies);
-
-		String lastEntry = null;
-
-		try {
-
-			Session session = resolver.adaptTo(Session.class);
-			AccessControlManager accessManager = session.getAccessControlManager();
-			PrincipalManager principalManager = AccessControlUtil.getPrincipalManager(session);
-
-			for (Entry<String, List<String>> pol : toSet.entrySet()) {
-				lastEntry = pol.getKey();
-				currentPolicies.remove(pol.getKey());
-				log.debug("Updating policies for {}", pol.getKey());
-
-				AccessControlPolicy[] policies = accessManager.getPolicies(pol.getKey());
-				List<String> toRemove = new ArrayList<String>();
-				for (AccessControlPolicy p : policies) {
-					if (p instanceof AccessControlList) {
-						AccessControlList policy = (AccessControlList) p;
-						for (AccessControlEntry entry : policy.getAccessControlEntries()) {
-							Principal prin = entry.getPrincipal();
-							if (prin.getName().equals(name)) {
-								for (Privilege privilege : entry.getPrivileges()) {
-									if (!pol.getValue().contains(privilege.getName())) {
-										log.debug("Removing privilege {}", privilege);
-										toRemove.add(privilege.getName());
-									}
-								}
-							}
-						}
-					}
-				}
-				Principal principal = principalManager.getPrincipal(name);
-				AccessControlUtil.replaceAccessControlEntry(session, pol.getKey(), principal,
-						pol.getValue().toArray(new String[pol.getValue().size()]), new String[0],
-						toRemove.toArray(new String[toRemove.size()]), null);
-			}
-			session.save();
-
-			for (String oldPolicy : currentPolicies) {
-				boolean removed = false;
-				log.debug("Removing policy for {}", oldPolicy);
-				AccessControlPolicy[] policies = accessManager.getPolicies(oldPolicy);
-				AccessControlEntry toRemove = null;
-				for (AccessControlPolicy p : policies) {
-					if (p instanceof AccessControlList) {
-						AccessControlList policy = (AccessControlList) p;
-						for (AccessControlEntry entry : policy.getAccessControlEntries()) {
-							Principal prin = entry.getPrincipal();
-							if (prin.getName().equals(name)) {
-								toRemove = entry;
-								break;
-							}
-						}
-						if (toRemove != null) {
-							removed = true;
-							policy.removeAccessControlEntry(toRemove);
-							accessManager.setPolicy(oldPolicy, policy);
-							session.save();
-							log.debug("Removed access control entry {}", toRemove);
-						}
-					}
-				}
-				if (!removed) {
-					log.warn("No policy found for {}", oldPolicy);
-				}
-			}
-		} catch (RepositoryException e) {
-			log.error("Exception updating principals with {}, failed on {}", toSet, lastEntry, e);
-			return false;
-		}
-
-		return true;
-	}
-
 	private List<String> extractPrincipals(Mapping mapping) {
 		List<String> principals = new ArrayList<String>();
 		String userName = mapping.map(mapping.getServiceName(), mapping.getSubServiceName());
@@ -404,6 +310,11 @@ public class ServiceUserWebConsolePlugin extends SimpleWebConsolePlugin {
 		return bundles;
 	}
 
+	@Override
+	public String getLabel() {
+		return LABEL;
+	}
+
 	private Resource getOrCreateServiceUser(HttpServletRequest request, ResourceResolver resolver) {
 
 		final String name = getParameter(request, PN_NAME, "");
@@ -450,12 +361,86 @@ public class ServiceUserWebConsolePlugin extends SimpleWebConsolePlugin {
 		return defaultValue;
 	}
 
+	private List<Pair<String, String>> getPrivileges(HttpServletRequest request) {
+		List<Pair<String, String>> privileges = new ArrayList<Pair<String, String>>();
+		List<String> params = Collections.list(request.getParameterNames());
+
+		for (String param : params) {
+			if (param.startsWith("acl-path-")) {
+				String path = request.getParameter(param);
+				String privilege = request.getParameter(param.replace("-path-", "-privilege-"));
+				if (StringUtils.isNotBlank(path) && StringUtils.isNotBlank(privilege)) {
+					privileges.add(new ImmutablePair<String, String>(path, privilege));
+				} else {
+					log.warn("Unable to load ACL due to missing value {}={}", path, privilege);
+				}
+			}
+		}
+
+		return privileges;
+	}
+
+	@SuppressWarnings("deprecation")
 	private ResourceResolver getResourceResolver(HttpServletRequest request) {
-		ResourceResolver resolver = (ResourceResolver) request
-				.getAttribute("org.apache.sling.auth.core.ResourceResolver");
+		ResourceResolver resolver = null;
+		try {
+			resolver = (ResourceResolver) request.getAttribute("org.apache.sling.auth.core.ResourceResolver");
+			if (resolver == null) {
+				log.warn("Resource resolver not available in request, falling back to adminstrative resource resolver");
+				resolver = resolverFactory.getAdministrativeResourceResolver(null);
+			}
+		} catch (LoginException le) {
+			throw new RuntimeException(
+					"Unable to get Administrative Resource Resolver, add the bundle org.apache.sling.serviceuser.webconsole in the Apache Sling Login Admin Whitelist",
+					le);
+		}
 		return resolver;
 	}
 
+	/**
+	 * Called internally by {@link AbstractWebConsolePlugin} to load resources.
+	 *
+	 * This particular implementation depends on the label. As example, if the
+	 * plugin is accessed as <code>/system/console/abc</code>, and the plugin
+	 * resources are accessed like
+	 * <code>/system/console/abc/res/logo.gif</code>, the code here will try
+	 * load resource <code>/res/logo.gif</code> from the bundle, providing the
+	 * plugin.
+	 *
+	 *
+	 * @param path
+	 *            the path to read.
+	 * @return the URL of the resource or <code>null</code> if not found.
+	 */
+	protected URL getResource(String path) {
+		String base = "/" + LABEL + "/";
+		return (path != null && path.startsWith(base)) ? getClass().getResource(path.substring(base.length() - 1))
+				: null;
+	}
+
+	private String[] getSupportedPrivileges(HttpServletRequest request) {
+		String[] names = null;
+		try {
+			ResourceResolver resolver = getResourceResolver(request);
+			Session session = resolver.adaptTo(Session.class);
+			AccessControlManager accessControl = session.getAccessControlManager();
+			Privilege[] privileges = accessControl.getSupportedPrivileges("/");
+			names = new String[privileges.length];
+			for (int i = 0; i < privileges.length; i++) {
+				names[i] = privileges[i].getName();
+			}
+			Arrays.sort(names);
+		} catch (RepositoryException re) {
+			log.error("Exception loading Supported Privileges", re);
+		}
+		return names;
+	}
+
+	@Override
+	public String getTitle() {
+		return TITLE;
+	}
+
 	private boolean hasPrincipal(Mapping map, String name) {
 		Iterable<String> principals = map.mapPrincipals(map.getServiceName(), map.getSubServiceName());
 		if (principals != null) {
@@ -533,6 +518,48 @@ public class ServiceUserWebConsolePlugin extends SimpleWebConsolePlugin {
 
 	}
 
+	private void printPrivilegeSelect(PrintWriter pw, String label, List<Pair<String, String>> privileges,
+			String[] supportedPrivileges, String alertMessage) {
+		pw.print("<td style='width:20%'>");
+		pw.print(xss.encodeForHTMLAttr(label));
+		pw.println("</td>");
+		pw.print("<td><table class=\"repeating-container\" style=\"width: 100%\" data-length=\"" + privileges.size()
+				+ "\"><tr><td>Path</td><td>Privilege</td><td></td>");
+
+		int idx = 0;
+		for (Pair<String, String> privilege : privileges) {
+			pw.print("</tr><tr class=\"repeating-item\"><td>");
+
+			pw.print("<input type=\"text\"  name=\"acl-path-" + idx + "\" value='");
+			pw.print(xss.encodeForHTMLAttr(StringUtils.defaultString(privilege.getKey())));
+			pw.print("' style='width:100%' />");
+
+			pw.print("</td><td>");
+
+			pw.print("<input type=\"text\" list=\"data-privileges\" name=\"acl-privilege-" + idx + "\" value='");
+			pw.print(xss.encodeForHTMLAttr(StringUtils.defaultString(privilege.getValue())));
+			pw.print("' style='width:100%' />");
+
+			pw.print("</td><td>");
+
+			pw.print("<input type=\"button\" value=\"&nbsp;-&nbsp;\" class=\"repeating-remove\" /></td>");
+		}
+		pw.print("</tr></table>");
+
+		pw.print("<input type=\"button\" value=\"&nbsp;+&nbsp;\" class=\"repeating-add\" />");
+
+		pw.print("<datalist id=\"data-privileges\">");
+		for (String option : supportedPrivileges) {
+			pw.print("<option");
+			pw.print(">");
+			pw.print(xss.encodeForHTMLAttr(option));
+			pw.print("</option>");
+		}
+		pw.print("</datalist><script src=\"/system/console/serviceusers/res/ui/serviceusermanager.js\"></script>");
+		infoDiv(pw, alertMessage);
+		pw.println("</td>");
+	}
+
 	private void printServiceUserDetails(HttpServletRequest request, PrintWriter pw)
 			throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
 		String name = getParameter(request, PN_USER, "");
@@ -676,44 +703,6 @@ public class ServiceUserWebConsolePlugin extends SimpleWebConsolePlugin {
 		}
 	}
 
-	private List<Pair<String, String>> getPrivileges(HttpServletRequest request) {
-		List<Pair<String, String>> privileges = new ArrayList<Pair<String, String>>();
-		List<String> params = Collections.list(request.getParameterNames());
-
-		for (String param : params) {
-			if (param.startsWith("acl-path-")) {
-				String path = request.getParameter(param);
-				String privilege = request.getParameter(param.replace("-path-", "-privilege-"));
-				if (StringUtils.isNotBlank(path) && StringUtils.isNotBlank(privilege)) {
-					privileges.add(new ImmutablePair<String, String>(path, privilege));
-				} else {
-					log.warn("Unable to load ACL due to missing value {}={}", path, privilege);
-				}
-			}
-		}
-
-		return privileges;
-	}
-
-	private String[] getSupportedPrivileges(HttpServletRequest request) {
-		String[] names = null;
-		try {
-			ResourceResolver resolver = getResourceResolver(request);
-			Session session = resolver.adaptTo(Session.class);
-			AccessControlManager accessControl = session.getAccessControlManager();
-			Privilege[] privileges = accessControl.getSupportedPrivileges("/");
-			names = new String[privileges.length];
-			for (int i = 0; i < privileges.length; i++) {
-				names[i] = privileges[i].getName();
-			}
-			Arrays.sort(names);
-		} catch (RepositoryException re) {
-			log.error("Exception loading Supported Privileges", re);
-		}
-		return names;
-
-	}
-
 	@Override
 	protected void renderContent(HttpServletRequest request, HttpServletResponse response)
 			throws ServletException, IOException {
@@ -745,48 +734,6 @@ public class ServiceUserWebConsolePlugin extends SimpleWebConsolePlugin {
 		}
 	}
 
-	private void printPrivilegeSelect(PrintWriter pw, String label, List<Pair<String, String>> privileges,
-			String[] supportedPrivileges, String alertMessage) {
-		pw.print("<td style='width:20%'>");
-		pw.print(xss.encodeForHTMLAttr(label));
-		pw.println("</td>");
-		pw.print("<td><table class=\"repeating-container\" style=\"width: 100%\" data-length=\"" + privileges.size()
-				+ "\"><tr><td>Path</td><td>Privilege</td><td></td>");
-
-		int idx = 0;
-		for (Pair<String, String> privilege : privileges) {
-			pw.print("</tr><tr class=\"repeating-item\"><td>");
-
-			pw.print("<input type=\"text\"  name=\"acl-path-" + idx + "\" value='");
-			pw.print(xss.encodeForHTMLAttr(StringUtils.defaultString(privilege.getKey())));
-			pw.print("' style='width:100%' />");
-
-			pw.print("</td><td>");
-
-			pw.print("<input type=\"text\" list=\"data-privileges\" name=\"acl-privilege-" + idx + "\" value='");
-			pw.print(xss.encodeForHTMLAttr(StringUtils.defaultString(privilege.getValue())));
-			pw.print("' style='width:100%' />");
-
-			pw.print("</td><td>");
-
-			pw.print("<input type=\"button\" value=\"&nbsp;-&nbsp;\" class=\"repeating-remove\" /></td>");
-		}
-		pw.print("</tr></table>");
-
-		pw.print("<input type=\"button\" value=\"&nbsp;+&nbsp;\" class=\"repeating-add\" />");
-
-		pw.print("<datalist id=\"data-privileges\">");
-		for (String option : supportedPrivileges) {
-			pw.print("<option");
-			pw.print(">");
-			pw.print(xss.encodeForHTMLAttr(option));
-			pw.print("</option>");
-		}
-		pw.print("</datalist><script src=\"/system/console/serviceusers/res/ui/serviceusermanager.js\"></script>");
-		infoDiv(pw, alertMessage);
-		pw.println("</td>");
-	}
-
 	private void selectField(PrintWriter pw, String label, String fieldName, String value, Collection<String> options,
 			String... alertMessages) {
 		pw.print("<td style='width:20%'>");
@@ -902,4 +849,100 @@ public class ServiceUserWebConsolePlugin extends SimpleWebConsolePlugin {
 		pw.println("</td>");
 	}
 
+	private boolean updatePrivileges(HttpServletRequest request, ResourceResolver resolver) {
+
+		List<Pair<String, String>> privileges = this.getPrivileges(request);
+		String name = getParameter(request, PN_NAME, "");
+
+		List<String> currentPolicies = new ArrayList<String>();
+		findACLs(resolver, name, currentPolicies);
+		for (int i = 0; i < currentPolicies.size(); i++) {
+			String path = StringUtils.substringBefore(currentPolicies.get(i), "/rep:policy");
+			currentPolicies.set(i, StringUtils.isNotBlank(path) ? path : "/");
+		}
+		log.debug("Loaded current policy paths: {}", currentPolicies);
+
+		Map<String, List<String>> toSet = new HashMap<String, List<String>>();
+		for (Pair<String, String> privilege : privileges) {
+			if (!toSet.containsKey(privilege.getKey())) {
+				toSet.put(privilege.getKey(), new ArrayList<String>());
+			}
+			toSet.get(privilege.getKey()).add(privilege.getValue());
+		}
+		log.debug("Loaded updated policy paths: {}", currentPolicies);
+
+		String lastEntry = null;
+
+		try {
+
+			Session session = resolver.adaptTo(Session.class);
+			AccessControlManager accessManager = session.getAccessControlManager();
+			PrincipalManager principalManager = AccessControlUtil.getPrincipalManager(session);
+
+			for (Entry<String, List<String>> pol : toSet.entrySet()) {
+				lastEntry = pol.getKey();
+				currentPolicies.remove(pol.getKey());
+				log.debug("Updating policies for {}", pol.getKey());
+
+				AccessControlPolicy[] policies = accessManager.getPolicies(pol.getKey());
+				List<String> toRemove = new ArrayList<String>();
+				for (AccessControlPolicy p : policies) {
+					if (p instanceof AccessControlList) {
+						AccessControlList policy = (AccessControlList) p;
+						for (AccessControlEntry entry : policy.getAccessControlEntries()) {
+							Principal prin = entry.getPrincipal();
+							if (prin.getName().equals(name)) {
+								for (Privilege privilege : entry.getPrivileges()) {
+									if (!pol.getValue().contains(privilege.getName())) {
+										log.debug("Removing privilege {}", privilege);
+										toRemove.add(privilege.getName());
+									}
+								}
+							}
+						}
+					}
+				}
+				Principal principal = principalManager.getPrincipal(name);
+				AccessControlUtil.replaceAccessControlEntry(session, pol.getKey(), principal,
+						pol.getValue().toArray(new String[pol.getValue().size()]), new String[0],
+						toRemove.toArray(new String[toRemove.size()]), null);
+			}
+			session.save();
+
+			for (String oldPolicy : currentPolicies) {
+				boolean removed = false;
+				log.debug("Removing policy for {}", oldPolicy);
+				AccessControlPolicy[] policies = accessManager.getPolicies(oldPolicy);
+				AccessControlEntry toRemove = null;
+				for (AccessControlPolicy p : policies) {
+					if (p instanceof AccessControlList) {
+						AccessControlList policy = (AccessControlList) p;
+						for (AccessControlEntry entry : policy.getAccessControlEntries()) {
+							Principal prin = entry.getPrincipal();
+							if (prin.getName().equals(name)) {
+								toRemove = entry;
+								break;
+							}
+						}
+						if (toRemove != null) {
+							removed = true;
+							policy.removeAccessControlEntry(toRemove);
+							accessManager.setPolicy(oldPolicy, policy);
+							session.save();
+							log.debug("Removed access control entry {}", toRemove);
+						}
+					}
+				}
+				if (!removed) {
+					log.warn("No policy found for {}", oldPolicy);
+				}
+			}
+		} catch (RepositoryException e) {
+			log.error("Exception updating principals with {}, failed on {}", toSet, lastEntry, e);
+			return false;
+		}
+
+		return true;
+	}
+
 }


[sling-org-apache-sling-serviceuser-webconsole] 12/16: SLING-7245 - Validate pull requests using Jenkins

Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceuser-webconsole.git

commit b19aa05232848d6cb2b557527757c931ff896f18
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Wed Jan 30 09:51:00 2019 +0100

    SLING-7245 - Validate pull requests using Jenkins
---
 Jenkinsfile | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 0000000..f582519
--- /dev/null
+++ b/Jenkinsfile
@@ -0,0 +1,20 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+slingOsgiBundleBuild()


[sling-org-apache-sling-serviceuser-webconsole] 04/16: [maven-release-plugin] prepare for next development iteration

Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceuser-webconsole.git

commit e1ba693d594036479e3c99c35f1d84632429cee8
Author: Dan Klco <da...@gmail.com>
AuthorDate: Fri Jan 12 16:12:35 2018 -0500

    [maven-release-plugin] prepare for next development iteration
---
 pom.xml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/pom.xml b/pom.xml
index bc48140..aa6e3d4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,7 +29,7 @@
 
     <artifactId>org.apache.sling.serviceuser.webconsole</artifactId>
     <packaging>bundle</packaging>
-    <version>1.0.0</version>
+    <version>1.0.1-SNAPSHOT</version>
 
     <name>Apache Sling Service User Web Console</name>
     <description>
@@ -40,7 +40,7 @@
         <connection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceuser-webconsole.git</connection>
         <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceuser-webconsole.git</developerConnection>
         <url>https://gitbox.apache.org/repos/asf?p=sling-org-apache-sling-serviceuser-webconsole.git</url>
-      <tag>org.apache.sling.serviceuser.webconsole-1.0.0</tag>
+      <tag>HEAD</tag>
   </scm>
 
     <build>


[sling-org-apache-sling-serviceuser-webconsole] 10/16: SLING-7215 - [nice-to-have] Add a CONTRIBUTING file to every module

Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceuser-webconsole.git

commit e12ea96e3c7d85dc36fb5a7a373e558d44f07d18
Author: Radu Cotescu <co...@adobe.com>
AuthorDate: Wed Sep 19 18:29:21 2018 +0200

    SLING-7215 - [nice-to-have] Add a CONTRIBUTING file to every module
---
 CONTRIBUTING.md | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..ca36072
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,6 @@
+Contributing
+====
+
+Thanks for choosing to contribute!
+
+You will find all the necessary details about how you can do this at https://sling.apache.org/contributing.html.