View Javadoc
1   /*
2    * The contents of this file are subject to the terms of the Common Development and
3    * Distribution License (the License). You may not use this file except in compliance with the
4    * License.
5    *
6    * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7    * specific language governing permission and limitations under the License.
8    *
9    * When distributing Covered Software, include this CDDL Header Notice in each file and include
10   * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11   * Header, with the fields enclosed by brackets [] replaced by your own identifying
12   * information: "Portions Copyright [year] [name of copyright owner]".
13   *
14   * Copyright 2009-2010 Sun Microsystems, Inc.
15   * Portions Copyright 2011-2016 ForgeRock AS.
16   */
17  
18  package org.forgerock.opendj.examples;
19  
20  import static org.forgerock.opendj.ldap.Connections.newCachedConnectionPool;
21  import static org.forgerock.opendj.ldap.LDAPListener.*;
22  import static org.forgerock.opendj.ldap.requests.Requests.newSimpleBindRequest;
23  
24  import java.io.IOException;
25  import java.util.HashSet;
26  import java.util.List;
27  import java.util.Set;
28  
29  import org.forgerock.opendj.ldap.Attribute;
30  import org.forgerock.opendj.ldap.AttributeDescription;
31  import org.forgerock.opendj.ldap.Attributes;
32  import org.forgerock.opendj.ldap.ConnectionFactory;
33  import org.forgerock.opendj.ldap.Connections;
34  import org.forgerock.opendj.ldap.DN;
35  import org.forgerock.opendj.ldap.Filter;
36  import org.forgerock.opendj.ldap.IntermediateResponseHandler;
37  import org.forgerock.opendj.ldap.LDAPClientContext;
38  import org.forgerock.opendj.ldap.LDAPConnectionFactory;
39  import org.forgerock.opendj.ldap.LDAPListener;
40  import org.forgerock.opendj.ldap.LdapException;
41  import org.forgerock.opendj.ldap.LdapResultHandler;
42  import org.forgerock.opendj.ldap.Modification;
43  import org.forgerock.opendj.ldap.RequestContext;
44  import org.forgerock.opendj.ldap.RequestHandler;
45  import org.forgerock.opendj.ldap.RequestHandlerFactory;
46  import org.forgerock.opendj.ldap.SearchResultHandler;
47  import org.forgerock.opendj.ldap.ServerConnectionFactory;
48  import org.forgerock.opendj.ldap.controls.Control;
49  import org.forgerock.opendj.ldap.requests.AddRequest;
50  import org.forgerock.opendj.ldap.requests.BindRequest;
51  import org.forgerock.opendj.ldap.requests.CompareRequest;
52  import org.forgerock.opendj.ldap.requests.DeleteRequest;
53  import org.forgerock.opendj.ldap.requests.ExtendedRequest;
54  import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
55  import org.forgerock.opendj.ldap.requests.ModifyRequest;
56  import org.forgerock.opendj.ldap.requests.Requests;
57  import org.forgerock.opendj.ldap.requests.SearchRequest;
58  import org.forgerock.opendj.ldap.responses.BindResult;
59  import org.forgerock.opendj.ldap.responses.CompareResult;
60  import org.forgerock.opendj.ldap.responses.ExtendedResult;
61  import org.forgerock.opendj.ldap.responses.Result;
62  import org.forgerock.opendj.ldap.responses.SearchResultEntry;
63  import org.forgerock.opendj.ldap.responses.SearchResultReference;
64  import org.forgerock.opendj.ldap.schema.AttributeType;
65  import org.forgerock.util.Options;
66  
67  import com.forgerock.reactive.ServerConnectionFactoryAdapter;
68  
69  /**
70   * This example is based on the {@link Proxy}. This example does no load
71   * balancing, but instead rewrites attribute descriptions and DN suffixes in
72   * requests to and responses from a directory server using hard coded
73   * configuration.
74   * <ul>
75   * <li>It transforms DNs ending in {@code o=example} on the client side to end
76   * in {@code dc=example,dc=com} on the server side and vice versa.
77   * <li>It transforms the attribute description {@code fullname} on the client
78   * side to {@code cn} on the server side and vice versa.
79   * </ul>
80   *
81   * This example has a number of restrictions.
82   * <ul>
83   * <li>It does not support SSL connections.
84   * <li>It does not support StartTLS.
85   * <li>It does not support Abandon or Cancel requests.
86   * <li>It has very basic authentication and authorization support.
87   * <li>It does not rewrite bind DNs.
88   * <li>It uses proxied authorization, so if you use OpenDJ directory server, you
89   * must set the {@code proxied-auth} privilege for the proxy user.
90   * <li>It does not touch matched DNs in results.
91   * <li>It does not rewrite attributes with options in search result entries.
92   * <li>It does not touch search result references.
93   * </ul>
94   * This example takes the following command line parameters:
95   *
96   * <pre>
97   *  {@code <localAddress> <localPort> <proxyDN> <proxyPassword> <serverAddress> <serverPort>}
98   * </pre>
99   *
100  * If you have imported the users from <a
101  * href="http://opendj.forgerock.org/Example.ldif">Example.ldif</a>, then you
102  * can set {@code proxyUserDN} to {@code cn=My App,ou=Apps,dc=example,dc=com}
103  * and {@code proxyUserPassword} to {@code password}.
104  */
105 public final class RewriterProxy {
106     private static final class Rewriter implements RequestHandler<RequestContext> {
107 
108         /** This example hard codes the attribute... */
109         private static final String CLIENT_ATTRIBUTE = "fullname";
110         private static final String SERVER_ATTRIBUTE = "cn";
111 
112         /** ...and DN rewriting configuration. */
113         private static final String CLIENT_SUFFIX = "o=example";
114         private static final String SERVER_SUFFIX = "dc=example,dc=com";
115 
116         private final AttributeDescription clientAttributeDescription = AttributeDescription
117                 .valueOf(CLIENT_ATTRIBUTE);
118         private final AttributeDescription serverAttributeDescription = AttributeDescription
119                 .valueOf(SERVER_ATTRIBUTE);
120 
121         /** Next request handler in the chain. */
122         private final RequestHandler<RequestContext> nextHandler;
123 
124         private Rewriter(final RequestHandler<RequestContext> nextHandler) {
125             this.nextHandler = nextHandler;
126         }
127 
128         @Override
129         public void handleAdd(final RequestContext requestContext, final AddRequest request,
130                 final IntermediateResponseHandler intermediateResponseHandler,
131                 final LdapResultHandler<Result> resultHandler) {
132             nextHandler.handleAdd(requestContext, rewrite(request), intermediateResponseHandler,
133                     resultHandler);
134         }
135 
136         @Override
137         public void handleBind(final RequestContext requestContext, final int version,
138                 final BindRequest request,
139                 final IntermediateResponseHandler intermediateResponseHandler,
140                 final LdapResultHandler<BindResult> resultHandler) {
141             nextHandler.handleBind(requestContext, version, rewrite(request),
142                     intermediateResponseHandler, resultHandler);
143         }
144 
145         @Override
146         public void handleCompare(final RequestContext requestContext,
147                 final CompareRequest request,
148                 final IntermediateResponseHandler intermediateResponseHandler,
149                 final LdapResultHandler<CompareResult> resultHandler) {
150             nextHandler.handleCompare(requestContext, rewrite(request),
151                     intermediateResponseHandler, resultHandler);
152         }
153 
154         @Override
155         public void handleDelete(final RequestContext requestContext, final DeleteRequest request,
156                 final IntermediateResponseHandler intermediateResponseHandler,
157                 final LdapResultHandler<Result> resultHandler) {
158             nextHandler.handleDelete(requestContext, rewrite(request), intermediateResponseHandler,
159                     resultHandler);
160         }
161 
162         @Override
163         public <R extends ExtendedResult> void handleExtendedRequest(
164                 final RequestContext requestContext, final ExtendedRequest<R> request,
165                 final IntermediateResponseHandler intermediateResponseHandler,
166                 final LdapResultHandler<R> resultHandler) {
167             nextHandler.handleExtendedRequest(requestContext, rewrite(request),
168                     intermediateResponseHandler, resultHandler);
169         }
170 
171         @Override
172         public void handleModify(final RequestContext requestContext, final ModifyRequest request,
173                 final IntermediateResponseHandler intermediateResponseHandler,
174                 final LdapResultHandler<Result> resultHandler) {
175             nextHandler.handleModify(requestContext, rewrite(request), intermediateResponseHandler,
176                     resultHandler);
177         }
178 
179         @Override
180         public void handleModifyDN(final RequestContext requestContext,
181                 final ModifyDNRequest request,
182                 final IntermediateResponseHandler intermediateResponseHandler,
183                 final LdapResultHandler<Result> resultHandler) {
184             nextHandler.handleModifyDN(requestContext, rewrite(request),
185                     intermediateResponseHandler, resultHandler);
186         }
187 
188         @Override
189         public void handleSearch(final RequestContext requestContext, final SearchRequest request,
190             final IntermediateResponseHandler intermediateResponseHandler,
191             final SearchResultHandler entryHandler, final LdapResultHandler<Result> resultHandler) {
192             nextHandler.handleSearch(requestContext, rewrite(request), intermediateResponseHandler,
193                 new SearchResultHandler() {
194                     @Override
195                     public boolean handleReference(SearchResultReference reference) {
196                         return entryHandler.handleReference(reference);
197                     }
198 
199                     @Override
200                     public boolean handleEntry(SearchResultEntry entry) {
201                         return entryHandler.handleEntry(rewrite(entry));
202                     }
203                 }, resultHandler);
204         }
205 
206         private AddRequest rewrite(final AddRequest request) {
207             // Transform the client DN into a server DN.
208             final AddRequest rewrittenRequest = Requests.copyOfAddRequest(request);
209             rewrittenRequest.setName(request.getName().toString().replace(CLIENT_SUFFIX,
210                     SERVER_SUFFIX));
211             /*
212              * Transform the client attribute names into server attribute names,
213              * fullname;lang-fr ==> cn;lang-fr.
214              */
215             for (final Attribute a : request.getAllAttributes(clientAttributeDescription)) {
216                 if (a != null) {
217                     final String ad =
218                             a.getAttributeDescriptionAsString().replaceFirst(
219                                     CLIENT_ATTRIBUTE, SERVER_ATTRIBUTE);
220                     final Attribute serverAttr =
221                             Attributes.renameAttribute(a, AttributeDescription.valueOf(ad));
222                     rewrittenRequest.addAttribute(serverAttr);
223                     rewrittenRequest.removeAttribute(a.getAttributeDescription());
224                 }
225             }
226             return rewrittenRequest;
227         }
228 
229         private BindRequest rewrite(final BindRequest request) {
230             // TODO: Transform client DN into server DN.
231             return request;
232         }
233 
234         private CompareRequest rewrite(final CompareRequest request) {
235             /*
236              * Transform the client attribute name into a server attribute name,
237              * fullname;lang-fr ==> cn;lang-fr.
238              */
239             final String ad = request.getAttributeDescription().toString();
240             if (ad.toLowerCase().startsWith(CLIENT_ATTRIBUTE.toLowerCase())) {
241                 final String serverAttrDesc =
242                         ad.replaceFirst(CLIENT_ATTRIBUTE, SERVER_ATTRIBUTE);
243                 request.setAttributeDescription(AttributeDescription.valueOf(serverAttrDesc));
244             }
245 
246             // Transform the client DN into a server DN.
247             return request
248                     .setName(request.getName().toString().replace(CLIENT_SUFFIX, SERVER_SUFFIX));
249         }
250 
251         private DeleteRequest rewrite(final DeleteRequest request) {
252             // Transform the client DN into a server DN.
253             return request
254                     .setName(request.getName().toString().replace(CLIENT_SUFFIX, SERVER_SUFFIX));
255         }
256 
257         private <S extends ExtendedResult> ExtendedRequest<S> rewrite(
258                 final ExtendedRequest<S> request) {
259             // TODO: Transform password modify, etc.
260             return request;
261         }
262 
263         private ModifyDNRequest rewrite(final ModifyDNRequest request) {
264             // Transform the client DNs into server DNs.
265             if (request.getNewSuperior() != null) {
266                 return request.setName(
267                         request.getName().toString().replace(CLIENT_SUFFIX, SERVER_SUFFIX))
268                         .setNewSuperior(
269                                 request.getNewSuperior().toString().replace(CLIENT_SUFFIX,
270                                         SERVER_SUFFIX));
271             } else {
272                 return request.setName(request.getName().toString().replace(CLIENT_SUFFIX,
273                         SERVER_SUFFIX));
274             }
275         }
276 
277         private ModifyRequest rewrite(final ModifyRequest request) {
278             // Transform the client DN into a server DN.
279             final ModifyRequest rewrittenRequest =
280                     Requests.newModifyRequest(request.getName().toString().replace(CLIENT_SUFFIX,
281                             SERVER_SUFFIX));
282 
283             /*
284              * Transform the client attribute names into server attribute names,
285              * fullname;lang-fr ==> cn;lang-fr.
286              */
287             final List<Modification> mods = request.getModifications();
288             for (final Modification mod : mods) {
289                 final Attribute a = mod.getAttribute();
290                 final AttributeDescription ad = a.getAttributeDescription();
291                 final AttributeType at = ad.getAttributeType();
292 
293                 if (at.equals(clientAttributeDescription.getAttributeType())) {
294                     final AttributeDescription serverAttrDesc =
295                             AttributeDescription.valueOf(ad.toString().replaceFirst(
296                                     CLIENT_ATTRIBUTE, SERVER_ATTRIBUTE));
297                     rewrittenRequest.addModification(new Modification(mod.getModificationType(),
298                             Attributes.renameAttribute(a, serverAttrDesc)));
299                 } else {
300                     rewrittenRequest.addModification(mod);
301                 }
302             }
303             for (final Control control : request.getControls()) {
304                 rewrittenRequest.addControl(control);
305             }
306 
307             return rewrittenRequest;
308         }
309 
310         private SearchRequest rewrite(final SearchRequest request) {
311             /*
312              * Transform the client attribute names to a server attribute names,
313              * fullname;lang-fr ==> cn;lang-fr.
314              */
315             final String[] a = new String[request.getAttributes().size()];
316             int count = 0;
317             for (final String attrName : request.getAttributes()) {
318                 if (attrName.toLowerCase().startsWith(CLIENT_ATTRIBUTE.toLowerCase())) {
319                     a[count] =
320                             attrName.replaceFirst(CLIENT_ATTRIBUTE, SERVER_ATTRIBUTE);
321                 } else {
322                     a[count] = attrName;
323                 }
324                 ++count;
325             }
326 
327             /*
328              * Rewrite the baseDN, and rewrite the Filter in dangerously lazy
329              * fashion. All the filter rewrite does is a string replace, so if
330              * the client attribute name appears in the value part of the AVA,
331              * this implementation will not work.
332              */
333             return Requests.newSearchRequest(DN.valueOf(request.getName().toString().replace(
334                     CLIENT_SUFFIX, SERVER_SUFFIX)), request.getScope(), Filter.valueOf(request
335                     .getFilter().toString().replace(CLIENT_ATTRIBUTE,
336                             SERVER_ATTRIBUTE)), a);
337         }
338 
339         private SearchResultEntry rewrite(final SearchResultEntry entry) {
340             // Replace server attributes with client attributes.
341             final Set<Attribute> attrsToAdd = new HashSet<>();
342             final Set<AttributeDescription> attrsToRemove = new HashSet<>();
343 
344             for (final Attribute a : entry.getAllAttributes(serverAttributeDescription)) {
345                 final AttributeDescription ad = a.getAttributeDescription();
346                 final AttributeType at = ad.getAttributeType();
347                 if (at.equals(serverAttributeDescription.getAttributeType())) {
348                     final AttributeDescription clientAttrDesc =
349                             AttributeDescription.valueOf(ad.toString().replaceFirst(
350                                     SERVER_ATTRIBUTE, CLIENT_ATTRIBUTE));
351                     attrsToAdd.add(Attributes.renameAttribute(a, clientAttrDesc));
352                     attrsToRemove.add(ad);
353                 }
354             }
355 
356             if (!attrsToAdd.isEmpty() && !attrsToRemove.isEmpty()) {
357                 for (final Attribute a : attrsToAdd) {
358                     entry.addAttribute(a);
359                 }
360                 for (final AttributeDescription ad : attrsToRemove) {
361                     entry.removeAttribute(ad);
362                 }
363             }
364 
365             // Transform the server DN suffix into a client DN suffix.
366             return entry.setName(entry.getName().toString().replace(SERVER_SUFFIX, CLIENT_SUFFIX));
367 
368         }
369 
370     }
371 
372     /**
373      * Main method.
374      *
375      * @param args
376      *            The command line arguments: local address, local port, proxy
377      *            user DN, proxy user password, server address, server port
378      */
379     public static void main(final String[] args) {
380         if (args.length != 6) {
381             System.err.println("Usage:" + "\tlocalAddress localPort proxyDN proxyPassword "
382                     + "serverAddress serverPort");
383             System.exit(1);
384         }
385 
386         final String localAddress = args[0];
387         final int localPort = Integer.parseInt(args[1]);
388         final String proxyDN = args[2];
389         final String proxyPassword = args[3];
390         final String remoteAddress = args[4];
391         final int remotePort = Integer.parseInt(args[5]);
392 
393         // Create connection factories.
394         final Options factoryOptions = Options.defaultOptions()
395                                    .set(LDAPConnectionFactory.AUTHN_BIND_REQUEST,
396                                         newSimpleBindRequest(proxyDN, proxyPassword.toCharArray()));
397         final ConnectionFactory factory = newCachedConnectionPool(new LDAPConnectionFactory(remoteAddress,
398                                                                                             remotePort,
399                                                                                             factoryOptions));
400         final ConnectionFactory bindFactory = newCachedConnectionPool(new LDAPConnectionFactory(remoteAddress,
401                                                                                                 remotePort));
402 
403         /*
404          * Create a server connection adapter which will create a new proxy
405          * backend for each inbound client connection. This is required because
406          * we need to maintain authorization state between client requests. The
407          * proxy bound will be wrapped in a rewriter in order to transform
408          * inbound requests and their responses.
409          */
410         final RequestHandlerFactory<LDAPClientContext, RequestContext> proxyFactory =
411                 new RequestHandlerFactory<LDAPClientContext, RequestContext>() {
412                     @Override
413                     public Rewriter handleAccept(final LDAPClientContext clientContext) throws LdapException {
414                         return new Rewriter(new ProxyBackend(factory, bindFactory));
415                     }
416                 };
417         final ServerConnectionFactory<LDAPClientContext, Integer> connectionHandler =
418                 Connections.newServerConnectionFactory(proxyFactory);
419 
420         // Create listener.
421         final Options listenerOptions = Options.defaultOptions().set(CONNECT_MAX_BACKLOG, 4096);
422         LDAPListener listener = null;
423         try {
424             listener = new LDAPListener(localAddress, localPort, new ServerConnectionFactoryAdapter(
425                     listenerOptions.get(LDAP_DECODE_OPTIONS), connectionHandler), listenerOptions);
426             System.out.println("Press any key to stop the server...");
427             System.in.read();
428         } catch (final IOException e) {
429             System.out.println("Error listening on " + localAddress + ":" + localPort);
430             e.printStackTrace();
431         } finally {
432             if (listener != null) {
433                 listener.close();
434             }
435         }
436     }
437 
438     private RewriterProxy() {
439         // Not used.
440     }
441 }