001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2009-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2011-2015 ForgeRock AS.
026 */
027
028package org.forgerock.opendj.examples;
029
030import static org.forgerock.opendj.ldap.Connections.newCachedConnectionPool;
031import static org.forgerock.opendj.ldap.LDAPListener.CONNECT_MAX_BACKLOG;
032import static org.forgerock.opendj.ldap.requests.Requests.newSimpleBindRequest;
033
034import java.io.IOException;
035import java.util.HashSet;
036import java.util.List;
037import java.util.Set;
038
039import org.forgerock.opendj.ldap.Attribute;
040import org.forgerock.opendj.ldap.AttributeDescription;
041import org.forgerock.opendj.ldap.Attributes;
042import org.forgerock.opendj.ldap.ConnectionFactory;
043import org.forgerock.opendj.ldap.Connections;
044import org.forgerock.opendj.ldap.DN;
045import org.forgerock.opendj.ldap.Filter;
046import org.forgerock.opendj.ldap.IntermediateResponseHandler;
047import org.forgerock.opendj.ldap.LDAPClientContext;
048import org.forgerock.opendj.ldap.LDAPConnectionFactory;
049import org.forgerock.opendj.ldap.LDAPListener;
050import org.forgerock.opendj.ldap.LdapException;
051import org.forgerock.opendj.ldap.LdapResultHandler;
052import org.forgerock.opendj.ldap.Modification;
053import org.forgerock.opendj.ldap.RequestContext;
054import org.forgerock.opendj.ldap.RequestHandler;
055import org.forgerock.opendj.ldap.RequestHandlerFactory;
056import org.forgerock.opendj.ldap.SearchResultHandler;
057import org.forgerock.opendj.ldap.ServerConnectionFactory;
058import org.forgerock.opendj.ldap.controls.Control;
059import org.forgerock.opendj.ldap.requests.AddRequest;
060import org.forgerock.opendj.ldap.requests.BindRequest;
061import org.forgerock.opendj.ldap.requests.CompareRequest;
062import org.forgerock.opendj.ldap.requests.DeleteRequest;
063import org.forgerock.opendj.ldap.requests.ExtendedRequest;
064import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
065import org.forgerock.opendj.ldap.requests.ModifyRequest;
066import org.forgerock.opendj.ldap.requests.Requests;
067import org.forgerock.opendj.ldap.requests.SearchRequest;
068import org.forgerock.opendj.ldap.responses.BindResult;
069import org.forgerock.opendj.ldap.responses.CompareResult;
070import org.forgerock.opendj.ldap.responses.ExtendedResult;
071import org.forgerock.opendj.ldap.responses.Result;
072import org.forgerock.opendj.ldap.responses.SearchResultEntry;
073import org.forgerock.opendj.ldap.responses.SearchResultReference;
074import org.forgerock.opendj.ldap.schema.AttributeType;
075import org.forgerock.util.Options;
076
077/**
078 * This example is based on the {@link Proxy}. This example does no load
079 * balancing, but instead rewrites attribute descriptions and DN suffixes in
080 * requests to and responses from a directory server using hard coded
081 * configuration.
082 * <ul>
083 * <li>It transforms DNs ending in {@code o=example} on the client side to end
084 * in {@code dc=example,dc=com} on the server side and vice versa.
085 * <li>It transforms the attribute description {@code fullname} on the client
086 * side to {@code cn} on the server side and vice versa.
087 * </ul>
088 *
089 * This example has a number of restrictions.
090 * <ul>
091 * <li>It does not support SSL connections.
092 * <li>It does not support StartTLS.
093 * <li>It does not support Abandon or Cancel requests.
094 * <li>It has very basic authentication and authorization support.
095 * <li>It does not rewrite bind DNs.
096 * <li>It uses proxied authorization, so if you use OpenDJ directory server, you
097 * must set the {@code proxied-auth} privilege for the proxy user.
098 * <li>It does not touch matched DNs in results.
099 * <li>It does not rewrite attributes with options in search result entries.
100 * <li>It does not touch search result references.
101 * </ul>
102 * This example takes the following command line parameters:
103 *
104 * <pre>
105 *  {@code <localAddress> <localPort> <proxyDN> <proxyPassword> <serverAddress> <serverPort>}
106 * </pre>
107 *
108 * If you have imported the users from <a
109 * href="http://opendj.forgerock.org/Example.ldif">Example.ldif</a>, then you
110 * can set {@code proxyUserDN} to {@code cn=My App,ou=Apps,dc=example,dc=com}
111 * and {@code proxyUserPassword} to {@code password}.
112 */
113public final class RewriterProxy {
114    private static final class Rewriter implements RequestHandler<RequestContext> {
115
116        /** This example hard codes the attribute... */
117        private static final String CLIENT_ATTRIBUTE = "fullname";
118        private static final String SERVER_ATTRIBUTE = "cn";
119
120        /** ...and DN rewriting configuration. */
121        private static final String CLIENT_SUFFIX = "o=example";
122        private static final String SERVER_SUFFIX = "dc=example,dc=com";
123
124        private final AttributeDescription clientAttributeDescription = AttributeDescription
125                .valueOf(CLIENT_ATTRIBUTE);
126        private final AttributeDescription serverAttributeDescription = AttributeDescription
127                .valueOf(SERVER_ATTRIBUTE);
128
129        /** Next request handler in the chain. */
130        private final RequestHandler<RequestContext> nextHandler;
131
132        private Rewriter(final RequestHandler<RequestContext> nextHandler) {
133            this.nextHandler = nextHandler;
134        }
135
136        @Override
137        public void handleAdd(final RequestContext requestContext, final AddRequest request,
138                final IntermediateResponseHandler intermediateResponseHandler,
139                final LdapResultHandler<Result> resultHandler) {
140            nextHandler.handleAdd(requestContext, rewrite(request), intermediateResponseHandler,
141                    resultHandler);
142        }
143
144        @Override
145        public void handleBind(final RequestContext requestContext, final int version,
146                final BindRequest request,
147                final IntermediateResponseHandler intermediateResponseHandler,
148                final LdapResultHandler<BindResult> resultHandler) {
149            nextHandler.handleBind(requestContext, version, rewrite(request),
150                    intermediateResponseHandler, resultHandler);
151        }
152
153        @Override
154        public void handleCompare(final RequestContext requestContext,
155                final CompareRequest request,
156                final IntermediateResponseHandler intermediateResponseHandler,
157                final LdapResultHandler<CompareResult> resultHandler) {
158            nextHandler.handleCompare(requestContext, rewrite(request),
159                    intermediateResponseHandler, resultHandler);
160        }
161
162        @Override
163        public void handleDelete(final RequestContext requestContext, final DeleteRequest request,
164                final IntermediateResponseHandler intermediateResponseHandler,
165                final LdapResultHandler<Result> resultHandler) {
166            nextHandler.handleDelete(requestContext, rewrite(request), intermediateResponseHandler,
167                    resultHandler);
168        }
169
170        @Override
171        public <R extends ExtendedResult> void handleExtendedRequest(
172                final RequestContext requestContext, final ExtendedRequest<R> request,
173                final IntermediateResponseHandler intermediateResponseHandler,
174                final LdapResultHandler<R> resultHandler) {
175            nextHandler.handleExtendedRequest(requestContext, rewrite(request),
176                    intermediateResponseHandler, resultHandler);
177        }
178
179        @Override
180        public void handleModify(final RequestContext requestContext, final ModifyRequest request,
181                final IntermediateResponseHandler intermediateResponseHandler,
182                final LdapResultHandler<Result> resultHandler) {
183            nextHandler.handleModify(requestContext, rewrite(request), intermediateResponseHandler,
184                    resultHandler);
185        }
186
187        @Override
188        public void handleModifyDN(final RequestContext requestContext,
189                final ModifyDNRequest request,
190                final IntermediateResponseHandler intermediateResponseHandler,
191                final LdapResultHandler<Result> resultHandler) {
192            nextHandler.handleModifyDN(requestContext, rewrite(request),
193                    intermediateResponseHandler, resultHandler);
194        }
195
196        @Override
197        public void handleSearch(final RequestContext requestContext, final SearchRequest request,
198            final IntermediateResponseHandler intermediateResponseHandler,
199            final SearchResultHandler entryHandler, final LdapResultHandler<Result> resultHandler) {
200            nextHandler.handleSearch(requestContext, rewrite(request), intermediateResponseHandler,
201                new SearchResultHandler() {
202                    @Override
203                    public boolean handleReference(SearchResultReference reference) {
204                        return entryHandler.handleReference(reference);
205                    }
206
207                    @Override
208                    public boolean handleEntry(SearchResultEntry entry) {
209                        return entryHandler.handleEntry(rewrite(entry));
210                    }
211                }, resultHandler);
212        }
213
214        private AddRequest rewrite(final AddRequest request) {
215            // Transform the client DN into a server DN.
216            final AddRequest rewrittenRequest = Requests.copyOfAddRequest(request);
217            rewrittenRequest.setName(request.getName().toString().replace(CLIENT_SUFFIX,
218                    SERVER_SUFFIX));
219            /*
220             * Transform the client attribute names into server attribute names,
221             * fullname;lang-fr ==> cn;lang-fr.
222             */
223            for (final Attribute a : request.getAllAttributes(clientAttributeDescription)) {
224                if (a != null) {
225                    final String ad =
226                            a.getAttributeDescriptionAsString().replaceFirst(
227                                    CLIENT_ATTRIBUTE, SERVER_ATTRIBUTE);
228                    final Attribute serverAttr =
229                            Attributes.renameAttribute(a, AttributeDescription.valueOf(ad));
230                    rewrittenRequest.addAttribute(serverAttr);
231                    rewrittenRequest.removeAttribute(a.getAttributeDescription());
232                }
233            }
234            return rewrittenRequest;
235        }
236
237        private BindRequest rewrite(final BindRequest request) {
238            // TODO: Transform client DN into server DN.
239            return request;
240        }
241
242        private CompareRequest rewrite(final CompareRequest request) {
243            /*
244             * Transform the client attribute name into a server attribute name,
245             * fullname;lang-fr ==> cn;lang-fr.
246             */
247            final String ad = request.getAttributeDescription().toString();
248            if (ad.toLowerCase().startsWith(CLIENT_ATTRIBUTE.toLowerCase())) {
249                final String serverAttrDesc =
250                        ad.replaceFirst(CLIENT_ATTRIBUTE, SERVER_ATTRIBUTE);
251                request.setAttributeDescription(AttributeDescription.valueOf(serverAttrDesc));
252            }
253
254            // Transform the client DN into a server DN.
255            return request
256                    .setName(request.getName().toString().replace(CLIENT_SUFFIX, SERVER_SUFFIX));
257        }
258
259        private DeleteRequest rewrite(final DeleteRequest request) {
260            // Transform the client DN into a server DN.
261            return request
262                    .setName(request.getName().toString().replace(CLIENT_SUFFIX, SERVER_SUFFIX));
263        }
264
265        private <S extends ExtendedResult> ExtendedRequest<S> rewrite(
266                final ExtendedRequest<S> request) {
267            // TODO: Transform password modify, etc.
268            return request;
269        }
270
271        private ModifyDNRequest rewrite(final ModifyDNRequest request) {
272            // Transform the client DNs into server DNs.
273            if (request.getNewSuperior() != null) {
274                return request.setName(
275                        request.getName().toString().replace(CLIENT_SUFFIX, SERVER_SUFFIX))
276                        .setNewSuperior(
277                                request.getNewSuperior().toString().replace(CLIENT_SUFFIX,
278                                        SERVER_SUFFIX));
279            } else {
280                return request.setName(request.getName().toString().replace(CLIENT_SUFFIX,
281                        SERVER_SUFFIX));
282            }
283        }
284
285        private ModifyRequest rewrite(final ModifyRequest request) {
286            // Transform the client DN into a server DN.
287            final ModifyRequest rewrittenRequest =
288                    Requests.newModifyRequest(request.getName().toString().replace(CLIENT_SUFFIX,
289                            SERVER_SUFFIX));
290
291            /*
292             * Transform the client attribute names into server attribute names,
293             * fullname;lang-fr ==> cn;lang-fr.
294             */
295            final List<Modification> mods = request.getModifications();
296            for (final Modification mod : mods) {
297                final Attribute a = mod.getAttribute();
298                final AttributeDescription ad = a.getAttributeDescription();
299                final AttributeType at = ad.getAttributeType();
300
301                if (at.equals(clientAttributeDescription.getAttributeType())) {
302                    final AttributeDescription serverAttrDesc =
303                            AttributeDescription.valueOf(ad.toString().replaceFirst(
304                                    CLIENT_ATTRIBUTE, SERVER_ATTRIBUTE));
305                    rewrittenRequest.addModification(new Modification(mod.getModificationType(),
306                            Attributes.renameAttribute(a, serverAttrDesc)));
307                } else {
308                    rewrittenRequest.addModification(mod);
309                }
310            }
311            for (final Control control : request.getControls()) {
312                rewrittenRequest.addControl(control);
313            }
314
315            return rewrittenRequest;
316        }
317
318        private SearchRequest rewrite(final SearchRequest request) {
319            /*
320             * Transform the client attribute names to a server attribute names,
321             * fullname;lang-fr ==> cn;lang-fr.
322             */
323            final String[] a = new String[request.getAttributes().size()];
324            int count = 0;
325            for (final String attrName : request.getAttributes()) {
326                if (attrName.toLowerCase().startsWith(CLIENT_ATTRIBUTE.toLowerCase())) {
327                    a[count] =
328                            attrName.replaceFirst(CLIENT_ATTRIBUTE, SERVER_ATTRIBUTE);
329                } else {
330                    a[count] = attrName;
331                }
332                ++count;
333            }
334
335            /*
336             * Rewrite the baseDN, and rewrite the Filter in dangerously lazy
337             * fashion. All the filter rewrite does is a string replace, so if
338             * the client attribute name appears in the value part of the AVA,
339             * this implementation will not work.
340             */
341            return Requests.newSearchRequest(DN.valueOf(request.getName().toString().replace(
342                    CLIENT_SUFFIX, SERVER_SUFFIX)), request.getScope(), Filter.valueOf(request
343                    .getFilter().toString().replace(CLIENT_ATTRIBUTE,
344                            SERVER_ATTRIBUTE)), a);
345        }
346
347        private SearchResultEntry rewrite(final SearchResultEntry entry) {
348            // Replace server attributes with client attributes.
349            final Set<Attribute> attrsToAdd = new HashSet<>();
350            final Set<AttributeDescription> attrsToRemove = new HashSet<>();
351
352            for (final Attribute a : entry.getAllAttributes(serverAttributeDescription)) {
353                final AttributeDescription ad = a.getAttributeDescription();
354                final AttributeType at = ad.getAttributeType();
355                if (at.equals(serverAttributeDescription.getAttributeType())) {
356                    final AttributeDescription clientAttrDesc =
357                            AttributeDescription.valueOf(ad.toString().replaceFirst(
358                                    SERVER_ATTRIBUTE, CLIENT_ATTRIBUTE));
359                    attrsToAdd.add(Attributes.renameAttribute(a, clientAttrDesc));
360                    attrsToRemove.add(ad);
361                }
362            }
363
364            if (!attrsToAdd.isEmpty() && !attrsToRemove.isEmpty()) {
365                for (final Attribute a : attrsToAdd) {
366                    entry.addAttribute(a);
367                }
368                for (final AttributeDescription ad : attrsToRemove) {
369                    entry.removeAttribute(ad);
370                }
371            }
372
373            // Transform the server DN suffix into a client DN suffix.
374            return entry.setName(entry.getName().toString().replace(SERVER_SUFFIX, CLIENT_SUFFIX));
375
376        }
377
378    }
379
380    /**
381     * Main method.
382     *
383     * @param args
384     *            The command line arguments: local address, local port, proxy
385     *            user DN, proxy user password, server address, server port
386     */
387    public static void main(final String[] args) {
388        if (args.length != 6) {
389            System.err.println("Usage:" + "\tlocalAddress localPort proxyDN proxyPassword "
390                    + "serverAddress serverPort");
391            System.exit(1);
392        }
393
394        final String localAddress = args[0];
395        final int localPort = Integer.parseInt(args[1]);
396        final String proxyDN = args[2];
397        final String proxyPassword = args[3];
398        final String remoteAddress = args[4];
399        final int remotePort = Integer.parseInt(args[5]);
400
401        // Create connection factories.
402        final Options factoryOptions = Options.defaultOptions()
403                                   .set(LDAPConnectionFactory.AUTHN_BIND_REQUEST,
404                                        newSimpleBindRequest(proxyDN, proxyPassword.toCharArray()));
405        final ConnectionFactory factory = newCachedConnectionPool(new LDAPConnectionFactory(remoteAddress,
406                                                                                            remotePort,
407                                                                                            factoryOptions));
408        final ConnectionFactory bindFactory = newCachedConnectionPool(new LDAPConnectionFactory(remoteAddress,
409                                                                                                remotePort));
410
411        /*
412         * Create a server connection adapter which will create a new proxy
413         * backend for each inbound client connection. This is required because
414         * we need to maintain authorization state between client requests. The
415         * proxy bound will be wrapped in a rewriter in order to transform
416         * inbound requests and their responses.
417         */
418        final RequestHandlerFactory<LDAPClientContext, RequestContext> proxyFactory =
419                new RequestHandlerFactory<LDAPClientContext, RequestContext>() {
420                    @Override
421                    public Rewriter handleAccept(final LDAPClientContext clientContext) throws LdapException {
422                        return new Rewriter(new ProxyBackend(factory, bindFactory));
423                    }
424                };
425        final ServerConnectionFactory<LDAPClientContext, Integer> connectionHandler =
426                Connections.newServerConnectionFactory(proxyFactory);
427
428        // Create listener.
429        final Options listenerOptions = Options.defaultOptions().set(CONNECT_MAX_BACKLOG, 4096);
430        LDAPListener listener = null;
431        try {
432            listener = new LDAPListener(localAddress, localPort, connectionHandler, listenerOptions);
433            System.out.println("Press any key to stop the server...");
434            System.in.read();
435        } catch (final IOException e) {
436            System.out.println("Error listening on " + localAddress + ":" + localPort);
437            e.printStackTrace();
438        } finally {
439            if (listener != null) {
440                listener.close();
441            }
442        }
443    }
444
445    private RewriterProxy() {
446        // Not used.
447    }
448}