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}