Java tutorial
/* * #%L * Alfresco Repository * %% * Copyright (C) 2005 - 2016 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of * the paid license agreement will prevail. Otherwise, the software is * provided under the following open source license terms: * * Alfresco is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Alfresco is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * #L% */ package org.alfresco.repo.security.authentication.ldap; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.util.Collections; import java.util.HashSet; import java.util.Hashtable; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.naming.AuthenticationNotSupportedException; import javax.naming.CommunicationException; import javax.naming.Context; import javax.naming.NamingException; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import javax.naming.directory.BasicAttribute; import javax.naming.directory.BasicAttributes; import javax.naming.directory.DirContext; import javax.naming.directory.InitialDirContext; import javax.naming.ldap.Control; import javax.naming.ldap.InitialLdapContext; import javax.naming.ldap.LdapContext; import javax.naming.ldap.PagedResultsControl; import javax.naming.ldap.PagedResultsResponseControl; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.security.authentication.AlfrescoSSLSocketFactory; import org.alfresco.repo.security.authentication.AuthenticationDiagnostic; import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.PropertyCheck; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; public class LDAPInitialDirContextFactoryImpl implements LDAPInitialDirContextFactory, InitializingBean { private static final Log logger = LogFactory.getLog(LDAPInitialDirContextFactoryImpl.class); private static Set<Map<String, String>> checkedEnvs = Collections .synchronizedSet(new HashSet<Map<String, String>>(11)); private Map<String, String> defaultEnvironment = Collections.<String, String>emptyMap(); private Map<String, String> authenticatedEnvironment = Collections.<String, String>emptyMap(); private Map<String, String> poolSystemProperties = Collections.<String, String>emptyMap(); private String trustStorePath; private String trustStoreType; private String trustStorePassPhrase; public String getTrustStorePath() { return trustStorePath; } public void setTrustStorePath(String trustStorePath) { if (PropertyCheck.isValidPropertyString(trustStorePath)) { this.trustStorePath = trustStorePath; } } public String getTrustStoreType() { return trustStoreType; } public void setTrustStoreType(String trustStoreType) { if (PropertyCheck.isValidPropertyString(trustStoreType)) { this.trustStoreType = trustStoreType; } } public String getTrustStorePassPhrase() { return trustStorePassPhrase; } public void setTrustStorePassPhrase(String trustStorePassPhrase) { if (PropertyCheck.isValidPropertyString(trustStorePassPhrase)) { this.trustStorePassPhrase = trustStorePassPhrase; } } static { System.setProperty("javax.security.auth.useSubjectCredentialsOnly", "false"); } public LDAPInitialDirContextFactoryImpl() { super(); } public void setInitialDirContextEnvironment(Map<String, String> initialDirContextEnvironment) { this.authenticatedEnvironment = initialDirContextEnvironment; this.authenticatedEnvironment.values().removeAll(Collections.singleton(null)); } public Map<String, String> getInitialDirContextEnvironment() { return authenticatedEnvironment; } public void setDefaultIntialDirContextEnvironment(Map<String, String> defaultEnvironment) { this.defaultEnvironment = new LinkedHashMap<String, String>(defaultEnvironment.size()); this.defaultEnvironment.putAll(defaultEnvironment); // filter out empty values, as this usually means that property should be omitted. for (Entry<String, String> entry : defaultEnvironment.entrySet()) { if (entry.getValue() == null || entry.getValue().trim().length() == 0) { this.defaultEnvironment.remove(entry.getKey()); } } this.defaultEnvironment.values().removeAll(Collections.singleton(null)); } public InitialDirContext getDefaultIntialDirContext() throws AuthenticationException { return getDefaultIntialDirContext(0, new AuthenticationDiagnostic()); } public void setPoolSystemProperties(Map<String, String> poolSystemProperties) { this.poolSystemProperties = poolSystemProperties; for (Entry<String, String> entry : this.poolSystemProperties.entrySet()) { if (entry.getValue() != null && entry.getValue().trim().length() > 0) { System.setProperty(entry.getKey(), entry.getValue()); } } } public InitialDirContext getDefaultIntialDirContext(int pageSize) throws AuthenticationException { return getDefaultIntialDirContext(pageSize, new AuthenticationDiagnostic()); } @Override public InitialDirContext getDefaultIntialDirContext(AuthenticationDiagnostic diagnostic) throws AuthenticationException { return getDefaultIntialDirContext(0, diagnostic); } public InitialDirContext getDefaultIntialDirContext(int pageSize, AuthenticationDiagnostic diagnostic) throws AuthenticationException { Hashtable<String, String> env = new Hashtable<String, String>(defaultEnvironment.size()); env.putAll(defaultEnvironment); return buildInitialDirContext(env, pageSize, diagnostic); } private InitialDirContext buildInitialDirContext(Hashtable<String, String> env, int pageSize, AuthenticationDiagnostic diagnostic) throws AuthenticationException { String securityPrincipal = env.get(Context.SECURITY_PRINCIPAL); String providerURL = env.get(Context.PROVIDER_URL); if (isSSLSocketFactoryRequired()) { KeyStore trustStore = initTrustStore(); AlfrescoSSLSocketFactory.initTrustedSSLSocketFactory(trustStore); env.put("java.naming.ldap.factory.socket", AlfrescoSSLSocketFactory.class.getName()); } if (diagnostic == null) { diagnostic = new AuthenticationDiagnostic(); } try { // If a page size has been requested, use LDAP v3 paging if (pageSize > 0) { InitialLdapContext ctx = new InitialLdapContext(env, null); ctx.setRequestControls(new Control[] { new PagedResultsControl(pageSize, Control.CRITICAL) }); return ctx; } else { InitialDirContext ret = new InitialDirContext(env); Object[] args = { providerURL, securityPrincipal }; diagnostic.addStep(AuthenticationDiagnostic.STEP_KEY_LDAP_CONNECTED, true, args); return ret; } } catch (javax.naming.AuthenticationException ax) { Object[] args1 = { securityPrincipal }; Object[] args = { providerURL, securityPrincipal }; diagnostic.addStep(AuthenticationDiagnostic.STEP_KEY_LDAP_CONNECTED, true, args); diagnostic.addStep(AuthenticationDiagnostic.STEP_KEY_LDAP_AUTHENTICATION, false, args1); // wrong user/password - if we get this far the connection is O.K Object[] args2 = { securityPrincipal, ax.getLocalizedMessage() }; throw new AuthenticationException("authentication.err.authentication", diagnostic, args2, ax); } catch (CommunicationException ce) { Object[] args1 = { providerURL }; diagnostic.addStep(AuthenticationDiagnostic.STEP_KEY_LDAP_CONNECTING, false, args1); StringBuffer message = new StringBuffer(); message.append(ce.getClass().getName() + ", " + ce.getMessage()); Throwable cause = ce.getCause(); while (cause != null) { message.append(", "); message.append(cause.getClass().getName() + ", " + cause.getMessage()); cause = cause.getCause(); } // failed to connect Object[] args = { providerURL, message.toString() }; throw new AuthenticationException("authentication.err.communication", diagnostic, args, cause); } catch (NamingException nx) { Object[] args = { providerURL }; diagnostic.addStep(AuthenticationDiagnostic.STEP_KEY_LDAP_CONNECTING, false, args); StringBuffer message = new StringBuffer(); message.append(nx.getClass().getName() + ", " + nx.getMessage()); Throwable cause = nx.getCause(); while (cause != null) { message.append(", "); message.append(cause.getClass().getName() + ", " + cause.getMessage()); cause = cause.getCause(); } // failed to connect Object[] args1 = { providerURL, message.toString() }; throw new AuthenticationException("authentication.err.connection", diagnostic, args1, nx); } catch (IOException e) { Object[] args = { providerURL, securityPrincipal }; diagnostic.addStep(AuthenticationDiagnostic.STEP_KEY_LDAP_CONNECTED, true, args); throw new AuthenticationException("Unable to encode LDAP v3 request controls", e); } } public boolean hasNextPage(DirContext ctx, int pageSize) { if (pageSize > 0) { try { LdapContext ldapContext = (LdapContext) ctx; Control[] controls = ldapContext.getResponseControls(); // Retrieve the paged result cookie if there is one if (controls != null) { for (Control control : controls) { if (control instanceof PagedResultsResponseControl) { byte[] cookie = ((PagedResultsResponseControl) control).getCookie(); if (cookie != null) { // Prepare for next page ldapContext.setRequestControls(new Control[] { new PagedResultsControl(pageSize, cookie, Control.CRITICAL) }); return true; } } } } } catch (NamingException nx) { throw new AuthenticationException("Unable to connect to LDAP Server; check LDAP configuration", nx); } catch (IOException e) { throw new AuthenticationException( "Unable to encode LDAP v3 request controls; check LDAP configuration", e); } } return false; } @Override public InitialDirContext getInitialDirContext(String principal, String credentials) throws AuthenticationException { return getInitialDirContext(principal, credentials, null); } public InitialDirContext getInitialDirContext(String principal, String credentials, AuthenticationDiagnostic diagnostic) throws AuthenticationException { if (diagnostic == null) { diagnostic = new AuthenticationDiagnostic(); } if (principal == null) { // failed before we tried to do anything diagnostic.addStep(AuthenticationDiagnostic.STEP_KEY_VALIDATION, false, null); throw new AuthenticationException("Null user name provided.", diagnostic); } if (principal.length() == 0) { diagnostic.addStep(AuthenticationDiagnostic.STEP_KEY_VALIDATION, false, null); throw new AuthenticationException("Empty user name provided.", diagnostic); } if (credentials == null) { diagnostic.addStep(AuthenticationDiagnostic.STEP_KEY_VALIDATION, false, null); throw new AuthenticationException("No credentials provided.", diagnostic); } if (credentials.length() == 0) { diagnostic.addStep(AuthenticationDiagnostic.STEP_KEY_VALIDATION, false, null); throw new AuthenticationException("Empty credentials provided.", diagnostic); } diagnostic.addStep(AuthenticationDiagnostic.STEP_KEY_VALIDATION, true, null); Hashtable<String, String> env = new Hashtable<String, String>(authenticatedEnvironment.size()); env.putAll(authenticatedEnvironment); env.put(Context.SECURITY_PRINCIPAL, principal); env.put(Context.SECURITY_CREDENTIALS, credentials); return buildInitialDirContext(env, 0, diagnostic); } public static void main(String[] args) { // ....build a pyramid selling scheme ..... // A group has three user members and 2 group members .... and off we go .... // We make the people and groups to represent this and stick them into LDAP ...used to populate a test data base for user and groups int userMembers = Integer.parseInt(args[3]); ApplicationContext applicationContext = ApplicationContextHelper.getApplicationContext(); LDAPInitialDirContextFactory factory = (LDAPInitialDirContextFactory) applicationContext .getBean("ldapInitialDirContextFactory"); InitialDirContext ctx = null; try { ctx = factory.getInitialDirContext("cn=" + args[0] + "," + args[2], args[1]); /* Values we'll use in creating the entry */ Attribute objClasses = new BasicAttribute("objectclass"); objClasses.add("top"); objClasses.add("person"); objClasses.add("organizationalPerson"); objClasses.add("inetOrgPerson"); for (int i = 0; i < userMembers; i++) { Attribute cn = new BasicAttribute("cn", "User" + i + " TestUser"); Attribute sn = new BasicAttribute("sn", "TestUser"); Attribute givenNames = new BasicAttribute("givenName", "User" + i); Attribute telephoneNumber = new BasicAttribute("telephoneNumber", "123"); Attribute uid = new BasicAttribute("uid", "User" + i); Attribute mail = new BasicAttribute("mail", "woof@woof"); Attribute o = new BasicAttribute("o", "Alfresco"); Attribute userPassword = new BasicAttribute("userPassword", "bobbins"); /* Specify the DN we're adding */ String dn = "cn=User" + i + " TestUser," + args[2]; Attributes orig = new BasicAttributes(); orig.put(objClasses); orig.put(cn); orig.put(sn); orig.put(givenNames); orig.put(telephoneNumber); orig.put(uid); orig.put(mail); orig.put(o); orig.put(userPassword); try { ctx.destroySubcontext(dn); } catch (NamingException e) { // TODO Auto-generated catch block e.printStackTrace(); } ctx.createSubcontext(dn, orig); } } catch (NamingException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { if (ctx != null) { try { ctx.close(); } catch (NamingException e) { e.printStackTrace(); } } } } public void afterPropertiesSet() throws Exception { logger.debug("after Properties Set"); // Check Anonymous bind Hashtable<String, String> env = new Hashtable<String, String>(authenticatedEnvironment.size()); env.putAll(authenticatedEnvironment); env.remove(Context.SECURITY_PRINCIPAL); env.remove(Context.SECURITY_CREDENTIALS); if (isSSLSocketFactoryRequired()) { KeyStore trustStore = initTrustStore(); AlfrescoSSLSocketFactory.initTrustedSSLSocketFactory(trustStore); env.put("java.naming.ldap.factory.socket", AlfrescoSSLSocketFactory.class.getName()); } try { new InitialDirContext(env); logger.warn("LDAP server supports anonymous bind " + env.get(Context.PROVIDER_URL)); } catch (javax.naming.AuthenticationException ax) { } catch (AuthenticationNotSupportedException e) { } catch (NamingException nx) { logger.error("Unable to connect to LDAP Server; check LDAP configuration", nx); return; } // Simple DN and password env = new Hashtable<String, String>(authenticatedEnvironment.size()); env.putAll(authenticatedEnvironment); env.put(Context.SECURITY_PRINCIPAL, "daftAsABrush"); env.put(Context.SECURITY_CREDENTIALS, "daftAsABrush"); if (isSSLSocketFactoryRequired()) { KeyStore trustStore = initTrustStore(); AlfrescoSSLSocketFactory.initTrustedSSLSocketFactory(trustStore); env.put("java.naming.ldap.factory.socket", AlfrescoSSLSocketFactory.class.getName()); } try { new InitialDirContext(env); throw new AuthenticationException("The ldap server at " + env.get(Context.PROVIDER_URL) + " falls back to use anonymous bind if invalid security credentials are presented. This is not supported."); } catch (javax.naming.AuthenticationException ax) { logger.info("LDAP server does not fall back to anonymous bind for a string uid and password at " + env.get(Context.PROVIDER_URL)); } catch (AuthenticationNotSupportedException e) { logger.info("LDAP server does not fall back to anonymous bind for a string uid and password at " + env.get(Context.PROVIDER_URL)); } catch (NamingException nx) { logger.info("LDAP server does not support simple string user ids and invalid credentials at " + env.get(Context.PROVIDER_URL)); } // DN and password env = new Hashtable<String, String>(authenticatedEnvironment.size()); env.putAll(authenticatedEnvironment); env.put(Context.SECURITY_PRINCIPAL, "cn=daftAsABrush,dc=woof"); env.put(Context.SECURITY_CREDENTIALS, "daftAsABrush"); if (isSSLSocketFactoryRequired()) { KeyStore trustStore = initTrustStore(); AlfrescoSSLSocketFactory.initTrustedSSLSocketFactory(trustStore); env.put("java.naming.ldap.factory.socket", AlfrescoSSLSocketFactory.class.getName()); } try { new InitialDirContext(env); throw new AuthenticationException("The ldap server at " + env.get(Context.PROVIDER_URL) + " falls back to use anonymous bind if invalid security credentials are presented. This is not supported."); } catch (javax.naming.AuthenticationException ax) { logger.info("LDAP server does not fall back to anonymous bind for a simple dn and password at " + env.get(Context.PROVIDER_URL)); } catch (AuthenticationNotSupportedException e) { logger.info("LDAP server does not fall back to anonymous bind for a simple dn and password at " + env.get(Context.PROVIDER_URL)); } catch (NamingException nx) { logger.info("LDAP server does not support simple DN and invalid password at " + env.get(Context.PROVIDER_URL)); } // Check more if we have a real principal we expect to work String principal = defaultEnvironment.get(Context.SECURITY_PRINCIPAL); if (principal != null) { // Correct principal invalid password env = new Hashtable<String, String>(authenticatedEnvironment.size()); env.putAll(authenticatedEnvironment); env.put(Context.SECURITY_PRINCIPAL, principal); env.put(Context.SECURITY_CREDENTIALS, "sdasdasdasdasd123123123"); if (isSSLSocketFactoryRequired()) { KeyStore trustStore = initTrustStore(); AlfrescoSSLSocketFactory.initTrustedSSLSocketFactory(trustStore); env.put("java.naming.ldap.factory.socket", AlfrescoSSLSocketFactory.class.getName()); } if (!checkedEnvs.contains(env)) { try { new InitialDirContext(env); throw new AuthenticationException("The ldap server at " + env.get(Context.PROVIDER_URL) + " falls back to use anonymous bind for a known principal if invalid security credentials are presented. This is not supported."); } catch (javax.naming.AuthenticationException ax) { logger.info( "LDAP server does not fall back to anonymous bind for known principal and invalid credentials at " + env.get(Context.PROVIDER_URL)); } catch (AuthenticationNotSupportedException e) { logger.info("LDAP server does not support the required authentication mechanism"); } catch (NamingException nx) { // already done } // Record this environment as checked so that we don't check it again on further restarts / other subsystem // instances checkedEnvs.add(env); } } } /** * Check if it required to use custom SSL socket factory with custom trustStore. * <br>Required for LDAPS configuration. The <code>ldap.authentication.java.naming.security.protocol</code> should be set to "ssl" for LDAPS. * <br>The following properties should be set: * <ul> * <li>ldap.authentication.truststore.path * <li>ldap.authentication.truststore.type * <li>ldap.authentication.truststore.passphrase * <li>ldap.authentication.java.naming.security.protocol * </ul> * * @return <code>true</code> if all the required properties are set */ private boolean isSSLSocketFactoryRequired() { boolean result = false; // Check for LDAPS config String protocol = authenticatedEnvironment.get(Context.SECURITY_PROTOCOL); if (protocol != null && protocol.equals("ssl")) { if (getTrustStoreType() != null && getTrustStorePath() != null && getTrustStoreType() != null) { result = true; } else { logger.warn("The SSL configuration for LDAPS is not full, the default configuration will be used."); } } return result; } /** * Initialize trustStore with Spring set properties: * <ul> * <li>ldap.authentication.truststore.path * <li>ldap.authentication.truststore.type * <li>ldap.authentication.truststore.passphrase * </ul> * * @return {@link KeyStore} with loaded trustStore file */ private KeyStore initTrustStore() { KeyStore ks; String trustStoreType = getTrustStoreType(); try { ks = KeyStore.getInstance(trustStoreType); } catch (KeyStoreException kse) { throw new AlfrescoRuntimeException("No provider supports " + trustStoreType, kse); } try { ks.load(new FileInputStream(getTrustStorePath()), getTrustStorePassPhrase().toCharArray()); } catch (FileNotFoundException fnfe) { throw new AlfrescoRuntimeException("The truststore file is not found.", fnfe); } catch (IOException ioe) { throw new AlfrescoRuntimeException("The truststore file cannot be read.", ioe); } catch (NoSuchAlgorithmException nsae) { throw new AlfrescoRuntimeException( "Algorithm used to check the integrity of the truststore cannot be found.", nsae); } catch (CertificateException ce) { throw new AlfrescoRuntimeException("The certificates cannot be loaded from truststore.", ce); } return ks; } }