001package com.nimbusds.common.ldap; 002 003 004import java.io.File; 005import java.io.IOException; 006import java.util.Properties; 007 008import javax.servlet.ServletContext; 009import javax.servlet.ServletContextEvent; 010import javax.servlet.ServletContextListener; 011 012import com.unboundid.ldap.listener.InMemoryDirectoryServer; 013import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig; 014import com.unboundid.ldap.listener.InMemoryListenerConfig; 015import com.unboundid.ldap.sdk.LDAPException; 016import com.unboundid.ldap.sdk.OperationType; 017import com.unboundid.ldap.sdk.schema.Schema; 018import com.unboundid.ldif.LDIFException; 019 020import org.apache.log4j.Logger; 021 022import com.thetransactioncompany.util.PropertyParseException; 023import com.thetransactioncompany.util.PropertyRetriever; 024 025import com.nimbusds.common.servlet.ResourceRetriever; 026 027 028/** 029 * Example in-memory LDAP directory server for demonstration and testing 030 * purposes. Access is limited to read and bind (authenticate) only. 031 * 032 * <p>The directory server is configured by a set of "exampleDirectoryServer.*" 033 * properties, see {@link Configuration}. 034 * 035 * <p>The example directory implements {@code ServletContextListener}. This 036 * enables its automatic startup and shutdown in a servlet container (Java web 037 * server), such as Apache Tomcat. When started from a servlet container the 038 * directory configuration is obtained from a properties file specified by a 039 * context parameter named {@code exampleDirectoryServer.configurationFile}. 040 */ 041public class ExampleDirectory implements ServletContextListener { 042 043 044 /** 045 * The example directory server configuration. 046 */ 047 public static class Configuration { 048 049 050 /** 051 * If {@code true} the example directory server must be 052 * enabled. 053 * 054 * <p>Property key: exampleDirectoryServer.enable 055 */ 056 public final boolean enable; 057 058 059 /** 060 * The default enable policy. 061 */ 062 public static final boolean DEFAULT_ENABLE = false; 063 064 065 /** 066 * The port number on which the example directory server 067 * must accept LDAP client connections. 068 * 069 * <p>Property key: exampleDirectoryServer.port 070 */ 071 public final int port; 072 073 074 /** 075 * The default port number. 076 */ 077 public static final int DEFAULT_PORT = 10389; 078 079 080 /** 081 * Specifies an alternative schema for the example directory + 082 * server, supplied in a single LDIF file. If {@code null} the 083 * default built-in server schema must be used. 084 * 085 * <p>Property key: exampleDirectoryServer.schema 086 */ 087 public final String schema; 088 089 090 /** 091 * The base distinguished name (DN) of the directory information 092 * tree. 093 * 094 * <p>Property key: exampleDirectoryServer.baseDN 095 */ 096 public final String baseDN; 097 098 099 /** 100 * The initial directory information tree, supplied in a single 101 * LDIF file. If {@code null} the directory will be left 102 * empty. 103 * 104 * <p>Property key: exampleDirectoryServer.content 105 */ 106 public final String content; 107 108 109 /** 110 * Creates a new example directory server configuration from the 111 * specified properties. 112 * 113 * @param props The configuration properties. Must not be 114 * {@code null}. 115 * 116 * @throws PropertyParseException On a missing or invalid 117 * property. 118 */ 119 public Configuration(final Properties props) 120 throws PropertyParseException { 121 122 PropertyRetriever pr = new PropertyRetriever(props); 123 124 enable = pr.getOptBoolean("exampleDirectoryServer.enable", DEFAULT_ENABLE); 125 126 if (! enable) { 127 128 port = DEFAULT_PORT; 129 schema = null; 130 baseDN = null; 131 content = null; 132 return; 133 } 134 135 // We're okay to read rest of config 136 137 port = pr.getOptInt("exampleDirectoryServer.port", DEFAULT_PORT); 138 139 String s = pr.getOptString("exampleDirectoryServer.schema", null); 140 141 if (s == null || s.isEmpty()) 142 schema = null; 143 else 144 schema = s; 145 146 baseDN = pr.getString("exampleDirectoryServer.baseDN"); 147 148 s = pr.getOptString("exampleDirectoryServer.content", null); 149 150 if (s == null || s.isEmpty()) 151 content = null; 152 else 153 content = s; 154 } 155 } 156 157 158 /** 159 * The example in-memory directory server. 160 */ 161 private InMemoryDirectoryServer ds = null; 162 163 164 /** 165 * The servlet context. 166 */ 167 private ServletContext servletContext; 168 169 170 /** 171 * The logger. 172 */ 173 private final Logger log = Logger.getLogger("MAIN"); 174 175 176 /** 177 * If the specified filename is relative returns its full path, else the 178 * filename is left unchanged. 179 * 180 * <p>Uses the servlet context {@code getRealPath} method. 181 * 182 * @param filename The filename. Must not be {@code null}. 183 * 184 * @return The full path filename. 185 */ 186 private String getFullPath(final String filename) { 187 188 String fullPath = null; 189 190 File file = new File(filename); 191 192 if (file.isAbsolute()) 193 return filename; 194 else 195 return servletContext.getRealPath(File.separator) + filename; 196 } 197 198 199 /** 200 * Starts the example in-memory directory server. 201 * 202 * @param config The example directory server configuration. Must not 203 * be {@code null}. 204 * 205 * @throws LDAPException If the in-memory directory server couldn't be 206 * started or its initialisation failed. 207 * @throws IOException If a schema file was specified and it couldn't 208 * be read. 209 * @throws LDIFException If a schema file was specified that is not 210 * valid LDIF. 211 */ 212 public void start(final Configuration config) 213 throws LDAPException, 214 IOException, 215 LDIFException { 216 217 if (! config.enable) { 218 219 log.info("Example directory server disabled"); 220 return; 221 } 222 223 InMemoryListenerConfig listenerConfig = 224 InMemoryListenerConfig.createLDAPConfig("example-ds", config.port); 225 226 // Get alternative schema, if any 227 228 Schema schema = null; 229 230 if (config.schema != null) { 231 232 String schemaFullPath = getFullPath(config.schema); 233 schema = Schema.getSchema(schemaFullPath); 234 235 log.info("Set example directory server schema to LDIF file " + schemaFullPath); 236 } 237 238 239 InMemoryDirectoryServerConfig dsConfig = new InMemoryDirectoryServerConfig(config.baseDN); 240 241 log.info("Set example directory server base DN to " + config.baseDN); 242 243 dsConfig.setSchema(schema); 244 dsConfig.setListenerConfigs(listenerConfig); 245 246 // Limit access to read and bind only 247 dsConfig.setAllowedOperationTypes(OperationType.BIND, 248 OperationType.COMPARE, 249 OperationType.SEARCH, 250 OperationType.EXTENDED); 251 252 // Start server 253 ds = new InMemoryDirectoryServer(dsConfig); 254 255 256 // Populate directory with LDIF, if any 257 258 if (config.content != null) { 259 260 String contentFullPath = getFullPath(config.content); 261 ds.importFromLDIF(true, contentFullPath); 262 263 log.info("Populated example directory server from LDIF file " + contentFullPath); 264 } 265 266 // Start listening on selected port 267 ds.startListening(); 268 269 log.info("Started example directory server on port " + ds.getListenPort()); 270 } 271 272 273 /** 274 * Stops the example in-memory directory server (if previously started). 275 * Information and status messages are logged at INFO level. 276 */ 277 public void stop() { 278 279 if (ds == null) 280 return; 281 282 // Clean all connections and stop server 283 ds.shutDown(true); 284 285 log.info("Shut down example directory server"); 286 } 287 288 289 /** 290 * Handler for servlet context startup events. Launches the example 291 * in-memory directory server (if enabled per configuration). Exceptions 292 * are logged at ERROR level, information and status messages at INFO 293 * level. 294 * 295 * <p>The example directory server configuration is retrieved from a 296 * properties file which location is specified by a servlet context 297 * parameter named {@code exampleDirectory.configurationFile}. 298 * 299 * @param sce A servlet context event. 300 */ 301 public void contextInitialized(ServletContextEvent sce) { 302 303 servletContext = sce.getServletContext(); 304 305 // Read configuration 306 Configuration config = null; 307 308 try { 309 Properties props = ResourceRetriever.getProperties(servletContext, 310 "exampleDirectory.configurationFile", 311 log); 312 313 config = new Configuration(props); 314 315 } catch (Exception e) { 316 317 log.error("Couldn't configure example directory server: " + e.getMessage()); 318 return; 319 } 320 321 322 // Start server 323 try { 324 start(config); 325 326 } catch (LDAPException e) { 327 328 log.error("Couldn't start example directory server: " + e.getMessage()); 329 330 } catch (IOException e) { 331 332 log.error("Couldn't read schema file: " + e.getMessage()); 333 334 } catch (LDIFException e) { 335 336 log.error("Couldn't read schema file: " + e.getMessage()); 337 } 338 } 339 340 341 /** 342 * Handler for servlet context shutdown events. Stops the example 343 * in-memory directory server (if previously started). 344 * 345 * @param sce A servlet context event. 346 */ 347 public void contextDestroyed(ServletContextEvent sce) { 348 349 stop(); 350 } 351} 352