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  package org.forgerock.opendj.examples;
18  
19  import java.util.concurrent.atomic.AtomicReference;
20  
21  import org.forgerock.opendj.ldap.Connection;
22  import org.forgerock.opendj.ldap.ConnectionFactory;
23  import org.forgerock.opendj.ldap.LdapException;
24  import org.forgerock.opendj.ldap.IntermediateResponseHandler;
25  import org.forgerock.opendj.ldap.RequestContext;
26  import org.forgerock.opendj.ldap.RequestHandler;
27  import org.forgerock.opendj.ldap.ResultCode;
28  import org.forgerock.opendj.ldap.LdapResultHandler;
29  import org.forgerock.opendj.ldap.SearchResultHandler;
30  import org.forgerock.opendj.ldap.controls.ProxiedAuthV2RequestControl;
31  import org.forgerock.opendj.ldap.requests.AddRequest;
32  import org.forgerock.opendj.ldap.requests.BindRequest;
33  import org.forgerock.opendj.ldap.requests.CancelExtendedRequest;
34  import org.forgerock.opendj.ldap.requests.CompareRequest;
35  import org.forgerock.opendj.ldap.requests.DeleteRequest;
36  import org.forgerock.opendj.ldap.requests.ExtendedRequest;
37  import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
38  import org.forgerock.opendj.ldap.requests.ModifyRequest;
39  import org.forgerock.opendj.ldap.requests.Request;
40  import org.forgerock.opendj.ldap.requests.SearchRequest;
41  import org.forgerock.opendj.ldap.requests.StartTLSExtendedRequest;
42  import org.forgerock.opendj.ldap.responses.BindResult;
43  import org.forgerock.opendj.ldap.responses.CompareResult;
44  import org.forgerock.opendj.ldap.responses.ExtendedResult;
45  import org.forgerock.opendj.ldap.responses.Result;
46  import org.forgerock.util.AsyncFunction;
47  import org.forgerock.util.promise.Promise;
48  import org.forgerock.util.promise.ResultHandler;
49  
50  import static org.forgerock.opendj.ldap.LdapException.*;
51  import static org.forgerock.util.Utils.*;
52  
53  /**
54   * A simple proxy back-end which forwards requests to a connection factory using
55   * proxy authorization. Simple bind requests are performed on a separate
56   * connection factory dedicated for authentication.
57   * <p>
58   * This implementation is very simple and is only intended as an example:
59   * <ul>
60   * <li>It does not support SSL connections
61   * <li>It does not support StartTLS
62   * <li>It does not support Abandon or Cancel requests
63   * <li>Very basic authentication and authorization support.
64   * </ul>
65   * <b>NOTE:</b> a proxy back-end is stateful due to its use of proxy
66   * authorization. Therefore, a proxy backend must be created for each inbound
67   * client connection. The following code illustrates how this may be achieved:
68   *
69   * <pre>
70   * final RequestHandlerFactory<LDAPClientContext, RequestContext> proxyFactory =
71   *     new RequestHandlerFactory<LDAPClientContext, RequestContext>() {
72   *         @Override
73   *         public ProxyBackend handleAccept(LDAPClientContext clientContext) throws LdapException {
74   *             return new ProxyBackend(factory, bindFactory);
75   *         }
76   *     };
77   * final ServerConnectionFactory<LDAPClientContext, Integer> connectionHandler = Connections
78   *     .newServerConnectionFactory(proxyFactory);}
79   * </pre>
80   */
81  final class ProxyBackend implements RequestHandler<RequestContext> {
82  
83      private final ConnectionFactory bindFactory;
84      private final ConnectionFactory factory;
85      private volatile ProxiedAuthV2RequestControl proxiedAuthControl;
86  
87      ProxyBackend(final ConnectionFactory factory, final ConnectionFactory bindFactory) {
88          this.factory = factory;
89          this.bindFactory = bindFactory;
90      }
91  
92      @Override
93      public void handleAdd(final RequestContext requestContext, final AddRequest request,
94          final IntermediateResponseHandler intermediateResponseHandler, final LdapResultHandler<Result> resultHandler) {
95          final AtomicReference<Connection> connectionHolder = new AtomicReference<>();
96          addProxiedAuthControl(request);
97  
98          factory.getConnectionAsync().thenAsync(new AsyncFunction<Connection, Result, LdapException>() {
99              @Override
100             public Promise<Result, LdapException> apply(Connection connection) throws LdapException {
101                 connectionHolder.set(connection);
102                 return connection.addAsync(request, intermediateResponseHandler);
103             }
104         }).thenOnResult(resultHandler).thenOnException(resultHandler).thenAlways(close(connectionHolder));
105     }
106 
107     @Override
108     public void handleBind(final RequestContext requestContext, final int version, final BindRequest request,
109         final IntermediateResponseHandler intermediateResponseHandler,
110         final LdapResultHandler<BindResult> resultHandler) {
111 
112         if (request.getAuthenticationType() != BindRequest.AUTHENTICATION_TYPE_SIMPLE) {
113             // TODO: SASL authentication not implemented.
114             resultHandler.handleException(newLdapException(ResultCode.PROTOCOL_ERROR,
115                     "non-SIMPLE authentication not supported: " + request.getAuthenticationType()));
116         } else {
117             // Authenticate using a separate bind connection pool, because
118             // we don't want to change the state of the pooled connection.
119             final AtomicReference<Connection> connectionHolder = new AtomicReference<>();
120             proxiedAuthControl = null;
121             bindFactory.getConnectionAsync()
122                     .thenAsync(new AsyncFunction<Connection, BindResult, LdapException>() {
123                         @Override
124                         public Promise<BindResult, LdapException> apply(Connection connection) throws LdapException {
125                             connectionHolder.set(connection);
126                             return connection.bindAsync(request, intermediateResponseHandler);
127                         }
128                     }).thenOnResult(new ResultHandler<BindResult>() {
129                         @Override
130                         public final void handleResult(final BindResult result) {
131                             proxiedAuthControl = ProxiedAuthV2RequestControl.newControl("dn:" + request.getName());
132                             resultHandler.handleResult(result);
133                         }
134                     }).thenOnException(resultHandler).thenAlways(close(connectionHolder));
135         }
136     }
137 
138     @Override
139     public void handleCompare(final RequestContext requestContext, final CompareRequest request,
140             final IntermediateResponseHandler intermediateResponseHandler,
141             final LdapResultHandler<CompareResult> resultHandler) {
142         addProxiedAuthControl(request);
143 
144         final AtomicReference<Connection> connectionHolder = new AtomicReference<>();
145         factory.getConnectionAsync().thenAsync(new AsyncFunction<Connection, CompareResult, LdapException>() {
146             @Override
147             public Promise<CompareResult, LdapException> apply(Connection connection) throws LdapException {
148                 connectionHolder.set(connection);
149                 return connection.compareAsync(request, intermediateResponseHandler);
150             }
151         }).thenOnResult(resultHandler).thenOnException(resultHandler).thenAlways(close(connectionHolder));
152     }
153 
154     @Override
155     public void handleDelete(final RequestContext requestContext, final DeleteRequest request,
156             final IntermediateResponseHandler intermediateResponseHandler,
157             final LdapResultHandler<Result> resultHandler) {
158         addProxiedAuthControl(request);
159 
160         final AtomicReference<Connection> connectionHolder = new AtomicReference<>();
161         factory.getConnectionAsync().thenAsync(new AsyncFunction<Connection, Result, LdapException>() {
162             @Override
163             public Promise<Result, LdapException> apply(Connection connection) throws LdapException {
164                 connectionHolder.set(connection);
165                 return connection.deleteAsync(request, intermediateResponseHandler);
166             }
167         }).thenOnResult(resultHandler).thenOnException(resultHandler).thenAlways(close(connectionHolder));
168     }
169 
170     @Override
171     public <R extends ExtendedResult> void handleExtendedRequest(final RequestContext requestContext,
172         final ExtendedRequest<R> request, final IntermediateResponseHandler intermediateResponseHandler,
173         final LdapResultHandler<R> resultHandler) {
174         if (CancelExtendedRequest.OID.equals(request.getOID())) {
175             // TODO: not implemented.
176             resultHandler.handleException(newLdapException(ResultCode.PROTOCOL_ERROR,
177                 "Cancel extended request operation not supported"));
178         } else if (StartTLSExtendedRequest.OID.equals(request.getOID())) {
179             // TODO: not implemented.
180             resultHandler.handleException(newLdapException(ResultCode.PROTOCOL_ERROR,
181                 "StartTLS extended request operation not supported"));
182         } else {
183             // Forward all other extended operations.
184             addProxiedAuthControl(request);
185 
186             final AtomicReference<Connection> connectionHolder = new AtomicReference<>();
187             factory.getConnectionAsync().thenAsync(new AsyncFunction<Connection, R, LdapException>() {
188                 @Override
189                 public Promise<R, LdapException> apply(Connection connection) throws LdapException {
190                     connectionHolder.set(connection);
191                     return connection.extendedRequestAsync(request, intermediateResponseHandler);
192                 }
193             }).thenOnResult(resultHandler).thenOnException(resultHandler)
194                 .thenAlways(close(connectionHolder));
195         }
196     }
197 
198     @Override
199     public void handleModify(final RequestContext requestContext, final ModifyRequest request,
200             final IntermediateResponseHandler intermediateResponseHandler,
201             final LdapResultHandler<Result> resultHandler) {
202         addProxiedAuthControl(request);
203 
204         final AtomicReference<Connection> connectionHolder = new AtomicReference<>();
205         factory.getConnectionAsync().thenAsync(new AsyncFunction<Connection, Result, LdapException>() {
206             @Override
207             public Promise<Result, LdapException> apply(Connection connection) throws LdapException {
208                 connectionHolder.set(connection);
209                 return connection.modifyAsync(request, intermediateResponseHandler);
210             }
211         }).thenOnResult(resultHandler).thenOnException(resultHandler).thenAlways(close(connectionHolder));
212     }
213 
214     @Override
215     public void handleModifyDN(final RequestContext requestContext, final ModifyDNRequest request,
216         final IntermediateResponseHandler intermediateResponseHandler, final LdapResultHandler<Result> resultHandler) {
217         addProxiedAuthControl(request);
218 
219         final AtomicReference<Connection> connectionHolder = new AtomicReference<>();
220         factory.getConnectionAsync().thenAsync(new AsyncFunction<Connection, Result, LdapException>() {
221             @Override
222             public Promise<Result, LdapException> apply(Connection connection) throws LdapException {
223                 connectionHolder.set(connection);
224                 return connection.modifyDNAsync(request, intermediateResponseHandler);
225             }
226         }).thenOnResult(resultHandler).thenOnException(resultHandler).thenAlways(close(connectionHolder));
227     }
228 
229     @Override
230     public void handleSearch(final RequestContext requestContext, final SearchRequest request,
231             final IntermediateResponseHandler intermediateResponseHandler, final SearchResultHandler entryHandler,
232             final LdapResultHandler<Result> resultHandler) {
233         addProxiedAuthControl(request);
234 
235         final AtomicReference<Connection> connectionHolder = new AtomicReference<>();
236         factory.getConnectionAsync().thenAsync(new AsyncFunction<Connection, Result, LdapException>() {
237             @Override
238             public Promise<Result, LdapException> apply(Connection connection) throws LdapException {
239                 connectionHolder.set(connection);
240                 return connection.searchAsync(request, intermediateResponseHandler, entryHandler);
241             }
242         }).thenOnResult(resultHandler).thenOnException(resultHandler).thenAlways(close(connectionHolder));
243     }
244 
245     private void addProxiedAuthControl(final Request request) {
246         final ProxiedAuthV2RequestControl control = proxiedAuthControl;
247         if (control != null) {
248             request.addControl(control);
249         }
250     }
251 
252     private Runnable close(final AtomicReference<Connection> c) {
253         return new Runnable() {
254             @Override
255             public void run() {
256                 closeSilently(c.get());
257             }
258         };
259     }
260 }