opendj3/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json
@@ -136,6 +136,12 @@ "/users" : { "baseDN" : "ou=people,dc=example,dc=com", "readOnUpdatePolicy" : "controls", "useSubtreeDelete" : true, "etagAttribute" : "etag", "namingStrategy" : { "strategy" : "clientDNNaming", "dnAttribute" : "uid" }, "additionalLDAPAttributes" : [ { "type" : "objectClass", @@ -147,11 +153,6 @@ ] } ], "namingStrategy" : { "strategy" : "clientDNNaming", "dnAttribute" : "uid" }, "etagAttribute" : "etag", "attributes" : { "schemas" : { "constant" : [ "urn:scim:schemas:core:1.0" ] }, "_id" : { "simple" : { "ldapAttribute" : "uid", "isSingleValued" : true, "isRequired" : true, "writability" : "createOnly" } }, opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java
@@ -23,20 +23,22 @@ * Common configuration options. */ final class Config { private final AuthorizationPolicy authzPolicy; private final ConnectionFactory factory; private final DecodeOptions options; private final AuthorizationPolicy authzPolicy; private final AuthzIdTemplate proxiedAuthzTemplate; private final ReadOnUpdatePolicy readOnUpdatePolicy; private final Schema schema; private final boolean useSubtreeDelete; Config(final ConnectionFactory factory, final ReadOnUpdatePolicy readOnUpdatePolicy, final AuthorizationPolicy authzPolicy, final AuthzIdTemplate proxiedAuthzTemplate, final Schema schema) { final boolean useSubtreeDelete, final Schema schema) { this.factory = factory; this.readOnUpdatePolicy = readOnUpdatePolicy; this.authzPolicy = authzPolicy; this.proxiedAuthzTemplate = proxiedAuthzTemplate; this.useSubtreeDelete = useSubtreeDelete; this.schema = schema; this.options = new DecodeOptions().setSchema(schema); } @@ -86,6 +88,17 @@ } /** * Returns {@code true} if delete requests should include the subtree delete * control. * * @return {@code true} if delete requests should include the subtree delete * control. */ boolean useSubtreeDelete() { return useSubtreeDelete; } /** * Returns the policy which should be used in order to read an entry before * it is deleted, or after it is added or modified. * opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
@@ -54,6 +54,7 @@ import org.forgerock.json.resource.UncategorizedException; import org.forgerock.json.resource.UpdateRequest; import org.forgerock.opendj.ldap.Attribute; import org.forgerock.opendj.ldap.AttributeDescription; import org.forgerock.opendj.ldap.DN; import org.forgerock.opendj.ldap.DecodeException; import org.forgerock.opendj.ldap.Entry; @@ -62,10 +63,12 @@ import org.forgerock.opendj.ldap.Function; import org.forgerock.opendj.ldap.SearchResultHandler; import org.forgerock.opendj.ldap.SearchScope; import org.forgerock.opendj.ldap.controls.AssertionRequestControl; import org.forgerock.opendj.ldap.controls.PostReadRequestControl; import org.forgerock.opendj.ldap.controls.PostReadResponseControl; import org.forgerock.opendj.ldap.controls.PreReadRequestControl; import org.forgerock.opendj.ldap.controls.PreReadResponseControl; import org.forgerock.opendj.ldap.controls.SubtreeDeleteRequestControl; import org.forgerock.opendj.ldap.requests.AddRequest; import org.forgerock.opendj.ldap.requests.Requests; import org.forgerock.opendj.ldap.requests.SearchRequest; @@ -86,17 +89,17 @@ private final AttributeMapper attributeMapper; private final DN baseDN; // TODO: support template variables. private final Config config; private final MVCCStrategy mvccStrategy; private final AttributeDescription etagAttribute; private final NameStrategy nameStrategy; LDAPCollectionResourceProvider(final DN baseDN, final AttributeMapper mapper, final NameStrategy nameStrategy, final MVCCStrategy mvccStrategy, final Config config, final List<Attribute> additionalLDAPAttributes) { final NameStrategy nameStrategy, final AttributeDescription etagAttribute, final Config config, final List<Attribute> additionalLDAPAttributes) { this.baseDN = baseDN; this.attributeMapper = mapper; this.config = config; this.nameStrategy = nameStrategy; this.mvccStrategy = mvccStrategy; this.etagAttribute = etagAttribute; this.additionalLDAPAttributes = additionalLDAPAttributes; } @@ -167,8 +170,6 @@ final Context c = wrap(context); final ResultHandler<Resource> h = wrap(c, handler); // FIXME: assertion and subtree delete controls. // Get connection then perform the search. c.run(h, new Runnable() { @Override @@ -186,6 +187,7 @@ @Override public void handleResult(final SearchResultEntry entry) { try { // Perform delete operation. final ChangeRecord deleteRequest = Requests.newDeleteRequest(entry.getName()); @@ -195,8 +197,16 @@ deleteRequest.addControl(PreReadRequestControl.newControl( false, attributes)); } if (config.useSubtreeDelete()) { deleteRequest.addControl(SubtreeDeleteRequestControl .newControl(true)); } addAssertionControl(deleteRequest, request.getRevision()); c.getConnection().applyChangeAsync(deleteRequest, null, postUpdateHandler(c, h)); } catch (final Exception e) { h.handleError(asResourceException(e)); } } }); } @@ -260,8 +270,7 @@ } final String id = nameStrategy.getResourceId(c, entry); final String revision = mvccStrategy.getRevisionFromEntry(c, entry); final String revision = getRevisionFromEntry(entry); final ResultHandler<JsonValue> mapHandler = new ResultHandler<JsonValue>() { @Override @@ -370,7 +379,7 @@ private void adaptEntry(final Context c, final Entry entry, final ResultHandler<Resource> handler) { final String actualResourceId = nameStrategy.getResourceId(c, entry); final String revision = mvccStrategy.getRevisionFromEntry(c, entry); final String revision = getRevisionFromEntry(entry); attributeMapper.toJSON(c, entry, transform(new Function<JsonValue, Resource, Void>() { @Override public Resource apply(final JsonValue value, final Void p) { @@ -379,6 +388,20 @@ }, handler)); } private void addAssertionControl(final ChangeRecord request, final String revision) throws NotSupportedException { if (revision != null) { if (etagAttribute != null) { request.addControl(AssertionRequestControl.newControl(true, Filter.equality( etagAttribute.toString(), revision))); } else { // FIXME: i18n throw new NotSupportedException( "Multi-version concurrency control is not supported by this resource"); } } } private DN getBaseDN(final Context context) { return baseDN; } @@ -410,7 +433,9 @@ // Get the LDAP attributes required by the Etag and name stategies. nameStrategy.getLDAPAttributes(c, requestedLDAPAttributes); mvccStrategy.getLDAPAttributes(c, requestedLDAPAttributes); if (etagAttribute != null) { requestedLDAPAttributes.add(etagAttribute.toString()); } return requestedLDAPAttributes.toArray(new String[requestedLDAPAttributes.size()]); } @@ -590,6 +615,10 @@ queryFilter.accept(visitor, h); } private String getRevisionFromEntry(final Entry entry) { return etagAttribute != null ? entry.parseAttribute(etagAttribute).asString() : null; } private org.forgerock.opendj.ldap.ResultHandler<Result> postUpdateHandler(final Context c, final ResultHandler<Resource> handler) { // The handler which will be invoked for the LDAP add result. opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/MVCCStrategy.java
File was deleted opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
@@ -79,12 +79,13 @@ private AuthorizationPolicy authzPolicy = AuthorizationPolicy.NONE; private DN baseDN; // TODO: support template variables. private ConnectionFactory factory; private MVCCStrategy mvccStrategy; private AttributeDescription etagAttribute; private NameStrategy nameStrategy; private AuthzIdTemplate proxiedAuthzTemplate; private ReadOnUpdatePolicy readOnUpdatePolicy = CONTROLS; private AttributeMapper rootMapper; private Schema schema = Schema.getDefaultSchema(); private boolean useSubtreeDelete; Builder() { useEtagAttribute(); @@ -142,8 +143,9 @@ break; } return new LDAPCollectionResourceProvider(baseDN, rootMapper, nameStrategy, mvccStrategy, new Config(factory, readOnUpdatePolicy, authzPolicy, proxiedAuthzTemplate, schema), additionalLDAPAttributes); etagAttribute, new Config(factory, readOnUpdatePolicy, authzPolicy, proxiedAuthzTemplate, useSubtreeDelete, schema), additionalLDAPAttributes); } /** @@ -194,6 +196,10 @@ useEtagAttribute(etagAttribute.asString()); } if (configuration.get("useSubtreeDelete").required().asBoolean()) { useSubtreeDelete(); } mapper(configureObjectMapper(configuration.get("attributes").required())); return this; @@ -267,7 +273,7 @@ } public Builder useEtagAttribute(final AttributeDescription attribute) { this.mvccStrategy = new AttributeMVCCStrategy(attribute); this.etagAttribute = attribute; return this; } @@ -294,6 +300,11 @@ return useServerNaming(at(dnAttribute), ad(idAttribute)); } public Builder useSubtreeDelete() { this.useSubtreeDelete = true; return this; } private AttributeDescription ad(final String attribute) { return AttributeDescription.valueOf(attribute, schema); } @@ -386,24 +397,6 @@ } } private static final class AttributeMVCCStrategy extends MVCCStrategy { private final AttributeDescription ldapAttribute; private AttributeMVCCStrategy(final AttributeDescription ldapAttribute) { this.ldapAttribute = ldapAttribute; } @Override void getLDAPAttributes(final Context c, final Set<String> ldapAttributes) { ldapAttributes.add(ldapAttribute.toString()); } @Override String getRevisionFromEntry(final Context c, final Entry entry) { return entry.parseAttribute(ldapAttribute).asString(); } } private static final class AttributeNameStrategy extends NameStrategy { private final AttributeDescription dnAttribute; private final AttributeDescription idAttribute; opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/BasicRequestsTest.java
@@ -27,9 +27,9 @@ import java.io.IOException; import org.forgerock.json.resource.ConflictException; import org.forgerock.json.resource.Connection; import org.forgerock.json.resource.NotFoundException; import org.forgerock.json.resource.PreconditionFailedException; import org.forgerock.json.resource.RequestHandler; import org.forgerock.json.resource.Resource; import org.forgerock.json.resource.RootContext; @@ -79,7 +79,7 @@ } } @Test(expectedExceptions = ConflictException.class, enabled = false) @Test(expectedExceptions = PreconditionFailedException.class) public void testDeleteMVCCNoMatch() throws Exception { final RequestHandler handler = newCollection(builder().build()); final Connection connection = newInternalConnection(handler);