001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2009-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2011-2016 ForgeRock AS. 016 */ 017 018package org.forgerock.opendj.examples; 019 020import static org.forgerock.opendj.ldap.Connections.newCachedConnectionPool; 021import static org.forgerock.opendj.ldap.LDAPListener.*; 022import static org.forgerock.opendj.ldap.requests.Requests.newSimpleBindRequest; 023 024import java.io.IOException; 025import java.util.HashSet; 026import java.util.List; 027import java.util.Set; 028 029import org.forgerock.opendj.ldap.Attribute; 030import org.forgerock.opendj.ldap.AttributeDescription; 031import org.forgerock.opendj.ldap.Attributes; 032import org.forgerock.opendj.ldap.ConnectionFactory; 033import org.forgerock.opendj.ldap.Connections; 034import org.forgerock.opendj.ldap.DN; 035import org.forgerock.opendj.ldap.Filter; 036import org.forgerock.opendj.ldap.IntermediateResponseHandler; 037import org.forgerock.opendj.ldap.LDAPClientContext; 038import org.forgerock.opendj.ldap.LDAPConnectionFactory; 039import org.forgerock.opendj.ldap.LDAPListener; 040import org.forgerock.opendj.ldap.LdapException; 041import org.forgerock.opendj.ldap.LdapResultHandler; 042import org.forgerock.opendj.ldap.Modification; 043import org.forgerock.opendj.ldap.RequestContext; 044import org.forgerock.opendj.ldap.RequestHandler; 045import org.forgerock.opendj.ldap.RequestHandlerFactory; 046import org.forgerock.opendj.ldap.SearchResultHandler; 047import org.forgerock.opendj.ldap.ServerConnectionFactory; 048import org.forgerock.opendj.ldap.controls.Control; 049import org.forgerock.opendj.ldap.requests.AddRequest; 050import org.forgerock.opendj.ldap.requests.BindRequest; 051import org.forgerock.opendj.ldap.requests.CompareRequest; 052import org.forgerock.opendj.ldap.requests.DeleteRequest; 053import org.forgerock.opendj.ldap.requests.ExtendedRequest; 054import org.forgerock.opendj.ldap.requests.ModifyDNRequest; 055import org.forgerock.opendj.ldap.requests.ModifyRequest; 056import org.forgerock.opendj.ldap.requests.Requests; 057import org.forgerock.opendj.ldap.requests.SearchRequest; 058import org.forgerock.opendj.ldap.responses.BindResult; 059import org.forgerock.opendj.ldap.responses.CompareResult; 060import org.forgerock.opendj.ldap.responses.ExtendedResult; 061import org.forgerock.opendj.ldap.responses.Result; 062import org.forgerock.opendj.ldap.responses.SearchResultEntry; 063import org.forgerock.opendj.ldap.responses.SearchResultReference; 064import org.forgerock.opendj.ldap.schema.AttributeType; 065import org.forgerock.util.Options; 066 067import com.forgerock.reactive.ServerConnectionFactoryAdapter; 068 069/** 070 * This example is based on the {@link Proxy}. This example does no load 071 * balancing, but instead rewrites attribute descriptions and DN suffixes in 072 * requests to and responses from a directory server using hard coded 073 * configuration. 074 * <ul> 075 * <li>It transforms DNs ending in {@code o=example} on the client side to end 076 * in {@code dc=example,dc=com} on the server side and vice versa. 077 * <li>It transforms the attribute description {@code fullname} on the client 078 * side to {@code cn} on the server side and vice versa. 079 * </ul> 080 * 081 * This example has a number of restrictions. 082 * <ul> 083 * <li>It does not support SSL connections. 084 * <li>It does not support StartTLS. 085 * <li>It does not support Abandon or Cancel requests. 086 * <li>It has very basic authentication and authorization support. 087 * <li>It does not rewrite bind DNs. 088 * <li>It uses proxied authorization, so if you use OpenDJ directory server, you 089 * must set the {@code proxied-auth} privilege for the proxy user. 090 * <li>It does not touch matched DNs in results. 091 * <li>It does not rewrite attributes with options in search result entries. 092 * <li>It does not touch search result references. 093 * </ul> 094 * This example takes the following command line parameters: 095 * 096 * <pre> 097 * {@code <localAddress> <localPort> <proxyDN> <proxyPassword> <serverAddress> <serverPort>} 098 * </pre> 099 * 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 */ 105public 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}