/** * Copyright (c) 2009-2011 VMware, Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.springsource.insight.plugin.ldap; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URI; import java.net.URISyntaxException; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Hashtable; import java.util.LinkedList; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import javax.naming.Context; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.directory.server.core.DefaultDirectoryService; import org.junit.AfterClass; import org.junit.BeforeClass; import org.springframework.security.ldap.server.ApacheDSContainer; import org.springframework.util.FileSystemUtils; import com.springsource.insight.collection.test.OperationCollectionAspectTestSupport; import com.springsource.insight.intercept.application.ApplicationName; import com.springsource.insight.intercept.operation.Operation; import com.springsource.insight.intercept.operation.OperationFields; import com.springsource.insight.intercept.server.ServerName; import com.springsource.insight.intercept.topology.ExternalResourceDescriptor; import com.springsource.insight.intercept.topology.ExternalResourceType; import com.springsource.insight.intercept.topology.MD5NameGenerator; import com.springsource.insight.intercept.trace.Frame; import com.springsource.insight.intercept.trace.FrameId; import com.springsource.insight.intercept.trace.SimpleFrame; import com.springsource.insight.intercept.trace.Trace; import com.springsource.insight.intercept.trace.TraceId; import com.springsource.insight.util.ClassUtil; import com.springsource.insight.util.FileUtil; import com.springsource.insight.util.StringUtil; import com.springsource.insight.util.time.TimeRange; /** * A spring equivalent to <code>OperationCollectionAspectTestSupport</code> */ public abstract class LdapOperationCollectionAspectTestSupport extends OperationCollectionAspectTestSupport { private static final Log LOG = LogFactory.getLog(LdapOperationCollectionAspectTestSupport.class); // Use non-default port to avoid conflicts if built on servers that have a running sever protected static final int TEST_PORT = 53899; // NOTE: the DN(s) should match the one in the LDIF file protected static final String ROOT_DN = "dc=springframework,dc=org", LDAP_URL = "ldap://localhost:" + TEST_PORT + "/" + ROOT_DN; // NOTE: these are the defaults - haven't figured out how to override them... protected static final String LDAP_USERNAME = "uid=admin,ou=system", LDAP_PASSWORD = "secret"; // NOTE: taken from a Spring security sample code protected static final String LDIF_LOCATION = "META-INF/testUsers.ldif", LDIF_RESUOURCE = "classpath:" + LDIF_LOCATION; protected static Collection<Map<String, Set<String>>> LDIF_ENTRIES; private static ApacheDSContainer dsContainer; protected final Log logger = LogFactory.getLog(getClass()); protected static final LdapExternalResourceAnalyzer analyzer = LdapExternalResourceAnalyzer.getInstance(); protected LdapOperationCollectionAspectTestSupport() { super(); } @BeforeClass public static final void startLdapServer() throws Exception { File apacheWorkDir = resolveApacheWorkDir(LdapOperationCollectionAspectTestSupport.class); // see ApacheDSContainer#afterPropertiesSet() as to why this is necessary if (FileSystemUtils.deleteRecursively(apacheWorkDir)) { System.out.println("Deleted " + apacheWorkDir.getAbsolutePath()); } dsContainer = new ApacheDSContainer(ROOT_DN, LDIF_RESUOURCE); dsContainer.setPort(TEST_PORT); dsContainer.setWorkingDirectory(apacheWorkDir); DefaultDirectoryService dsService = dsContainer.getService(); dsService.setShutdownHookEnabled(true); LOG.info("Starting LDAP server on port " + TEST_PORT); dsContainer.afterPropertiesSet(); LOG.info("LDAP server started"); LDIF_ENTRIES = readLdifEntries(LDIF_LOCATION); LOG.info("Read " + LDIF_ENTRIES.size() + " LDIF entries from " + LDIF_LOCATION); } @AfterClass public static final void stopLdapServer() throws Exception { if (dsContainer == null) { LOG.warn("No current LDAP server instance running..."); return; } LOG.info("Stopping LDAP server ..."); dsContainer.destroy(); LOG.info("LDAP server stopped..."); } protected Operation assertContextOperation(String testName, String lookupName, Map<?, ?> environment) { Operation op = getLastEntered(); assertNotNull(testName + ": No operation generated", op); assertEquals(testName + ": Mismatched operation type", LdapDefinitions.LDAP_OP, op.getType()); assertEquals(testName + ": Mismatched lookup name", lookupName, op.get(LdapDefinitions.LOOKUP_NAME_ATTR, String.class)); LdapOperationCollectionAspectSupport aspectInstance = (LdapOperationCollectionAspectSupport) getAspect(); assertEquals(testName + ": Mismatched action", aspectInstance.action, op.get(OperationFields.METHOD_NAME, String.class)); Class<?> contextClass = aspectInstance.contextClass; assertEquals(testName + ": Mismatched short class name", contextClass.getSimpleName(), op.get(OperationFields.SHORT_CLASS_NAME, String.class)); assertEquals(testName + ": Mismatched full class name", contextClass.getName(), op.get(OperationFields.CLASS_NAME, String.class)); return op; } protected static final Collection<ExternalResourceDescriptor> assertExternalResourceAnalysis( String testName, Operation op, String ldapUrl) throws URISyntaxException { Frame frame = new SimpleFrame(FrameId.valueOf("0"), null, op, TimeRange.FULL_RANGE, Collections.<Frame>emptyList()); Trace trace = new Trace(ServerName.valueOf("fake-server"), ApplicationName.valueOf("fake-app"), new Date(System.currentTimeMillis()), TraceId.valueOf("0"), frame); Collection<ExternalResourceDescriptor> result = analyzer.locateExternalResourceName(trace); assertNotNull(testName + ": No external resources recovered", result); assertEquals(testName + ": Mismatched number of results", 1, result.size()); ExternalResourceDescriptor desc = result.iterator().next(); assertSame(testName + ": Mismatched result frame", frame, desc.getFrame()); assertEquals(testName + ": Mismathed name", MD5NameGenerator.getName(ldapUrl), desc.getName()); assertEquals(testName + ": Mismatched vendor", ldapUrl, desc.getVendor()); assertEquals(testName + ": Mismatched label", ldapUrl, desc.getLabel()); assertEquals(testName + ": Mismatched type", ExternalResourceType.LDAP.name(), desc.getType()); URI uri = new URI(ldapUrl); assertEquals(testName + ": Mismatched host", uri.getHost(), desc.getHost()); assertEquals(testName + ": Mismatched port", LdapExternalResourceAnalyzer.resolvePort(uri), desc.getPort()); return result; } private static File resolveApacheWorkDir(Class<?> anchorClass) { // see ApacheDSContainer#afterPropertiesSet String apacheWorkDir = System.getProperty("apacheDSWorkDir"); if (apacheWorkDir != null) { LOG.info("resolveApacheWorkDir(" + anchorClass.getSimpleName() + ") using pre-defined " + apacheWorkDir); return new File(apacheWorkDir); } File targetDir = FileUtil.detectTargetFolder(anchorClass); if (targetDir == null) { throw new IllegalStateException("No target folder for " + anchorClass.getSimpleName()); } targetDir = new File(targetDir, "apacheDSWorkDir"); LOG.info("resolveApacheWorkDir(" + anchorClass.getSimpleName() + ") location: " + targetDir); return targetDir; } private static Collection<Map<String, Set<String>>> readLdifEntries(String location) throws IOException { ClassLoader cl = ClassUtil.getDefaultClassLoader(); InputStream in = cl.getResourceAsStream(location); assertNotNull("No LDIF input at " + location, in); BufferedReader rdr = new BufferedReader(new InputStreamReader(in)); try { return readLdifEntries(rdr); } finally { rdr.close(); } } private static Collection<Map<String, Set<String>>> readLdifEntries(BufferedReader rdr) throws IOException { Collection<Map<String, Set<String>>> result = new LinkedList<Map<String, Set<String>>>(); Map<String, Set<String>> curEntry = null; for (String line = rdr.readLine(); line != null; line = rdr.readLine()) { line = line.trim(); if (LOG.isTraceEnabled()) { LOG.trace("readLdifEntries - " + line); } // empty line signals end of entry if (StringUtil.isEmpty(line)) { if (curEntry != null) { result.add(curEntry); } curEntry = null; continue; } if (curEntry == null) { curEntry = new TreeMap<String, Set<String>>(String.CASE_INSENSITIVE_ORDER); } int namePos = line.indexOf(':'); String name = line.substring(0, namePos), value = line.substring(namePos + 2).trim(); if (StringUtil.isEmpty(name) || StringUtil.isEmpty(value)) { throw new IllegalArgumentException("Bad line: " + line); } if ("dn".equalsIgnoreCase(name)) { assertTrue("DN(" + value + ") not subset of root (" + ROOT_DN + ")", value.toLowerCase().endsWith(ROOT_DN.toLowerCase())); } Set<String> values = curEntry.get(name); if (values == null) { values = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER); curEntry.put(name, values); } if (!values.add(value)) { throw new IllegalStateException("Duplicate values for name=" + name + " at line " + line); } } return result; } protected static final Hashtable<String, Object> createEnvironment() { Hashtable<String, Object> env = new Hashtable<String, Object>(); env.put(Context.PROVIDER_URL, LDAP_URL); env.put(Context.SECURITY_AUTHENTICATION, "simple"); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); env.put(Context.SECURITY_PRINCIPAL, LDAP_USERNAME); env.put(Context.SECURITY_CREDENTIALS, LDAP_PASSWORD); return env; } }