This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to annotated tag org.apache.sling.serviceusermapper-1.3.4 in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceusermapper.git
commit 341899c59b23e25db5a10eb4c6bd0d31069f6109 Author: Karl Pauls <[email protected]> AuthorDate: Mon Jul 10 15:40:52 2017 +0000 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 --- .../ServicePrincipalsValidator.java | 37 +++++ .../serviceusermapping/ServiceUserMapper.java | 15 ++ .../sling/serviceusermapping/impl/Mapping.java | 63 +++++++- .../impl/MappingConfigAmendment.java | 10 +- .../impl/MappingInventoryPrinter.java | 92 ++++++++++-- .../impl/ServiceUserMapperImpl.java | 110 +++++++++++++- .../sling/serviceusermapping/package-info.java | 2 +- .../sling/serviceusermapping/impl/MappingTest.java | 161 ++++++++++++++------- .../impl/ServiceUserMapperImplTest.java | 141 ++++++++++++++++++ 9 files changed, 551 insertions(+), 80 deletions(-) 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 @@ public interface ServiceUserMapper { * 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 @@ class Mapping implements Comparable<Mapping> { 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 @@ class Mapping implements Comparable<Mapping> { 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 @@ class Mapping implements Comparable<Mapping> { 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 @@ class Mapping implements Comparable<Mapping> { 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 @@ public class MappingConfigAmendment implements Comparable<MappingConfigAmendment 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 @@ package org.apache.sling.serviceusermapping.impl; 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 @@ public class MappingInventoryPrinter implements InventoryPrinter { 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); + } + } + 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); + } } - list.add(m); } return result; } @@ -80,19 +114,26 @@ public class MappingInventoryPrinter implements InventoryPrinter { 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 @@ public class MappingInventoryPrinter implements InventoryPrinter { } 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 @@ public class MappingInventoryPrinter implements InventoryPrinter { 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 @@ public class MappingInventoryPrinter implements InventoryPrinter { 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.CopyOnWriteArrayList; 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 @@ public class ServiceUserMapperImpl implements ServiceUserMapper { @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 @@ public class ServiceUserMapperImpl implements ServiceUserMapper { 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 @@ public class ServiceUserMapperImpl implements ServiceUserMapper { */ @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 @@ public class ServiceUserMapperImpl implements ServiceUserMapper { * @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 @@ public class ServiceUserMapperImpl implements ServiceUserMapper { 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 @@ public class ServiceUserMapperImpl implements ServiceUserMapper { 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 @@ public class ServiceUserMapperImpl implements ServiceUserMapper { } } + 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. */ [email protected]("1.2.1") [email protected]("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 @@ public class MappingTest { @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); + } + + @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"); } - private void assertMapping(final String serviceName, final String subServiceName, final String userName) { + @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 @@ public class MappingTest { 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 @@ public class ServiceUserMapperImplTest { 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 @@ public class ServiceUserMapperImplTest { 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 @@ public class ServiceUserMapperImplTest { 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 @@ public class ServiceUserMapperImplTest { } @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[] { -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
