SLING-6963: Add Service user declaration based on principal names - patch provided by Angela Schreiber.
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/serviceusermapper@1801482 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/sling/serviceusermapping/ServicePrincipalsValidator.java b/src/main/java/org/apache/sling/serviceusermapping/ServicePrincipalsValidator.java
new file mode 100644
index 0000000..4fa384b
--- /dev/null
+++ b/src/main/java/org/apache/sling/serviceusermapping/ServicePrincipalsValidator.java
@@ -0,0 +1,37 @@
+/*
+ * 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.serviceusermapping;
+
+import org.osgi.annotation.versioning.ConsumerType;
+
+/**
+ * The {@code ServicePrincipalsValidator} allows to implement validation of configured
+ * service user mappings.
+ */
+@ConsumerType
+public interface ServicePrincipalsValidator {
+
+ /**
+ * Validates the configured service principal names.
+ *
+ * @param serviceUserId The principal names associated with the service.
+ * @param serviceName The name of the service
+ * @param subServiceName The optional sub service name.
+ * @return {@code true} if all configured service principal names are valid; {@code false} otherwise.
+ */
+ boolean isValid(Iterable<String> servicePrincipalNames, String serviceName, String subServiceName);
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/serviceusermapping/ServiceUserMapper.java b/src/main/java/org/apache/sling/serviceusermapping/ServiceUserMapper.java
index d8e6701..8431d7e 100644
--- a/src/main/java/org/apache/sling/serviceusermapping/ServiceUserMapper.java
+++ b/src/main/java/org/apache/sling/serviceusermapping/ServiceUserMapper.java
@@ -71,4 +71,19 @@
* optional {@code serviceInfo}.
*/
String getServiceUserID(Bundle bundle, String subServiceName);
+
+ /**
+ * Returns the principal names to access the data store on behalf of the
+ * service.
+ *
+ * @param bundle The bundle implementing the service request access to resources.
+ * @param subServiceName Name of the sub service. This parameter is optional
+ * and may be an empty string or {@code null}.
+ * @return The principal names to use to provide access to the resources for
+ * the service. This may be {@code null} if no mapping has been defined
+ * for the service identified by the bundle and the optional {@code serviceInfo}
+ * or if no principal names have been specified with the mapping.
+ * In this case {@link #getServiceUserID(Bundle, String)} should be used instead.
+ */
+ Iterable<String> getServicePrincipalNames(Bundle bundle, String subServiceName);
}
diff --git a/src/main/java/org/apache/sling/serviceusermapping/impl/Mapping.java b/src/main/java/org/apache/sling/serviceusermapping/impl/Mapping.java
index b4650c2..503a985 100644
--- a/src/main/java/org/apache/sling/serviceusermapping/impl/Mapping.java
+++ b/src/main/java/org/apache/sling/serviceusermapping/impl/Mapping.java
@@ -18,9 +18,12 @@
*/
package org.apache.sling.serviceusermapping.impl;
+import java.util.HashSet;
+import java.util.Set;
+
/**
* The <code>Mapping</code> class defines the mapping of a service's name and
- * optional service information to a user name.
+ * optional service information to a user name and optionally to a set of principal names.
*/
class Mapping implements Comparable<Mapping> {
@@ -36,11 +39,14 @@
private final String userName;
+ private final Set<String> principalNames;
+
/**
* Creates a mapping entry for the entry specification of the form:
*
* <pre>
- * spec = serviceName [ ":" subServiceName ] "=" userName .
+ * spec = serviceName [ ":" subServiceName ] "=" userName | "[" principalNames "]"
+ * principalNames = principalName ["," principalNames]
* </pre>
*
* @param spec The mapping specification.
@@ -56,7 +62,7 @@
if (colon == 0 || equals <= 0) {
throw new IllegalArgumentException("serviceName is required");
} else if (equals == spec.length() - 1) {
- throw new IllegalArgumentException("userName is required");
+ throw new IllegalArgumentException("userName or principalNames is required");
} else if (colon + 1 == equals) {
throw new IllegalArgumentException("serviceInfo must not be empty");
}
@@ -69,18 +75,38 @@
this.subServiceName = spec.substring(colon + 1, equals);
}
- this.userName = spec.substring(equals + 1);
+ String s = spec.substring(equals + 1);
+ if (s.charAt(0) == '[' && s.charAt(s.length()-1) == ']') {
+ this.userName = null;
+ this.principalNames = extractPrincipalNames(s);
+ } else {
+ this.userName = s;
+ this.principalNames = null;
+ }
+ }
+
+ static Set<String> extractPrincipalNames(String s) {
+ String[] sArr = s.substring(1, s.length() - 1).split(",");
+ Set<String> set = new HashSet<>();
+ for (String name : sArr) {
+ String n = name.trim();
+ if (!n.isEmpty()) {
+ set.add(n);
+ }
+ }
+ return set;
}
/**
* Returns the user name if the {@code serviceName} and the
- * {@code serviceInfo} match. Otherwise {@code null} is returned.
+ * {@code serviceInfo} match and a single user name is configured (in contrast
+ * to a set of principal names). Otherwise {@code null} is returned.
*
* @param serviceName The name of the service to match. If this is
* {@code null} this mapping will not match.
* @param subServiceName The Subservice Name to match. This may be
* {@code null}.
- * @return The user name if this mapping matches or {@code null} otherwise.
+ * @return The user name if this mapping matches and the configuration doesn't specify a set of principal names; {@code null} otherwise.
*/
String map(final String serviceName, final String subServiceName) {
if (this.serviceName.equals(serviceName) && equals(this.subServiceName, subServiceName)) {
@@ -90,14 +116,37 @@
return null;
}
+ /**
+ * Returns the principal names if the {@code serviceName} and the
+ * {@code serviceInfo} match and principal names have been configured.
+ * Otherwise {@code null} is returned. If no principal names are configured
+ * {@link #map(String, String)} needs to be used instead.
+ *
+ * @param serviceName The name of the service to match. If this is
+ * {@code null} this mapping will not match.
+ * @param subServiceName The Subservice Name to match. This may be
+ * {@code null}.
+ * @return An iterable of principals names this mapping matches and the configuration
+ * does specify a set of principal names (intstead of a single user name); {@code null}
+ * otherwise.
+ */
+ Iterable<String> mapPrincipals(final String serviceName, final String subServiceName) {
+ if (this.serviceName.equals(serviceName) && equals(this.subServiceName, subServiceName)) {
+ return principalNames;
+ }
+
+ return null;
+ }
+
private boolean equals(String str1, String str2) {
return ((str1 == null) ? str2 == null : str1.equals(str2));
}
@Override
public String toString() {
+ String name = (userName != null) ? "userName=" + userName : "principleNames" + principalNames.toString();
return "Mapping [serviceName=" + serviceName + ", subServiceName="
- + subServiceName + ", userName=" + userName + "]";
+ + subServiceName + ", " + name;
}
public String getServiceName() {
diff --git a/src/main/java/org/apache/sling/serviceusermapping/impl/MappingConfigAmendment.java b/src/main/java/org/apache/sling/serviceusermapping/impl/MappingConfigAmendment.java
index c0fe863..60a425d 100644
--- a/src/main/java/org/apache/sling/serviceusermapping/impl/MappingConfigAmendment.java
+++ b/src/main/java/org/apache/sling/serviceusermapping/impl/MappingConfigAmendment.java
@@ -46,10 +46,12 @@
int service_ranking() default 0;
@AttributeDefinition(name = "Service Mappings",
- description = "Provides mappings from service name to user names. "
- + "Each entry is of the form 'bundleId [ \":\" subServiceName ] \"=\" userName' "
- + "where bundleId and subServiceName identify the service and userName "
- + "defines the name of the user to provide to the service. Invalid entries are logged and ignored.")
+ description = "Provides mappings from service name to user (and optionally principal) names. "
+ + "Each entry is of the form 'bundleId [ \":\" subServiceName ] \"=\" userName' | \"[\" principalNames \"]\" "
+ + "where bundleId and subServiceName identify the service and userName/principalNames "
+ + "defines the name(s) of the user/principals to provide to the service. "
+ + "'principalNames is defined to be a comma separated list of principal names. "
+ + "Invalid entries are logged and ignored.")
String[] user_mapping() default {};
// Internal Name hint for web console.
diff --git a/src/main/java/org/apache/sling/serviceusermapping/impl/MappingInventoryPrinter.java b/src/main/java/org/apache/sling/serviceusermapping/impl/MappingInventoryPrinter.java
index e3488f5..8f32898 100644
--- a/src/main/java/org/apache/sling/serviceusermapping/impl/MappingInventoryPrinter.java
+++ b/src/main/java/org/apache/sling/serviceusermapping/impl/MappingInventoryPrinter.java
@@ -21,6 +21,7 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
@@ -62,16 +63,49 @@
return m.map(m.getServiceName(), m.getSubServiceName());
}
+ private String[] getMappedPrincipalNames(Mapping m) {
+ Iterable<String> principalNames = m.mapPrincipals(m.getServiceName(), m.getSubServiceName());
+ if (principalNames == null) {
+ return null;
+ } else {
+ List<String> l = new ArrayList<>();
+ for (String pName : principalNames) {
+ l.add(pName);
+ }
+ return l.toArray(new String[l.size()]);
+ }
+ }
+
private SortedMap<String, List<Mapping>> getMappingsByUser(List<Mapping> mappings) {
SortedMap<String, List<Mapping>> result = new TreeMap<String, List<Mapping>>();
for(Mapping m : mappings) {
final String user = getMappedUser(m);
- List<Mapping> list = result.get(user);
- if(list == null) {
- list = new ArrayList<Mapping>();
- result.put(user, list);
+ if (user != null) {
+ List<Mapping> list = result.get(user);
+ if (list == null) {
+ list = new ArrayList<Mapping>();
+ result.put(user, list);
+ }
+ list.add(m);
}
- list.add(m);
+ }
+ return result;
+ }
+
+ private SortedMap<String, List<Mapping>> getMappingsByPrincipalName(List<Mapping> mappings) {
+ SortedMap<String, List<Mapping>> result = new TreeMap<String, List<Mapping>>();
+ for(Mapping m : mappings) {
+ final String[] principalNames = getMappedPrincipalNames(m);
+ if (principalNames != null) {
+ for (String pName : principalNames) {
+ List<Mapping> list = result.get(pName);
+ if (list == null) {
+ list = new ArrayList<Mapping>();
+ result.put(pName, list);
+ }
+ list.add(m);
+ }
+ }
}
return result;
}
@@ -80,19 +114,26 @@
w.object();
w.key("serviceName").value(m.getServiceName());
w.key("subServiceName").value(m.getSubServiceName());
- w.key("user").value(getMappedUser(m));
+ String[] pNames = getMappedPrincipalNames(m);
+ if (pNames != null) {
+ w.key("principals").value(pNames);
+ } else {
+ w.key("user").value(getMappedUser(m));
+ }
w.endObject();
}
private void renderJson(PrintWriter out) throws IOException {
final List<Mapping> data = mapper.getActiveMappings();
final Map<String, List<Mapping>> byUser = getMappingsByUser(data);
+ final Map<String, List<Mapping>> byPrincipalName = getMappingsByPrincipalName(data);
final JSONWriter w = new JSONWriter(out);
w.object();
w.key("title").value("Service User Mappings");
w.key("mappingsCount").value(data.size());
w.key("uniqueUsersCount").value(byUser.keySet().size());
+ w.key("uniquePrincipalsCount").value(byPrincipalName.keySet().size());
w.key("mappingsByUser");
w.object();
@@ -106,6 +147,18 @@
}
w.endObject();
+ w.key("mappingsByPrincipal");
+ w.object();
+ for(Map.Entry<String, List<Mapping>> e : byPrincipalName.entrySet()) {
+ w.key(e.getKey());
+ w.array();
+ for(Mapping m : e.getValue()) {
+ asJSON(w,m);
+ }
+ w.endArray();
+ }
+ w.endObject();
+
w.endObject();
}
@@ -117,19 +170,23 @@
final String sub = m.getSubServiceName();
w.print(sub == null ? "" : sub);
w.print(SEP);
- w.println(getMappedUser(m));
+ String[] principalNames = getMappedPrincipalNames(m);
+ if (principalNames != null) {
+ w.println(Arrays.toString(principalNames));
+ } else {
+ w.println(getMappedUser(m));
+ }
}
private void renderText(PrintWriter out) {
final List<Mapping> data = mapper.getActiveMappings();
- final Map<String, List<Mapping>> byUser = getMappingsByUser(data);
- final String formatInfo = " (format: service name / sub service name / user)";
+ final Map<String, List<Mapping>> byUser = getMappingsByUser(data);
out.print("*** Mappings by user (");
out.print(byUser.keySet().size());
out.print(" users):");
- out.println(formatInfo);
+ out.println(" (format: service name / sub service name / user)");
for(Map.Entry<String, List<Mapping>> e : byUser.entrySet()) {
out.print(" ");
@@ -138,5 +195,20 @@
asText(out, m, " ");
}
}
+
+ final Map<String, List<Mapping>> byPrincipalName = getMappingsByPrincipalName(data);
+
+ out.print("*** Mappings by principals (");
+ out.print(byPrincipalName.keySet().size());
+ out.print(" principals):");
+ out.println(" (format: service name / sub service name / principal names)");
+
+ for(Map.Entry<String, List<Mapping>> e : byPrincipalName.entrySet()) {
+ out.print(" ");
+ out.println(e.getKey());
+ for(Mapping m : e.getValue()) {
+ asText(out, m, " ");
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/serviceusermapping/impl/ServiceUserMapperImpl.java b/src/main/java/org/apache/sling/serviceusermapping/impl/ServiceUserMapperImpl.java
index a7b8c48..904dcef 100644
--- a/src/main/java/org/apache/sling/serviceusermapping/impl/ServiceUserMapperImpl.java
+++ b/src/main/java/org/apache/sling/serviceusermapping/impl/ServiceUserMapperImpl.java
@@ -35,6 +35,7 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import org.apache.sling.serviceusermapping.ServicePrincipalsValidator;
import org.apache.sling.serviceusermapping.ServiceUserMapped;
import org.apache.sling.serviceusermapping.ServiceUserMapper;
import org.apache.sling.serviceusermapping.ServiceUserValidator;
@@ -65,9 +66,10 @@
@AttributeDefinition(name = "Service Mappings",
description = "Provides mappings from service name to user names. "
- + "Each entry is of the form 'bundleId [ \":\" subServiceName ] \"=\" userName' "
+ + "Each entry is of the form 'bundleId [ \":\" subServiceName ] \"=\" userName' | \"[\" principalNames \"]\" "
+ "where bundleId and subServiceName identify the service and userName "
- + "defines the name of the user to provide to the service. Invalid entries are logged and ignored.")
+ + "defines the name of the user to provide to the service; alternative the the mapping"
+ + "can define a comma separated set of principalNames instead of the userName. Invalid entries are logged and ignored.")
String[] user_mapping() default {};
@AttributeDefinition(name = "Default User",
@@ -95,7 +97,9 @@
private Mapping[] activeMappings = new Mapping[0];
- private final List<ServiceUserValidator> validators = new CopyOnWriteArrayList<>();
+ private final List<ServiceUserValidator> userValidators = new CopyOnWriteArrayList<>();
+
+ private final List<ServicePrincipalsValidator> principalsValidators = new CopyOnWriteArrayList<>();
private SortedMap<Mapping, Registration> activeRegistrations = new TreeMap<>();
@@ -166,7 +170,7 @@
*/
@Reference(cardinality=ReferenceCardinality.MULTIPLE, policy= ReferencePolicy.DYNAMIC)
protected synchronized void bindServiceUserValidator(final ServiceUserValidator serviceUserValidator) {
- validators.add(serviceUserValidator);
+ userValidators.add(serviceUserValidator);
restartAllActiveServiceUserMappedServices();
}
@@ -175,7 +179,26 @@
* @param serviceUserValidator
*/
protected synchronized void unbindServiceUserValidator(final ServiceUserValidator serviceUserValidator) {
- validators.remove(serviceUserValidator);
+ userValidators.remove(serviceUserValidator);
+ restartAllActiveServiceUserMappedServices();
+ }
+
+ /**
+ * bind the servicePrincipalsValidator
+ * @param servicePrincipalsValidator
+ */
+ @Reference(cardinality=ReferenceCardinality.MULTIPLE, policy= ReferencePolicy.DYNAMIC)
+ protected synchronized void bindServicePrincipalsValidator(final ServicePrincipalsValidator servicePrincipalsValidator) {
+ principalsValidators.add(servicePrincipalsValidator);
+ restartAllActiveServiceUserMappedServices();
+ }
+
+ /**
+ * unbind the servicePrincipalsValidator
+ * @param servicePrincipalsValidator
+ */
+ protected synchronized void unbindServicePrincipalsValidator(final ServicePrincipalsValidator servicePrincipalsValidator) {
+ principalsValidators.remove(servicePrincipalsValidator);
restartAllActiveServiceUserMappedServices();
}
@@ -194,6 +217,21 @@
return result;
}
+ /**
+ * @see org.apache.sling.serviceusermapping.ServiceUserMapper#getServicePrincipalNames(org.osgi.framework.Bundle, java.lang.String)
+ */
+ @Override
+ public Iterable<String> getServicePrincipalNames(Bundle bundle, String subServiceName) {
+ final String serviceName = getServiceName(bundle);
+ final Iterable<String> names = internalGetPrincipalNames(serviceName, subServiceName);
+ final boolean valid = areValidPrincipals(names, serviceName, subServiceName);
+ final Iterable<String> result = valid ? names : null;
+ log.debug(
+ "getServicePrincipalNames(bundle {}, subServiceName {}) returns [{}] (raw principalNames={}, valid={})",
+ new Object[] { bundle, subServiceName, result, names, valid});
+ return result;
+ }
+
@Reference(cardinality=ReferenceCardinality.MULTIPLE,policy=ReferencePolicy.DYNAMIC,updated="updateAmendment")
protected synchronized void bindAmendment(final MappingConfigAmendment amendment, final Map<String, Object> props) {
final Long key = (Long) props.get(Constants.SERVICE_ID);
@@ -393,8 +431,8 @@
log.debug("isValidUser: userId is null -> invalid");
return false;
}
- if ( !validators.isEmpty() ) {
- for (final ServiceUserValidator validator : validators) {
+ if ( !userValidators.isEmpty() ) {
+ for (final ServiceUserValidator validator : userValidators) {
if ( validator.isValid(userId, serviceName, subServiceName) ) {
log.debug("isValidUser: Validator {} accepts userId [{}] -> valid", validator, userId);
return true;
@@ -408,6 +446,64 @@
}
}
+ private boolean areValidPrincipals(final Iterable<String> principalNames, final String serviceName, final String subServiceName) {
+ if (principalNames == null) {
+ log.debug("areValidPrincipals: principalNames are null -> invalid");
+ return false;
+ }
+ if ( !principalsValidators.isEmpty() ) {
+ for (final ServicePrincipalsValidator validator : principalsValidators) {
+ if ( validator.isValid(principalNames, serviceName, subServiceName) ) {
+ log.debug("areValidPrincipals: Validator {} accepts principal names [{}] -> valid", validator, principalNames);
+ return true;
+ }
+ }
+ log.debug("areValidPrincipals: No validator accepted principal names [{}] -> invalid", principalNames);
+ return false;
+ } else {
+ log.debug("areValidPrincipals: No active validators for principal names [{}] -> valid", principalNames);
+ return true;
+ }
+ }
+
+ private Iterable<String> internalGetPrincipalNames(final String serviceName, final String subServiceName) {
+ log.debug(
+ "internalGetPrincipalNames: {} active mappings, looking for mapping for {}/{}",
+ new Object[] { this.activeMappings.length, serviceName, subServiceName });
+
+ for (final Mapping mapping : this.activeMappings) {
+ final Iterable<String> principalNames = mapping.mapPrincipals(serviceName, subServiceName);
+ if (principalNames != null) {
+ log.debug("Got principalNames [{}] from {}/{}", new Object[] {principalNames, serviceName, subServiceName });
+ return principalNames;
+ }
+ }
+
+ for (Mapping mapping : this.activeMappings) {
+ final Iterable<String> principalNames = mapping.mapPrincipals(serviceName, null);
+ if (principalNames != null) {
+ log.debug("Got principalNames [{}] from {}/{}", new Object[] {principalNames, serviceName });
+ return principalNames;
+ }
+ }
+
+ // second round without serviceInfo
+ log.debug(
+ "internalGetPrincipalNames: {} active mappings, looking for mapping for {}/<no subServiceName>",
+ this.activeMappings.length, serviceName);
+
+ for (Mapping mapping : this.activeMappings) {
+ final Iterable<String> principalNames = mapping.mapPrincipals(serviceName, null);
+ if (principalNames != null) {
+ log.debug("Got principalNames [{}] from {}/<no subServiceName>", principalNames, serviceName);
+ return principalNames;
+ }
+ }
+
+ log.debug("internalGetPrincipalNames: no mapping found.");
+ return null;
+ }
+
static String getServiceName(final Bundle bundle) {
return bundle.getSymbolicName();
}
diff --git a/src/main/java/org/apache/sling/serviceusermapping/package-info.java b/src/main/java/org/apache/sling/serviceusermapping/package-info.java
index 69a2cdd..1ee3078 100644
--- a/src/main/java/org/apache/sling/serviceusermapping/package-info.java
+++ b/src/main/java/org/apache/sling/serviceusermapping/package-info.java
@@ -17,6 +17,6 @@
* under the License.
*/
-@org.osgi.annotation.versioning.Version("1.2.1")
+@org.osgi.annotation.versioning.Version("1.3.0")
package org.apache.sling.serviceusermapping;
diff --git a/src/test/java/org/apache/sling/serviceusermapping/impl/MappingTest.java b/src/test/java/org/apache/sling/serviceusermapping/impl/MappingTest.java
index 7764d79..2ade43e 100644
--- a/src/test/java/org/apache/sling/serviceusermapping/impl/MappingTest.java
+++ b/src/test/java/org/apache/sling/serviceusermapping/impl/MappingTest.java
@@ -19,69 +19,43 @@
package org.apache.sling.serviceusermapping.impl;
import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Set;
import junit.framework.TestCase;
-
-import org.apache.sling.serviceusermapping.impl.Mapping;
import org.junit.Test;
public class MappingTest {
- @Test
+ @Test(expected = NullPointerException.class)
public void test_constructor_null() {
- try {
- new Mapping(null);
- TestCase.fail("NullPointerException expected");
- } catch (NullPointerException npe) {
- // expected
- }
+ new Mapping(null);
}
- @Test
+ @Test(expected = IllegalArgumentException.class)
public void test_constructor_empty() {
- try {
- new Mapping("");
- TestCase.fail("IllegalArgumentException expected");
- } catch (IllegalArgumentException iae) {
- // expected
- }
+ new Mapping("");
}
- @Test
+ @Test(expected = IllegalArgumentException.class)
public void test_constructor_missing_user_name() {
- try {
- new Mapping("serviceName");
- TestCase.fail("IllegalArgumentException expected");
- } catch (IllegalArgumentException iae) {
- // expected
- }
+ new Mapping("serviceName");
- try {
- new Mapping("serviceName=");
- TestCase.fail("IllegalArgumentException expected");
- } catch (IllegalArgumentException iae) {
- // expected
- }
}
- @Test
+ @Test(expected = IllegalArgumentException.class)
+ public void test_constructor_missing_user_name2() {
+ new Mapping("serviceName=");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
public void test_constructor_missing_service_name() {
- try {
- new Mapping("=user");
- TestCase.fail("IllegalArgumentException expected");
- } catch (IllegalArgumentException iae) {
- // expected
- }
+ new Mapping("=user");
}
- @Test
+ @Test(expected = IllegalArgumentException.class)
public void test_constructor_empty_service_info() {
- try {
- new Mapping("srv:=user");
- TestCase.fail("IllegalArgumentException expected");
- } catch (IllegalArgumentException iae) {
- // expected
- }
+ new Mapping("srv:=user");
}
@Test
@@ -91,41 +65,115 @@
@Test
public void test_constructor_and_map() {
- assertMapping("service", null, "user");
- assertMapping("service", "subServiceName", "user");
+ assertMapping("service", null, "user", (String[]) null);
+ assertMapping("service", "subServiceName", "user", (String[]) null);
}
- private void assertMapping(final String serviceName, final String subServiceName, final String userName) {
+ @Test(expected = IllegalArgumentException.class)
+ public void test_constructor_and_null_principals() {
+ assertMapping("service", "subServiceName", null, (String[]) null);
+ }
+
+ @Test
+ public void test_constructor_and_map_empty_principals() {
+ assertMapping("service", "subServiceName", null);
+ }
+
+ @Test
+ public void test_constructor_and_map_with_empty_principal() {
+ assertMapping("service", "subServiceName", null, "principal", "", "principal1");
+ }
+
+ @Test
+ public void test_constructor_and_map_with_null_principal() {
+ assertMapping("service", "subServiceName", null, "principal", null, "principal1");
+ }
+
+ @Test
+ public void test_constructor_and_map_single_principal() {
+ assertMapping("service", "subServiceName", null, "principal");
+ }
+
+ @Test
+ public void test_constructor_and_map_duplicate_principals() {
+ assertMapping("service", "subServiceName", null, "principal", "principal");
+ }
+
+ @Test
+ public void test_constructor_and_map_principals() {
+ assertMapping("service", "subServiceName", null, "principal1", "principal2", "principal3");
+ }
+
+ @Test
+ public void test_constructor_and_map_user_and_principals() {
+ assertMapping("service", "subServiceName", "user", "principal1", "principal2", "principal3");
+ }
+
+ private void assertMapping(final String serviceName, final String subServiceName, final String userName, final String... principalNames) {
StringBuilder spec = new StringBuilder();
spec.append(serviceName);
if (subServiceName != null) {
spec.append(':').append(subServiceName);
}
- spec.append('=').append(userName);
+ spec.append('=');
+
+ String expectedUserName = null;
+ Set<String> expectedPrincipalsNames = null;
+ if (principalNames != null) {
+ spec.append(Arrays.toString(principalNames));
+ expectedPrincipalsNames = Mapping.extractPrincipalNames(Arrays.toString(principalNames));
+ } else if (userName != null) {
+ spec.append(userName);
+ expectedUserName = userName;
+ }
// spec analysis
final Mapping mapping = new Mapping(spec.toString());
- TestCase.assertEquals(getField(mapping, "serviceName"), serviceName);
- TestCase.assertEquals(getField(mapping, "subServiceName"), subServiceName);
- TestCase.assertEquals(getField(mapping, "userName"), userName);
+ TestCase.assertEquals(serviceName, getField(mapping, "serviceName"));
+ TestCase.assertEquals(subServiceName, getField(mapping, "subServiceName"));
+ if (expectedUserName == null) {
+ TestCase.assertNull(getField(mapping, "userName"));
+ } else {
+ TestCase.assertEquals(expectedUserName, getField(mapping, "userName"));
+ }
+ if (principalNames == null) {
+ TestCase.assertNull(getSetField(mapping, "principalNames"));
+ } else {
+ TestCase.assertEquals(expectedPrincipalsNames, getSetField(mapping, "principalNames"));
+ }
// mapping
- TestCase.assertEquals(userName, mapping.map(serviceName, subServiceName));
+ if (expectedUserName == null) {
+ TestCase.assertNull(mapping.map(serviceName, subServiceName));
+ } else {
+ TestCase.assertEquals(userName, mapping.map(serviceName, subServiceName));
+ }
+
+ if (expectedPrincipalsNames == null) {
+ TestCase.assertNull(mapping.mapPrincipals(serviceName, subServiceName));
+ } else {
+ TestCase.assertEquals(expectedPrincipalsNames, mapping.mapPrincipals(serviceName, subServiceName));
+ }
+
if (subServiceName == null) {
// Mapping without subServiceName must not match request with any
// subServiceName
TestCase.assertNull(mapping.map(serviceName, subServiceName + "-garbage"));
+ TestCase.assertNull(mapping.mapPrincipals(serviceName, subServiceName + "-garbage"));
} else {
// Mapping with subServiceName must not match request without
// subServiceName
TestCase.assertNull(mapping.map(serviceName, null));
+ TestCase.assertNull(mapping.mapPrincipals(serviceName, null));
}
// no match for different service name
TestCase.assertNull(mapping.map(serviceName + "-garbage", subServiceName));
+ TestCase.assertNull(mapping.mapPrincipals(serviceName + "-garbage", subServiceName));
// no match for null service name
TestCase.assertNull(mapping.map(null, subServiceName));
+ TestCase.assertNull(mapping.mapPrincipals(null, subServiceName));
}
private String getField(final Object object, final String fieldName) {
@@ -138,4 +186,15 @@
return null; // will not get here, quiesce compiler
}
}
+
+ private Set<String> getSetField(final Object object, final String fieldName) {
+ try {
+ Field f = object.getClass().getDeclaredField(fieldName);
+ f.setAccessible(true);
+ return (Set<String>) f.get(object);
+ } catch (Exception e) {
+ TestCase.fail("Cannot get field " + fieldName + ": " + e.toString());
+ return null; // will not get here, quiesce compiler
+ }
+ }
}
diff --git a/src/test/java/org/apache/sling/serviceusermapping/impl/ServiceUserMapperImplTest.java b/src/test/java/org/apache/sling/serviceusermapping/impl/ServiceUserMapperImplTest.java
index 8d0b719..41da0f6 100644
--- a/src/test/java/org/apache/sling/serviceusermapping/impl/ServiceUserMapperImplTest.java
+++ b/src/test/java/org/apache/sling/serviceusermapping/impl/ServiceUserMapperImplTest.java
@@ -18,14 +18,22 @@
*/
package org.apache.sling.serviceusermapping.impl;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertNull;
+import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import java.util.Arrays;
import java.util.Dictionary;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
import java.util.Map;
+import java.util.Set;
+import org.apache.sling.serviceusermapping.ServicePrincipalsValidator;
import org.apache.sling.serviceusermapping.ServiceUserValidator;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
@@ -44,6 +52,10 @@
private static final String BUNDLE_SYMBOLIC3 = "bundle3";
+ private static final String BUNDLE_SYMBOLIC4 = "bundle4";
+
+ private static final String BUNDLE_SYMBOLIC5 = "bundle5";
+
private static final String SUB = "sub";
private static final String NONE = "none";
@@ -62,6 +74,10 @@
private static final Bundle BUNDLE3;
+ private static final Bundle BUNDLE4;
+
+ private static final Bundle BUNDLE5;
+
static {
BUNDLE1 = mock(Bundle.class);
when(BUNDLE1.getSymbolicName()).thenReturn(BUNDLE_SYMBOLIC1);
@@ -71,6 +87,12 @@
BUNDLE3 = mock(Bundle.class);
when(BUNDLE3.getSymbolicName()).thenReturn(BUNDLE_SYMBOLIC3);
+
+ BUNDLE4 = mock(Bundle.class);
+ when(BUNDLE4.getSymbolicName()).thenReturn(BUNDLE_SYMBOLIC4);
+
+ BUNDLE5 = mock(Bundle.class);
+ when(BUNDLE5.getSymbolicName()).thenReturn(BUNDLE_SYMBOLIC5);
}
@Test
@@ -184,6 +206,125 @@
}
@Test
+ public void test_getServicePrincipalNames() {
+ ServiceUserMapperImpl.Config config = mock(ServiceUserMapperImpl.Config.class);
+ when(config.user_mapping()).thenReturn(new String[] {
+ BUNDLE_SYMBOLIC1 + "=[" + SAMPLE + "]", //
+ BUNDLE_SYMBOLIC2 + "=[ " + ANOTHER + " ]", //
+ BUNDLE_SYMBOLIC3 + "=[" + SAMPLE + "," + ANOTHER + "]", //
+ BUNDLE_SYMBOLIC4 + "=[ " + SAMPLE + ", " + ANOTHER + " ]", //
+ BUNDLE_SYMBOLIC5 + "=[]", //
+ BUNDLE_SYMBOLIC1 + ":" + SUB + "=[" + SAMPLE_SUB + "]", //
+ BUNDLE_SYMBOLIC2 + ":" + SUB + "=[" + SAMPLE_SUB + "," + ANOTHER_SUB + "]" //
+ });
+
+ final ServiceUserMapperImpl sum = new ServiceUserMapperImpl();
+ sum.configure(null, config);
+
+ assertEqualPrincipalNames(sum.getServicePrincipalNames(BUNDLE1, null), SAMPLE);
+ assertEqualPrincipalNames(sum.getServicePrincipalNames(BUNDLE2, null), ANOTHER);
+ assertEqualPrincipalNames(sum.getServicePrincipalNames(BUNDLE3, null), SAMPLE, ANOTHER);
+ assertEqualPrincipalNames(sum.getServicePrincipalNames(BUNDLE4, null), SAMPLE, ANOTHER);
+ assertEqualPrincipalNames(sum.getServicePrincipalNames(BUNDLE5, null));
+ assertEqualPrincipalNames(sum.getServicePrincipalNames(BUNDLE1, SUB), SAMPLE_SUB);
+ assertEqualPrincipalNames(sum.getServicePrincipalNames(BUNDLE2, SUB), SAMPLE_SUB, ANOTHER_SUB);
+ }
+
+ @Test
+ public void test_getServicePrincipalNames_EmptySubService() {
+ ServiceUserMapperImpl.Config config = mock(ServiceUserMapperImpl.Config.class);
+ when(config.user_mapping()).thenReturn(new String[] {
+ BUNDLE_SYMBOLIC1 + "=[" + SAMPLE + "]", //
+ BUNDLE_SYMBOLIC2 + "=[ " + ANOTHER + " ]", //
+ });
+
+ final ServiceUserMapperImpl sum = new ServiceUserMapperImpl();
+ sum.configure(null, config);
+
+ assertEqualPrincipalNames(sum.getServicePrincipalNames(BUNDLE1, ""), SAMPLE);
+ assertEqualPrincipalNames(sum.getServicePrincipalNames(BUNDLE2, ""), ANOTHER);
+ }
+
+ @Test
+ public void test_getServicePrincipalNames_WithUserNameConfig() {
+ ServiceUserMapperImpl.Config config = mock(ServiceUserMapperImpl.Config.class);
+ when(config.user_mapping()).thenReturn(new String[] {
+ BUNDLE_SYMBOLIC1 + "=" + SAMPLE, //
+ BUNDLE_SYMBOLIC1 + ":" + SUB + "=" + SAMPLE_SUB, //
+ });
+ when(config.user_default()).thenReturn(NONE);
+ when(config.user_enable_default_mapping()).thenReturn(false);
+
+ final ServiceUserMapperImpl sum = new ServiceUserMapperImpl();
+ sum.configure(null, config);
+
+ assertNull(sum.getServicePrincipalNames(BUNDLE1, null));
+ assertNull(SAMPLE_SUB, sum.getServicePrincipalNames(BUNDLE1, SUB));
+ }
+
+ @Test
+ public void test_getServicePrincipalNames_IgnoresDefaultUser() {
+ ServiceUserMapperImpl.Config config = mock(ServiceUserMapperImpl.Config.class);
+ when(config.user_default()).thenReturn(NONE);
+ when(config.user_enable_default_mapping()).thenReturn(true);
+
+ final ServiceUserMapperImpl sum = new ServiceUserMapperImpl();
+ sum.configure(null, config);
+
+ assertNull(sum.getServicePrincipalNames(BUNDLE1, null));
+ assertNull(sum.getServicePrincipalNames(BUNDLE1, SUB));
+ }
+
+ @Test
+ public void test_getServicePrincipalnames_WithServicePrincipalsValidator() {
+ ServiceUserMapperImpl.Config config = mock(ServiceUserMapperImpl.Config.class);
+ when(config.user_mapping()).thenReturn(new String[] {
+ BUNDLE_SYMBOLIC1 + "=[" + SAMPLE + "]", //
+ BUNDLE_SYMBOLIC2 + "=[" + SAMPLE + "," + ANOTHER + "]", //
+ BUNDLE_SYMBOLIC1 + ":" + SUB + "=[" + SAMPLE + "," + SAMPLE_SUB + "]", //
+ BUNDLE_SYMBOLIC2 + ":" + SUB + "=[" + ANOTHER_SUB + "," + SAMPLE_SUB + "," + SAMPLE + "]"//
+ });
+
+ final ServiceUserMapperImpl sum = new ServiceUserMapperImpl();
+ sum.configure(null, config);
+ ServicePrincipalsValidator validator = new ServicePrincipalsValidator() {
+ @Override
+ public boolean isValid(Iterable<String> servicePrincipalNames, String serviceName, String subServiceName) {
+ for (String pName : servicePrincipalNames) {
+ if (SAMPLE.equals(pName)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ };
+ sum.bindServicePrincipalsValidator(validator);
+
+ assertNull(sum.getServicePrincipalNames(BUNDLE1, null));
+ assertNull(sum.getServicePrincipalNames(BUNDLE2, null));
+ assertNull(sum.getServicePrincipalNames(BUNDLE1, SUB));
+ assertNull(sum.getServicePrincipalNames(BUNDLE2, SUB));
+ }
+
+ private static void assertEqualPrincipalNames(Iterable<String> result, String... expected) {
+ if (expected == null) {
+ assertNull(result);
+ } else if (expected.length == 0) {
+ assertFalse(result.iterator().hasNext());
+ } else {
+ Set<String> resultSet = new HashSet<>();
+ Iterator<String> it = result.iterator();
+ while (it.hasNext()) {
+ resultSet.add(it.next());
+ }
+ Set<String> expectedSet = new HashSet<>();
+ expectedSet.addAll(Arrays.asList(expected));
+ assertEquals(expectedSet, resultSet);
+ }
+ }
+
+
+ @Test
public void test_amendment() {
ServiceUserMapperImpl.Config config = mock(ServiceUserMapperImpl.Config.class);
when(config.user_mapping()).thenReturn(new String[] {