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