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}