Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.jackrabbit.oak.security.authentication.ldap; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Hashtable; import java.util.List; import java.util.Map; import javax.naming.Context; import javax.naming.NamingException; import javax.naming.ldap.InitialLdapContext; import javax.naming.ldap.LdapContext; import org.apache.commons.io.FileUtils; import org.apache.directory.api.ldap.model.constants.SupportedSaslMechanisms; import org.apache.directory.api.ldap.model.entry.DefaultEntry; import org.apache.directory.api.ldap.model.entry.Entry; import org.apache.directory.api.ldap.model.exception.LdapException; import org.apache.directory.api.ldap.model.ldif.LdifEntry; import org.apache.directory.api.ldap.model.ldif.LdifReader; import org.apache.directory.api.ldap.model.name.Dn; import org.apache.directory.api.ldap.model.schema.SchemaManager; import org.apache.directory.api.ldap.schema.manager.impl.DefaultSchemaManager; import org.apache.directory.server.constants.ServerDNConstants; import org.apache.directory.server.constants.SystemSchemaConstants; import org.apache.directory.server.core.DefaultDirectoryService; import org.apache.directory.server.core.api.CacheService; import org.apache.directory.server.core.api.CoreSession; import org.apache.directory.server.core.api.DirectoryService; import org.apache.directory.server.core.api.InstanceLayout; import org.apache.directory.server.core.api.schema.SchemaPartition; import org.apache.directory.server.core.jndi.CoreContextFactory; import org.apache.directory.server.core.partition.impl.avl.AvlPartition; import org.apache.directory.server.core.shared.DefaultDnFactory; import org.apache.directory.server.ldap.LdapServer; import org.apache.directory.server.ldap.handlers.extended.StartTlsHandler; import org.apache.directory.server.ldap.handlers.extended.StoredProcedureExtendedOperationHandler; import org.apache.directory.server.ldap.handlers.sasl.MechanismHandler; import org.apache.directory.server.ldap.handlers.sasl.cramMD5.CramMd5MechanismHandler; import org.apache.directory.server.ldap.handlers.sasl.digestMD5.DigestMd5MechanismHandler; import org.apache.directory.server.ldap.handlers.sasl.gssapi.GssapiMechanismHandler; import org.apache.directory.server.ldap.handlers.sasl.ntlm.NtlmMechanismHandler; import org.apache.directory.server.ldap.handlers.sasl.plain.PlainMechanismHandler; import org.apache.directory.server.protocol.shared.transport.TcpTransport; import org.apache.mina.util.AvailablePortFinder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A simple ldap test server */ public abstract class AbstractServer { public static final String EXAMPLE_DN = "dc=example,dc=com"; private static final Logger LOG = LoggerFactory.getLogger(AbstractServer.class); private static final List<LdifEntry> EMPTY_LIST = Collections.unmodifiableList(new ArrayList<LdifEntry>(0)); private static final String CTX_FACTORY = "com.sun.jndi.ldap.LdapCtxFactory"; /** * the context root for the rootDSE */ protected CoreSession rootDSE; /** * flag whether to delete database files for each test or not */ protected boolean doDelete = true; protected int port = -1; protected DirectoryService directoryService; protected LdapServer ldapServer; /** * Loads an LDIF from an input stream and adds the entries it contains to * the server. It appears as though the administrator added these entries * to the server. * * @param in the input stream containing the LDIF entries to load * @return a list of entries added to the server in the order they were added * @throws NamingException of the load fails */ protected List<LdifEntry> loadLdif(InputStream in) throws Exception { if (in == null) { return EMPTY_LIST; } LdifReader ldifReader = new LdifReader(in); return loadLdif(ldifReader); } protected List<LdifEntry> loadLdif(LdifReader ldifReader) throws Exception { List<LdifEntry> entries = new ArrayList<LdifEntry>(); for (LdifEntry ldifEntry : ldifReader) { Dn dn = ldifEntry.getDn(); if (ldifEntry.isEntry()) { org.apache.directory.api.ldap.model.entry.Entry items = ldifEntry.getEntry(); rootDSE.add(new DefaultEntry(directoryService.getSchemaManager(), items)); LOG.info("Added entry {}", dn); entries.add(ldifEntry); } } return entries; } /** * Inject an ldif String into the server. DN must be relative to the * root. * * @param ldif the entries to inject * @throws NamingException if the entries cannot be added */ protected void addEntry(String ldif) throws Exception { ByteArrayInputStream in = new ByteArrayInputStream(ldif.getBytes("utf-8")); LdifReader reader = new LdifReader(in); loadLdif(reader); } /** * Common code to get an initial context via a simple bind to the * server over the wire using the SUN JNDI LDAP provider. Do not use * this method until after the setUp() method is called to start the * server otherwise it will fail. * * @return an LDAP context as the the administrator to the rootDSE * @throws NamingException if the server cannot be contacted */ protected LdapContext getWiredContext() throws Exception { return getWiredContext(ServerDNConstants.ADMIN_SYSTEM_DN, "secret"); } /** * Common code to get an initial context via a simple bind to the * server over the wire using the SUN JNDI LDAP provider. Do not use * this method until after the setUp() method is called to start the * server otherwise it will fail. * * @param bindPrincipalDn the DN of the principal to bind as * @param password the password of the bind principal * @return an LDAP context as the the administrator to the rootDSE * @throws NamingException if the server cannot be contacted */ protected LdapContext getWiredContext(String bindPrincipalDn, String password) throws Exception { Hashtable<String, String> env = new Hashtable<String, String>(); env.put(Context.INITIAL_CONTEXT_FACTORY, CTX_FACTORY); env.put(Context.PROVIDER_URL, "ldap://localhost:" + port); env.put(Context.SECURITY_PRINCIPAL, bindPrincipalDn); env.put(Context.SECURITY_CREDENTIALS, password); env.put(Context.SECURITY_AUTHENTICATION, "simple"); return new InitialLdapContext(env, null); } /** * Get's the initial context factory for the provider's ou=system context * root. */ protected void setUp() throws Exception { File cwd = new File("target", "apacheds"); doDelete(cwd); // setup directory service directoryService = new DefaultDirectoryService(); directoryService.setShutdownHookEnabled(false); directoryService.setInstanceLayout(new InstanceLayout(cwd)); CacheService cache = new CacheService(); cache.initialize(directoryService.getInstanceLayout()); SchemaManager schemaManager = new DefaultSchemaManager(); directoryService.setSchemaManager(schemaManager); directoryService .setDnFactory(new DefaultDnFactory(directoryService.getSchemaManager(), cache.getCache("dnCache"))); AvlPartition schLdifPart = new AvlPartition(directoryService.getSchemaManager(), directoryService.getDnFactory()); schLdifPart.setId("schema"); schLdifPart.setSuffixDn(directoryService.getDnFactory().create(ServerDNConstants.CN_SCHEMA_DN)); SchemaPartition schPart = new SchemaPartition(directoryService.getSchemaManager()); schPart.setWrappedPartition(schLdifPart); directoryService.setSchemaPartition(schPart); AvlPartition sysPart = new AvlPartition(directoryService.getSchemaManager(), directoryService.getDnFactory()); sysPart.setId(SystemSchemaConstants.SCHEMA_NAME); sysPart.setSuffixDn(directoryService.getDnFactory().create(ServerDNConstants.SYSTEM_DN)); directoryService.setSystemPartition(sysPart); AvlPartition examplePart = new AvlPartition(directoryService.getSchemaManager(), directoryService.getDnFactory()); examplePart.setId("example"); examplePart.setSuffixDn(directoryService.getDnFactory().create(EXAMPLE_DN)); examplePart.setCacheService(cache); directoryService.addPartition(examplePart); // setup ldap server port = AvailablePortFinder.getNextAvailable(1024); ldapServer = new LdapServer(); setupLdapServer(); setupSaslMechanisms(); directoryService.startup(); setupExamplePartition(); ldapServer.start(); setContexts(ServerDNConstants.ADMIN_SYSTEM_DN, "secret"); } protected void setupLdapServer() throws Exception { ldapServer.setTransports(new TcpTransport(port)); ldapServer.setDirectoryService(directoryService); ldapServer.addExtendedOperationHandler(new StartTlsHandler()); ldapServer.addExtendedOperationHandler(new StoredProcedureExtendedOperationHandler()); } protected void setupExamplePartition() throws Exception { // setup acl to allow read all users // Inject the context entry for dc=example,dc=com partition if it does not already exist try { directoryService.getAdminSession().lookup(new Dn(EXAMPLE_DN)); } catch (LdapException lnnfe) { Entry entry = directoryService.newEntry(new Dn(EXAMPLE_DN)); entry.add("objectClass", "top", "domain", "extensibleObject"); entry.add("dc", "example"); entry.add("administrativeRole", "accessControlSpecificArea"); directoryService.getAdminSession().add(entry); entry = directoryService.newEntry(new Dn("cn=enableSearchForAllUsers," + EXAMPLE_DN)); entry.add("objectClass", "top", "subentry", "accessControlSubentry"); entry.add("cn", "enableSearchForAllUsers"); entry.add("subtreeSpecification", "{}"); entry.add("prescriptiveACI", "{ \n" + " identificationTag \"enableSearchForAllUsers\",\n" + " precedence 14,\n" + " authenticationLevel simple,\n" + " itemOrUserFirst userFirst: \n" + " { \n" + " userClasses { allUsers }, \n" + " userPermissions \n" + " { \n" + " {\n" + " protectedItems {entry, allUserAttributeTypesAndValues}, \n" + " grantsAndDenials { grantRead, grantReturnDN, grantBrowse } \n" + " }\n" + " } \n" + " } \n" + "}"); directoryService.getAdminSession().add(entry); directoryService.sync(); } } public void setMaxSizeLimit(long maxSizeLimit) { ldapServer.setMaxSizeLimit(maxSizeLimit); } private void setupSaslMechanisms() { Map<String, MechanismHandler> mechanismHandlerMap = new HashMap<String, MechanismHandler>(); mechanismHandlerMap.put(SupportedSaslMechanisms.PLAIN, new PlainMechanismHandler()); CramMd5MechanismHandler cramMd5MechanismHandler = new CramMd5MechanismHandler(); mechanismHandlerMap.put(SupportedSaslMechanisms.CRAM_MD5, cramMd5MechanismHandler); DigestMd5MechanismHandler digestMd5MechanismHandler = new DigestMd5MechanismHandler(); mechanismHandlerMap.put(SupportedSaslMechanisms.DIGEST_MD5, digestMd5MechanismHandler); GssapiMechanismHandler gssapiMechanismHandler = new GssapiMechanismHandler(); mechanismHandlerMap.put(SupportedSaslMechanisms.GSSAPI, gssapiMechanismHandler); NtlmMechanismHandler ntlmMechanismHandler = new NtlmMechanismHandler(); // TODO - set some sort of default NtlmProvider implementation here // ntlmMechanismHandler.setNtlmProvider( provider ); // TODO - or set FQCN of some sort of default NtlmProvider implementation here // ntlmMechanismHandler.setNtlmProviderFqcn( "com.foo.BarNtlmProvider" ); mechanismHandlerMap.put(SupportedSaslMechanisms.NTLM, ntlmMechanismHandler); mechanismHandlerMap.put(SupportedSaslMechanisms.GSS_SPNEGO, ntlmMechanismHandler); ldapServer.setSaslMechanismHandlers(mechanismHandlerMap); } /** * Deletes the Eve working directory. * * @param wkdir the directory to delete * @throws IOException if the directory cannot be deleted */ protected void doDelete(File wkdir) throws IOException { if (doDelete) { if (wkdir.exists()) { FileUtils.deleteDirectory(wkdir); } if (wkdir.exists()) { throw new IOException("Failed to delete: " + wkdir); } } } /** * Sets the contexts for this base class. Values of user and password used to * set the respective JNDI properties. These values can be overriden by the * overrides properties. * * @param user the username for authenticating as this user * @param passwd the password of the user * @throws NamingException if there is a failure of any kind */ protected void setContexts(String user, String passwd) throws Exception { Hashtable<String, Object> env = new Hashtable<String, Object>(); env.put(DirectoryService.JNDI_KEY, directoryService); env.put(Context.SECURITY_PRINCIPAL, user); env.put(Context.SECURITY_CREDENTIALS, passwd); env.put(Context.SECURITY_AUTHENTICATION, "simple"); env.put(Context.INITIAL_CONTEXT_FACTORY, CoreContextFactory.class.getName()); setContexts(env); } /** * Sets the contexts of this class taking into account the extras and overrides * properties. * * @param env an environment to use while setting up the system root. * @throws NamingException if there is a failure of any kind */ protected void setContexts(Hashtable<String, Object> env) throws Exception { Hashtable<String, Object> envFinal = new Hashtable<String, Object>(env); envFinal.put(Context.PROVIDER_URL, ""); rootDSE = directoryService.getAdminSession(); } /** * Sets the system context root to null. */ protected void tearDown() throws Exception { ldapServer.stop(); try { directoryService.shutdown(); } catch (Exception e) { // ignore } } }