Tests for sub-tree flattening config
Also adds a few accessors in order to faciliate accessors for tests. More work is needed in this area -- FR's code is an inconsistent mess of final classes, fluent factory methods, and accessors with the wrong access level.
| | |
| | | }, |
| | | "isReadOnly": true |
| | | }, |
| | | // This resource provides a read-only view of all users in the system, including |
| | | // users nested underneath entries like org units, organizations, etc., starting |
| | | // from "ou=people,dc=example,dc=com" and working down. |
| | | "all-users": { |
| | | "type": "collection", |
| | | "dnTemplate": "ou=people,dc=example,dc=com", |
| | | "resource": "frapi:opendj:rest2ldap:user:1.0", |
| | | "namingStrategy": { |
| | | "type": "clientDnNaming", |
| | | "dnAttribute": "uid" |
| | | }, |
| | | "isReadOnly": true, |
| | | "flattenSubtree": true |
| | | }, |
| | | "groups": { |
| | | "type": "collection", |
| | | "dnTemplate": "ou=groups,dc=example,dc=com", |
| | |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.Collection; |
| | | import java.util.Collections; |
| | | import java.util.HashMap; |
| | | import java.util.HashSet; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.LinkedHashSet; |
| | |
| | | * @return The unique service ID for this resource, given the specified writability. |
| | | */ |
| | | String getServiceId(boolean isReadOnly) { |
| | | StringBuilder serviceId = new StringBuilder(this.getResourceId()); |
| | | final StringBuilder serviceId = new StringBuilder(this.getResourceId()); |
| | | |
| | | if (isReadOnly) { |
| | | serviceId.append(":read-only"); |
| | |
| | | return serviceId.toString(); |
| | | } |
| | | |
| | | /** |
| | | * Gets a map of the sub-resources under this resource, keyed by URL template. |
| | | * |
| | | * @return The map of sub-resource URL templates to sub-resources. |
| | | */ |
| | | Map<String, SubResource> getSubResourceMap() { |
| | | final Map<String, SubResource> result = new HashMap<>(); |
| | | |
| | | for (SubResource subResource : this.subResources) { |
| | | result.put(subResource.getUrlTemplate(), subResource); |
| | | } |
| | | |
| | | return result; |
| | | } |
| | | |
| | | void build(final Rest2Ldap rest2Ldap) { |
| | | // Prevent re-entrant calls. |
| | | if (isBuilt) { |
| | |
| | | |
| | | String urlTemplate = ""; |
| | | String dnTemplateString = ""; |
| | | boolean isReadOnly = false; |
| | | Rest2Ldap rest2Ldap; |
| | | Resource resource; |
| | | |
| | | protected boolean isReadOnly = false; |
| | | protected Rest2Ldap rest2Ldap; |
| | | protected Resource resource; |
| | | |
| | | SubResource(final String resourceId) { |
| | | this.resourceId = resourceId; |
| | |
| | | |
| | | @Override |
| | | public final String toString() { |
| | | return getUrlTemplate(); |
| | | } |
| | | |
| | | /** |
| | | * Gets the URL template that must match for this sub-resource to apply to a given request. |
| | | * |
| | | * @return The URL template for this sub-resource. |
| | | */ |
| | | public String getUrlTemplate() { |
| | | return urlTemplate; |
| | | } |
| | | |
| | | /** |
| | | * Gets whether or not this sub-resource has been configured for read-only access. |
| | | * |
| | | * @return {@code true} if the sub-resource is read-only; {@code false} otherwise. |
| | | */ |
| | | public boolean isReadOnly() { |
| | | return isReadOnly; |
| | | } |
| | | |
| | | final Resource getResource() { |
| | | return resource; |
| | | } |
| | |
| | | } |
| | | |
| | | /** |
| | | * Gets whether or not this sub-resource should flatten sub-entries in results. |
| | | * |
| | | * @return {@code true} if entries deep in the sub-tree are included in a flattened |
| | | * collection view; {@code false} if only entries at the top level of the DN for this |
| | | * sub-resource should be returned. |
| | | */ |
| | | public boolean shouldFlattenSubtree() { |
| | | return flattenSubtree; |
| | | } |
| | | |
| | | /** |
| | | * Indicates that the JSON resource ID must be provided by the user, and will be used for naming the associated LDAP |
| | | * entry. More specifically, LDAP entry names will be derived by appending a single RDN to the collection's base DN |
| | | * composed of the specified attribute type and LDAP value taken from the LDAP entry once attribute mapping has been |
| | |
| | | import java.nio.file.Paths; |
| | | import java.util.Collections; |
| | | |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import org.forgerock.api.CrestApiProducer; |
| | | import org.forgerock.api.models.ApiDescription; |
| | | import org.forgerock.api.models.Items; |
| | |
| | | import org.forgerock.services.context.RootContext; |
| | | import org.forgerock.testng.ForgeRockTestCase; |
| | | import org.forgerock.util.Options; |
| | | import org.testng.annotations.DataProvider; |
| | | import org.testng.annotations.Test; |
| | | |
| | | import com.fasterxml.jackson.databind.ObjectMapper; |
| | |
| | | |
| | | @Test |
| | | public void testConfigureEndpointsWithApiDescription() throws Exception { |
| | | final DescribableRequestHandler handler = |
| | | createDescribableHandler(CONFIG_DIR.resolve("endpoints").toFile()); |
| | | |
| | | final File endpointsDir = CONFIG_DIR.resolve("endpoints").toFile(); |
| | | final DescribableRequestHandler handler = createDescribableHandler(endpointsDir); |
| | | final ApiDescription api = requestApi(handler, "api/users/bjensen"); |
| | | |
| | | assertThat(api).isNotNull(); |
| | |
| | | assertThat(api.getPaths().getNames()).containsOnly( |
| | | "/api/users", |
| | | "/api/read-only-users", |
| | | "/api/all-users", |
| | | "/api/groups"); |
| | | |
| | | assertThat(api.getDefinitions().getNames()).containsOnly( |
| | |
| | | } |
| | | } |
| | | |
| | | @DataProvider |
| | | public Object[][] invalidSubResourceConfigurations() { |
| | | // @Checkstyle:off |
| | | return new Object[][] { |
| | | { |
| | | "{" |
| | | + "'example-v1': {" |
| | | + "'subResources': {" |
| | | + "'writeable-collection': {" |
| | | + "'type': 'collection'," |
| | | + "'dnTemplate': 'ou=people,dc=example,dc=com'," |
| | | + "'resource': 'frapi:opendj:rest2ldap:user:1.0'," |
| | | + "'namingStrategy': {" |
| | | + "'type': 'clientDnNaming'," |
| | | + "'dnAttribute': 'uid'" |
| | | + "}," |
| | | + "'flattenSubtree': true" |
| | | + "}" |
| | | + "}" |
| | | + "}" |
| | | + "}" |
| | | }, |
| | | { |
| | | "{" |
| | | + "'example-v1': {" |
| | | + "'subResources': {" |
| | | + "'writeable-collection': {" |
| | | + "'type': 'collection'," |
| | | + "'dnTemplate': 'ou=people,dc=example,dc=com'," |
| | | + "'resource': 'frapi:opendj:rest2ldap:user:1.0'," |
| | | + "'namingStrategy': {" |
| | | + "'type': 'clientDnNaming'," |
| | | + "'dnAttribute': 'uid'" |
| | | + "}," |
| | | + "'isReadOnly': false," |
| | | + "'flattenSubtree': true" |
| | | + "}" |
| | | + "}" |
| | | + "}" |
| | | + "}" |
| | | } |
| | | }; |
| | | // @Checkstyle:on |
| | | } |
| | | |
| | | @DataProvider |
| | | public Object[][] validSubResourceConfigurations() { |
| | | // @Checkstyle:off |
| | | return new Object[][] { |
| | | { |
| | | false, |
| | | false, |
| | | "{" |
| | | + "'example-v1': {" |
| | | + "'subResources': {" |
| | | + "'all-users': {" |
| | | + "'type': 'collection'," |
| | | + "'dnTemplate': 'ou=people,dc=example,dc=com'," |
| | | + "'resource': 'frapi:opendj:rest2ldap:user:1.0'," |
| | | + "'namingStrategy': {" |
| | | + "'type': 'clientDnNaming'," |
| | | + "'dnAttribute': 'uid'" |
| | | + "}," |
| | | + "'flattenSubtree': false" |
| | | + "}" |
| | | + "}" |
| | | + "}" |
| | | + "}" |
| | | }, |
| | | { |
| | | true, |
| | | false, |
| | | "{" |
| | | + "'example-v1': {" |
| | | + "'subResources': {" |
| | | + "'all-users': {" |
| | | + "'type': 'collection'," |
| | | + "'dnTemplate': 'ou=people,dc=example,dc=com'," |
| | | + "'resource': 'frapi:opendj:rest2ldap:user:1.0'," |
| | | + "'namingStrategy': {" |
| | | + "'type': 'clientDnNaming'," |
| | | + "'dnAttribute': 'uid'" |
| | | + "}," |
| | | + "'isReadOnly': true" |
| | | + "}" |
| | | + "}" |
| | | + "}" |
| | | + "}" |
| | | }, |
| | | { |
| | | true, |
| | | false, |
| | | "{" |
| | | + "'example-v1': {" |
| | | + "'subResources': {" |
| | | + "'all-users': {" |
| | | + "'type': 'collection'," |
| | | + "'dnTemplate': 'ou=people,dc=example,dc=com'," |
| | | + "'resource': 'frapi:opendj:rest2ldap:user:1.0'," |
| | | + "'namingStrategy': {" |
| | | + "'type': 'clientDnNaming'," |
| | | + "'dnAttribute': 'uid'" |
| | | + "}," |
| | | + "'isReadOnly': true," |
| | | + "'flattenSubtree': false" |
| | | + "}" |
| | | + "}" |
| | | + "}" |
| | | + "}" |
| | | }, |
| | | { |
| | | false, |
| | | false, |
| | | "{" |
| | | + "'example-v1': {" |
| | | + "'subResources': {" |
| | | + "'all-users': {" |
| | | + "'type': 'collection'," |
| | | + "'dnTemplate': 'ou=people,dc=example,dc=com'," |
| | | + "'resource': 'frapi:opendj:rest2ldap:user:1.0'," |
| | | + "'namingStrategy': {" |
| | | + "'type': 'clientDnNaming'," |
| | | + "'dnAttribute': 'uid'" |
| | | + "}," |
| | | + "'isReadOnly': false," |
| | | + "'flattenSubtree': false" |
| | | + "}" |
| | | + "}" |
| | | + "}" |
| | | + "}" |
| | | }, |
| | | { |
| | | true, |
| | | true, |
| | | "{" |
| | | + "'example-v1': {" |
| | | + "'subResources': {" |
| | | + "'all-users': {" |
| | | + "'type': 'collection'," |
| | | + "'dnTemplate': 'ou=people,dc=example,dc=com'," |
| | | + "'resource': 'frapi:opendj:rest2ldap:user:1.0'," |
| | | + "'namingStrategy': {" |
| | | + "'type': 'clientDnNaming'," |
| | | + "'dnAttribute': 'uid'" |
| | | + "}," |
| | | + "'isReadOnly': true," |
| | | + "'flattenSubtree': true" |
| | | + "}" |
| | | + "}" |
| | | + "}" |
| | | + "}" |
| | | } |
| | | }; |
| | | // @Checkstyle:on |
| | | } |
| | | |
| | | @Test(dataProvider = "invalidSubResourceConfigurations") |
| | | public void testInvalidSubResourceConfigurations(final String rawJson) throws Exception { |
| | | try { |
| | | Rest2LdapJsonConfigurator.configureResources(parseJson(rawJson)); |
| | | |
| | | fail("Expected an IllegalArgumentException"); |
| | | } |
| | | catch (IllegalArgumentException ex) { |
| | | assertThat(ex.getMessage()) |
| | | .isEqualTo("Sub-resources must be read-only to support sub-tree flattening."); |
| | | } |
| | | } |
| | | |
| | | @Test(dataProvider = "validSubResourceConfigurations") |
| | | public void testValidSubResourceConfigurations(final boolean expectingReadOnly, |
| | | final boolean expectingSubtreeFlattened, |
| | | final String rawJson) throws Exception { |
| | | final List<org.forgerock.opendj.rest2ldap.Resource> resources = |
| | | Rest2LdapJsonConfigurator.configureResources(parseJson(rawJson)); |
| | | final org.forgerock.opendj.rest2ldap.Resource firstResource; |
| | | final Map<String, SubResource> subResources; |
| | | final SubResourceCollection allUsersSubResource; |
| | | |
| | | assertThat(resources.size()).isEqualTo(1); |
| | | |
| | | firstResource = resources.get(0); |
| | | |
| | | assertThat(firstResource.getResourceId()).isEqualTo("example-v1"); |
| | | |
| | | subResources = firstResource.getSubResourceMap(); |
| | | |
| | | assertThat(subResources.size()).isEqualTo(1); |
| | | |
| | | allUsersSubResource = (SubResourceCollection)subResources.get("all-users"); |
| | | |
| | | assertThat(allUsersSubResource.isReadOnly()).isEqualTo(expectingReadOnly); |
| | | assertThat(allUsersSubResource.shouldFlattenSubtree()).isEqualTo(expectingSubtreeFlattened); |
| | | } |
| | | |
| | | private RequestHandler createRequestHandler(final File endpointsDir) throws IOException { |
| | | return Rest2LdapJsonConfigurator.configureEndpoints(endpointsDir, Options.defaultOptions()); |
| | | } |
| | |
| | | }, |
| | | "isReadOnly": true |
| | | }, |
| | | // This resource provides a read-only view of all users in the system, including |
| | | // users nested underneath entries like org units, organizations, etc., starting |
| | | // from "ou=people,dc=example,dc=com" and working down. |
| | | "all-users": { |
| | | "type": "collection", |
| | | "dnTemplate": "ou=people,dc=example,dc=com", |
| | | "resource": "frapi:opendj:rest2ldap:user:1.0", |
| | | "namingStrategy": { |
| | | "type": "clientDnNaming", |
| | | "dnAttribute": "uid" |
| | | }, |
| | | "isReadOnly": true, |
| | | "flattenSubtree": true |
| | | }, |
| | | "groups": { |
| | | "type": "collection", |
| | | "dnTemplate": "ou=groups,dc=example,dc=com", |