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