diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index a8f2f433cd..693bff23d3 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -456,6 +456,7 @@ alfresco.messages.subscription-service alfresco.messages.replication alfresco.messages.categories + alfresco.messages.authentication diff --git a/config/alfresco/messages/authentication.properties b/config/alfresco/messages/authentication.properties new file mode 100644 index 0000000000..a618d5e37e --- /dev/null +++ b/config/alfresco/messages/authentication.properties @@ -0,0 +1,34 @@ +# authentication error messages +authentication.err.validation.authenticator.notfound=Failed to authenticate, authenticator with name, {0}, not found +authentication.err.validation.authenticator.notactive=The authenticator is not active + +authentication.err.authentication=Failed to authenticate, username or password is wrong. User name:{0} Reason {1} +authentication.err.connection=Failed to connect to {0}. Reason {1} +authentication.err.communication=Failed to communicate with {0}. Reason {1} + +# LDAP error messages +authentication.err.connection.ldap.authenticator.unknownhost=Failed to connect, the ldap host {0} is unknown +authentication.err.connection.ldap.user.notfound=LDAP User {0} not found +authentication.err.connection.ldap.manager.notfound=LDAP Manager User {0} not found +authentication.err.connection.ldap.search=Unable to search LDAP. Reason {0} + +# PASSTHRU +authentication.err.connection.passthru.server=Failed to open session to passthru server +authentication.err.passthru.token.unsupported=Unsupported authentication token type +authentication.err.passthru.guest.notenabled=Guest logons disabled +authentication.err.passthru.user.disabled=Account disabled +authentication.err.passthru.user.notfound=Passthru user {0} not found + +# Authentication Diagnostic Steps +authentication.step.ldap.validation=Validation of request +authentication.step.ldap.connecting=Connecting to LDAP Server {0} +authentication.step.ldap.connected=Connected to LDAP server {0} with principal: {1} +authentication.step.ldap.lookup=Lookup Test User userId:{0} with query: {1} +authentication.step.ldap.lookedup=Looked up Test User userId:{0}, found distinguished name (DN):{1} +authentication.step.ldap.format.user=User Name Format specified, userNameFormat:{2} Format user userId:{0} to make Distinguished name (DN):{1} +authentication.step.ldap.authentication=Authenticate userId:{0} + +# Error messages +authentication.ldap.validation.authenticator.notfound=Authenticator not found + + diff --git a/config/alfresco/messages/email-service.properties b/config/alfresco/messages/email-service.properties index b6793e137c..326b7a7fe1 100644 --- a/config/alfresco/messages/email-service.properties +++ b/config/alfresco/messages/email-service.properties @@ -23,3 +23,5 @@ email.server.err.parse_message=Failed to parse the email message: {0} email.server.err.usupported_encoding=Encoding ''{0}'' is not supported email.server.err.failed_to_read_content_stream=Failed to read the message part content: {0} email.server.err.incorrect_message_part=Incorrect message part: {0} + +email.outbound.err.send.failed=Failed to send email to: {0} cause {1} diff --git a/config/alfresco/subsystems/Authentication/common-ldap-context.xml b/config/alfresco/subsystems/Authentication/common-ldap-context.xml index a9ab794413..1cd6c314b1 100644 --- a/config/alfresco/subsystems/Authentication/common-ldap-context.xml +++ b/config/alfresco/subsystems/Authentication/common-ldap-context.xml @@ -13,6 +13,13 @@ on from the web browser. You do not have to use LDAP authentication to synchronise groups and users from an LDAP store if it supports other authentication routes, like Active Directory. --> + + + + + + + diff --git a/config/alfresco/subsystems/email/OutboundSMTP/outboundSMTP-context.xml b/config/alfresco/subsystems/email/OutboundSMTP/outboundSMTP-context.xml index 1310a0e18d..782062d059 100755 --- a/config/alfresco/subsystems/email/OutboundSMTP/outboundSMTP-context.xml +++ b/config/alfresco/subsystems/email/OutboundSMTP/outboundSMTP-context.xml @@ -89,4 +89,10 @@ ${mail.testmessage.text} + + + + + + \ No newline at end of file diff --git a/config/alfresco/template-services-context.xml b/config/alfresco/template-services-context.xml index 96c9017881..912ce2c23c 100644 --- a/config/alfresco/template-services-context.xml +++ b/config/alfresco/template-services-context.xml @@ -174,4 +174,10 @@ + + + urldecode + + + diff --git a/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java b/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java index 7b55986ac0..8effd3a334 100644 --- a/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java @@ -25,6 +25,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; @@ -87,7 +88,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase public static final String PARAM_TEMPLATE_MODEL = "template_model"; public static final String PARAM_IGNORE_SEND_FAILURE = "ignore_send_failure"; public static final String PARAM_SEND_AFTER_COMMIT = "send_after_commit"; - + /** * From address */ @@ -96,7 +97,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase /** * The java mail sender */ - private JavaMailSender javaMailSender; + private JavaMailSender mailService; /** * The Template service @@ -169,7 +170,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase */ public void setMailService(JavaMailSender javaMailSender) { - this.javaMailSender = javaMailSender; + this.mailService = javaMailSender; } /** @@ -261,11 +262,57 @@ public class MailActionExecuter extends ActionExecuterAbstractBase { this.sendTestMessage = sendTestMessage; } + + /** + * Send a test message + * + * @return true, message sent + * @throws AlfrescoRuntimeException + */ + public boolean sendTestMessage() + { + Map params = new HashMap(); + params.put(PARAM_TO, testMessageTo); + params.put(PARAM_SUBJECT, testMessageSubject); + params.put(PARAM_TEXT, testMessageText); + + Action ruleAction = serviceRegistry.getActionService().createAction(NAME, params); + + MimeMessageHelper message = prepareEmail(ruleAction, null); + try + { + mailService.send(message.getMimeMessage()); + onSend(); + } + catch (MailException me) + { + onFail(); + StringBuffer txt = new StringBuffer(); + + txt.append(me.getClass().getName() + ", " + me.getMessage()); + + Throwable cause = me.getCause(); + while (cause != null) + { + txt.append(", "); + txt.append(cause.getClass().getName() + ", " + cause.getMessage()); + cause = cause.getCause(); + } + + Object[] args = {testMessageTo, txt.toString()}; + throw new AlfrescoRuntimeException("email.outbound.err.send.failed", args, me); + } + + return true; + } @Override public void init() { + numberSuccessfulSends.set(0); + numberFailedSends.set(0); + super.init(); if (sendTestMessage) { @@ -323,7 +370,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase { if (finalMessage != null) { - sendEmail(ruleAction, actionedUponNodeRef, finalMessage); + sendEmail(ruleAction, finalMessage); } return null; } @@ -335,7 +382,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase { if (validNodeRefIfPresent(actionedUponNodeRef)) { - sendEmail(ruleAction, actionedUponNodeRef, finalMessage); + sendEmail(ruleAction, finalMessage); } } } @@ -363,7 +410,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase return sendAfterCommit == null ? false : sendAfterCommit.booleanValue(); } - public MimeMessageHelper prepareEmail(final Action ruleAction, final NodeRef actionedUponNodeRef) + public MimeMessageHelper prepareEmail(final Action ruleAction , final NodeRef actionedUponNodeRef) { // Create the mime mail message. // Hack: using an array here to get around the fact that inner classes aren't closures. @@ -604,7 +651,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase } }; - MimeMessage mimeMessage = javaMailSender.createMimeMessage(); + MimeMessage mimeMessage = mailService.createMimeMessage(); try { mailPreparer.prepare(mimeMessage); @@ -620,7 +667,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase return messageRef[0]; } - private void sendEmail(final Action ruleAction, final NodeRef actionedUponNodeRef, MimeMessageHelper preparedMessage) + private void sendEmail(final Action ruleAction, MimeMessageHelper preparedMessage) { try { @@ -629,7 +676,8 @@ public class MailActionExecuter extends ActionExecuterAbstractBase { if (!testMode) { - javaMailSender.send(preparedMessage.getMimeMessage()); + mailService.send(preparedMessage.getMimeMessage()); + onSend(); } else { @@ -639,6 +687,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase } catch (MailException e) { + onFail(); String to = (String)ruleAction.getParameterValue(PARAM_TO); if (to == null) { @@ -656,7 +705,8 @@ public class MailActionExecuter extends ActionExecuterAbstractBase Boolean ignoreError = (Boolean)ruleAction.getParameterValue(PARAM_IGNORE_SEND_FAILURE); if (ignoreError == null || ignoreError.booleanValue() == false) { - throw new AlfrescoRuntimeException("Failed to send email to:" + to, e); + Object[] args = {to, e.toString()}; + throw new AlfrescoRuntimeException("email.outbound.err.send.failed", args, e); } } } @@ -815,4 +865,26 @@ public class MailActionExecuter extends ActionExecuterAbstractBase + sysAdminParams.getAlfrescoPort(); } } + + static AtomicInteger numberSuccessfulSends = new AtomicInteger(0); + static AtomicInteger numberFailedSends = new AtomicInteger(0); + protected void onSend() + { + numberSuccessfulSends.getAndIncrement(); + } + + protected void onFail() + { + numberFailedSends.getAndIncrement(); + } + + public int getNumberSuccessfulSends() + { + return numberSuccessfulSends.get(); + } + + public int getNumberFailedSends() + { + return numberFailedSends.get(); + } } diff --git a/source/java/org/alfresco/repo/action/executer/MailActionExecuterMonitor.java b/source/java/org/alfresco/repo/action/executer/MailActionExecuterMonitor.java new file mode 100644 index 0000000000..0655837916 --- /dev/null +++ b/source/java/org/alfresco/repo/action/executer/MailActionExecuterMonitor.java @@ -0,0 +1,36 @@ +package org.alfresco.repo.action.executer; + +import org.alfresco.error.AlfrescoRuntimeException; + +public class MailActionExecuterMonitor +{ + private MailActionExecuter mailActionExceuter; + + public String sendTestMessage() + { + try + { + mailActionExceuter.sendTestMessage(); + return "email message sent"; + } + catch + (AlfrescoRuntimeException are) + { + return (are.getMessage()); + } + } + public int getNumberFailedSends() + { + return mailActionExceuter.getNumberFailedSends(); + } + + public int getNumberSuccessfulSends() + { + return mailActionExceuter.getNumberSuccessfulSends(); + } + + public void setMailActionExecuter(MailActionExecuter mailActionExceuter) + { + this.mailActionExceuter = mailActionExceuter; + } +} diff --git a/source/java/org/alfresco/repo/management/subsystems/ChildApplicationContextFactory.java b/source/java/org/alfresco/repo/management/subsystems/ChildApplicationContextFactory.java index 2575197c9d..a0b5dbdeab 100644 --- a/source/java/org/alfresco/repo/management/subsystems/ChildApplicationContextFactory.java +++ b/source/java/org/alfresco/repo/management/subsystems/ChildApplicationContextFactory.java @@ -383,6 +383,26 @@ public class ChildApplicationContextFactory extends AbstractPropertyBackedBean i super.destroy(permanent); } } + + + /** + * Gets the application context. Will not start a subsystem. + * + * @return the application context or null + */ + public ApplicationContext getReadOnlyApplicationContext() + { + this.lock.readLock().lock(); + try + { + return ((ApplicationContextState) getState(false)).getApplicationContext(); + } + finally + { + this.lock.readLock().unlock(); + } + + } /* * (non-Javadoc) @@ -769,6 +789,16 @@ public class ChildApplicationContextFactory extends AbstractPropertyBackedBean i } } } + + /** + * Gets the application context. Will not start a subsystem. + * + * @return the application context or null + */ + public ApplicationContext getReadOnlyApplicationContext() + { + return this.applicationContext; + } /** * Gets the application context. diff --git a/source/java/org/alfresco/repo/management/subsystems/ChildApplicationContextManager.java b/source/java/org/alfresco/repo/management/subsystems/ChildApplicationContextManager.java index 27acc613fb..b761fd8613 100644 --- a/source/java/org/alfresco/repo/management/subsystems/ChildApplicationContextManager.java +++ b/source/java/org/alfresco/repo/management/subsystems/ChildApplicationContextManager.java @@ -43,7 +43,7 @@ public interface ChildApplicationContextManager * * @param id * the identifier of the application context to retrieve - * @return the application context with the given identifier + * @return the application context with the given identifier or null if it does not exist */ public ApplicationContext getApplicationContext(String id); } diff --git a/source/java/org/alfresco/repo/module/ModuleComponentHelper.java b/source/java/org/alfresco/repo/module/ModuleComponentHelper.java index 1e510e925f..f662109450 100644 --- a/source/java/org/alfresco/repo/module/ModuleComponentHelper.java +++ b/source/java/org/alfresco/repo/module/ModuleComponentHelper.java @@ -346,6 +346,36 @@ public class ModuleComponentHelper throw AlfrescoRuntimeException.create(ERR_ORPHANED_COMPONENTS, missedComponents.size()); } } + + /** + * Gets a list of all registered modules. + * + * @return A Collection of module IDs + */ + Collection getRegistryModuleIDs() + { + // Get the IDs of all modules from the registry + RegistryKey moduleKeyAllIds = new RegistryKey( + ModuleComponentHelper.URI_MODULES_1_0, + REGISTRY_PATH_MODULES, null); + + return registryService.getChildElements(moduleKeyAllIds); + } + + /** + * Returns the version number of a module from the Registry. + * + * @param moduleId + * @return + */ + VersionNumber getVersion(String moduleId) + { + RegistryKey moduleKeyCurrentVersion = new RegistryKey( + ModuleComponentHelper.URI_MODULES_1_0, + REGISTRY_PATH_MODULES, moduleId, REGISTRY_PROPERTY_CURRENT_VERSION); + VersionNumber versionCurrent = (VersionNumber) registryService.getProperty(moduleKeyCurrentVersion); + return versionCurrent; + } /** * Checks to see if there are any modules registered as installed that aren't in the @@ -356,10 +386,7 @@ public class ModuleComponentHelper private void checkForMissingModules() { // Get the IDs of all modules from the registry - RegistryKey moduleKeyAllIds = new RegistryKey( - ModuleComponentHelper.URI_MODULES_1_0, - REGISTRY_PATH_MODULES, null); - Collection moduleIds = registryService.getChildElements(moduleKeyAllIds); + Collection moduleIds = getRegistryModuleIDs(); // Check that each module is present in the distribution for (String moduleId : moduleIds) @@ -375,10 +402,8 @@ public class ModuleComponentHelper else { // Get the specifics of the missing module - RegistryKey moduleKeyCurrentVersion = new RegistryKey( - ModuleComponentHelper.URI_MODULES_1_0, - REGISTRY_PATH_MODULES, moduleId, REGISTRY_PROPERTY_CURRENT_VERSION); - VersionNumber versionCurrent = (VersionNumber) registryService.getProperty(moduleKeyCurrentVersion); + + VersionNumber versionCurrent = getVersion(moduleId); // The module is missing, so warn loggerService.warn(I18NUtil.getMessage(MSG_MISSING, moduleId, versionCurrent)); } diff --git a/source/java/org/alfresco/repo/module/ModuleComponentHelperTest.java b/source/java/org/alfresco/repo/module/ModuleComponentHelperTest.java index 5b20ceb19c..a9977d784c 100644 --- a/source/java/org/alfresco/repo/module/ModuleComponentHelperTest.java +++ b/source/java/org/alfresco/repo/module/ModuleComponentHelperTest.java @@ -247,6 +247,24 @@ public class ModuleComponentHelperTest extends BaseAlfrescoTestCase { throw new UnsupportedOperationException(); } + + @Override + public List getMissingModules() + { + // Create some module details + List details = new ArrayList(3); + for (int i = 0; i < 3; i++) + { + ModuleDetails moduleDetails = new ModuleDetailsImpl( + MODULE_IDS[i], + currentVersion, + "Module-" + i, + "Description-" + i); + details.add(moduleDetails); + } + // Done + return details; + } } /** Keep track of the execution count */ diff --git a/source/java/org/alfresco/repo/module/ModuleServiceImpl.java b/source/java/org/alfresco/repo/module/ModuleServiceImpl.java index 3f82282f2a..390816a023 100644 --- a/source/java/org/alfresco/repo/module/ModuleServiceImpl.java +++ b/source/java/org/alfresco/repo/module/ModuleServiceImpl.java @@ -27,14 +27,17 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.TreeMap; import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.admin.registry.RegistryKey; import org.alfresco.repo.admin.registry.RegistryService; import org.alfresco.repo.tenant.TenantAdminService; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.module.ModuleDetails; import org.alfresco.service.cmr.module.ModuleService; import org.alfresco.service.descriptor.DescriptorService; +import org.alfresco.util.VersionNumber; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; @@ -43,6 +46,7 @@ import org.springframework.context.ApplicationContextAware; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.extensions.surf.util.I18NUtil; /** * This component controls the execution of @@ -162,6 +166,35 @@ public class ModuleServiceImpl implements ApplicationContextAware, ModuleService return result; } + /** + * {@inheritDoc} + */ + public List getMissingModules() + { + cacheModuleDetails(); + + // Get the IDs of all modules from the registry + Collection moduleIds = moduleComponentHelper.getRegistryModuleIDs(); + + List result = new ArrayList(); + + //Check for missing modules + for (String moduleId : moduleIds) + { + ModuleDetails moduleDetails = getModule(moduleId); + if (moduleDetails == null) + { + // Get the specifics of the missing module and add them to the list. + VersionNumber currentVersion = moduleComponentHelper.getVersion(moduleId); + + ModuleDetails newModuleDetails = new ModuleDetailsImpl(moduleId, currentVersion, "", ""); + + result.add(newModuleDetails); + } + } + return result; + } + /** * Ensure that the {@link #moduleDetailsById module details} are populated. *

diff --git a/source/java/org/alfresco/repo/security/authentication/AbstractAuthenticationComponent.java b/source/java/org/alfresco/repo/security/authentication/AbstractAuthenticationComponent.java index d1abb30fa5..a312cd0dd0 100644 --- a/source/java/org/alfresco/repo/security/authentication/AbstractAuthenticationComponent.java +++ b/source/java/org/alfresco/repo/security/authentication/AbstractAuthenticationComponent.java @@ -22,6 +22,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.Set; import java.util.TreeSet; +import java.util.concurrent.atomic.AtomicInteger; import net.sf.acegisecurity.Authentication; import net.sf.acegisecurity.GrantedAuthority; @@ -160,9 +161,11 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC try { authenticateImpl(userName, password); + onAuthenticate(); } catch (RuntimeException e) { + onFail(); if (logger.isDebugEnabled()) { logger.debug("Failed to authenticate user \"" + userName + '"', e); @@ -661,4 +664,26 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC { return authenticationContext.setUserDetails(ud); } + + AtomicInteger numberSuccessfulAuthentications = new AtomicInteger(0); + AtomicInteger numberFailedAuthentications = new AtomicInteger(0); + protected void onAuthenticate() + { + numberSuccessfulAuthentications.getAndIncrement(); + } + + protected void onFail() + { + numberFailedAuthentications.getAndIncrement(); + } + + public int getNumberSuccessfulAuthentications() + { + return numberSuccessfulAuthentications.get(); + } + + public int getNumberFailedAuthentications() + { + return numberFailedAuthentications.get(); + } } diff --git a/source/java/org/alfresco/repo/security/authentication/AbstractChainingAuthenticationComponent.java b/source/java/org/alfresco/repo/security/authentication/AbstractChainingAuthenticationComponent.java index fd285145d5..6cb1e19bc9 100644 --- a/source/java/org/alfresco/repo/security/authentication/AbstractChainingAuthenticationComponent.java +++ b/source/java/org/alfresco/repo/security/authentication/AbstractChainingAuthenticationComponent.java @@ -22,6 +22,8 @@ import java.util.Collection; import java.util.Set; import java.util.TreeSet; +import org.alfresco.repo.management.subsystems.ActivateableBean; + import net.sf.acegisecurity.Authentication; /** @@ -40,6 +42,13 @@ public abstract class AbstractChainingAuthenticationComponent extends AbstractAu { super(); } + + /** + * Get the authentication component with the specified name + * @param name + * @return the authentication component or null if it does not exist + */ + protected abstract AuthenticationComponent getAuthenticationComponent(String name); /** * Gets the authentication components across which methods will chain. @@ -55,6 +64,8 @@ public abstract class AbstractChainingAuthenticationComponent extends AbstractAu * the user name * @param password * the password + * + * @throws AuthenticationException */ @Override protected void authenticateImpl(String userName, char[] password) @@ -73,6 +84,44 @@ public abstract class AbstractChainingAuthenticationComponent extends AbstractAu } throw new AuthenticationException("Failed to authenticate"); } + + /** + * Test authenticate with a specific authenticator and user name and password. + * + * @param authenticatorName + * the name of the authenticator to use + * @param userName + * the user name + * @param password + * the password + * @throws AuthenticationException including diagnostic information about the failure + */ + public void testAuthenticate(String authenticatorName, String userName, char[] password) + { + + AuthenticationComponent authenticationComponent = getAuthenticationComponent(authenticatorName); + if(authenticationComponent != null) + { + if (authenticationComponent instanceof ActivateableBean) + { + if(!((ActivateableBean) authenticationComponent).isActive()) + { + AuthenticationDiagnostic diagnostic = new AuthenticationDiagnostic(); + Object[] args = {authenticatorName}; + diagnostic.addStep(AuthenticationDiagnostic.STEP_KEY_VALIDATION_AUTHENTICATOR_NOT_ACTIVE, false, args); + throw new AuthenticationException("authentication.err.validation.authenticator.notactive", args , diagnostic); + } + } + + authenticationComponent.authenticate(userName, password); + return; + } + + AuthenticationDiagnostic diagnostic = new AuthenticationDiagnostic(); + Object[] args = {authenticatorName}; + diagnostic.addStep(AuthenticationDiagnostic.STEP_KEY_VALIDATION_AUTHENTICATOR_NOT_FOUND, false, args); + throw new AuthenticationException("authentication.err.validation.authenticator.notfound", args , diagnostic); + } /** * If any implementation supports guest then guest is allowed. diff --git a/source/java/org/alfresco/repo/security/authentication/ChainingAuthenticationComponentImpl.java b/source/java/org/alfresco/repo/security/authentication/ChainingAuthenticationComponentImpl.java index 7bed7b175f..80e13ede62 100644 --- a/source/java/org/alfresco/repo/security/authentication/ChainingAuthenticationComponentImpl.java +++ b/source/java/org/alfresco/repo/security/authentication/ChainingAuthenticationComponentImpl.java @@ -27,8 +27,8 @@ import net.sf.acegisecurity.Authentication; import org.alfresco.repo.security.authentication.ntlm.NLTMAuthenticator; /** - * A chaining authentication component is required for all the beans that qire up an authentication component and not an - * authentication service. It supports chaining in much the same way and wires up components in the same way asthe + * A chaining authentication component is required for all the beans that wire up an authentication component and not an + * authentication service. It supports chaining in much the same way and wires up components in the same way as the * chaining authentication service wires up services. * * @author andyh @@ -306,4 +306,11 @@ public class ChainingAuthenticationComponentImpl extends AbstractChainingAuthent return services; } } + + @Override + protected AuthenticationComponent getAuthenticationComponent(String name) + { + // not implemented + return null; + } } diff --git a/source/java/org/alfresco/repo/security/authentication/ldap/LDAPAuthenticationComponentImpl.java b/source/java/org/alfresco/repo/security/authentication/ldap/LDAPAuthenticationComponentImpl.java index eeb346d2c1..62ee199273 100644 --- a/source/java/org/alfresco/repo/security/authentication/ldap/LDAPAuthenticationComponentImpl.java +++ b/source/java/org/alfresco/repo/security/authentication/ldap/LDAPAuthenticationComponentImpl.java @@ -23,6 +23,7 @@ import javax.naming.directory.InitialDirContext; import org.alfresco.repo.management.subsystems.ActivateableBean; import org.alfresco.repo.security.authentication.AbstractAuthenticationComponent; +import org.alfresco.repo.security.authentication.AuthenticationDiagnostic; import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.security.sync.ldap.LDAPNameResolver; import org.springframework.beans.factory.InitializingBean; @@ -109,17 +110,35 @@ public class LDAPAuthenticationComponentImpl extends AbstractAuthenticationCompo */ protected void authenticateImpl(String userName, char[] password) throws AuthenticationException { - // If we aren't using a fixed name format, do a search to resolve the user DN - String userDN = userNameFormat == null ? ldapNameResolver.resolveDistinguishedName(userName) : String.format( - userNameFormat, new Object[] - { - escapeUserName(userName, escapeCommasInBind) - }); + // Distinguished name of user. + String userDN; + + AuthenticationDiagnostic diagnostic = new AuthenticationDiagnostic(); + + if(userNameFormat == null) + { + // If we aren't using a fixed name format, do a search to resolve the user DN + userDN = ldapNameResolver.resolveDistinguishedName(userName, diagnostic); + + Object[] params = {userName, userDN}; + diagnostic.addStep(AuthenticationDiagnostic.STEP_KEY_LDAP_LOOKEDUP_USER, true, params); + } + else + { + // we are using a fixed name format, + userDN = String.format( + userNameFormat, new Object[] + { + escapeUserName(userName, escapeCommasInBind) + }); + Object[] params = {userName, userDN, userNameFormat}; + diagnostic.addStep(AuthenticationDiagnostic.STEP_KEY_LDAP_FORMAT_USER, true, params); + } InitialDirContext ctx = null; try { - ctx = ldapInitialContextFactory.getInitialDirContext(userDN, new String(password)); + ctx = ldapInitialContextFactory.getInitialDirContext(userDN, new String(password), diagnostic); // Authentication has been successful. // Set the current user, they are now authenticated. diff --git a/source/java/org/alfresco/repo/security/authentication/ldap/LDAPInitialDirContextFactory.java b/source/java/org/alfresco/repo/security/authentication/ldap/LDAPInitialDirContextFactory.java index 5bf59444f9..ede04265c6 100644 --- a/source/java/org/alfresco/repo/security/authentication/ldap/LDAPInitialDirContextFactory.java +++ b/source/java/org/alfresco/repo/security/authentication/ldap/LDAPInitialDirContextFactory.java @@ -23,6 +23,7 @@ import java.util.Map; import javax.naming.directory.DirContext; import javax.naming.directory.InitialDirContext; +import org.alfresco.repo.security.authentication.AuthenticationDiagnostic; import org.alfresco.repo.security.authentication.AuthenticationException; /** @@ -39,6 +40,20 @@ public interface LDAPInitialDirContextFactory */ public void setInitialDirContextEnvironment(Map environment); + /** + * Use the environment properties and connect to the LDAP server, optionally configuring RFC 2696 paged results. + * Used to obtain read only access to the LDAP server. + * + * @param pageSize + * if a positive value, indicates that a LDAP v3 RFC 2696 paged results control should be used. The + * results of a search operation should be returned by the LDAP server in batches of the specified size. + * @param diagnostic + * @return the default intial dir context + * @throws AuthenticationException + * the authentication exception + */ + public InitialDirContext getDefaultIntialDirContext(int pageSize, AuthenticationDiagnostic diagnostic) throws AuthenticationException; + /** * Use the environment properties and connect to the LDAP server, optionally configuring RFC 2696 paged results. * Used to obtain read only access to the LDAP server. @@ -61,6 +76,15 @@ public interface LDAPInitialDirContextFactory */ public InitialDirContext getDefaultIntialDirContext() throws AuthenticationException; + /** + * Use the environment properties and connect to the LDAP server. + * Used to obtain read only access to the LDAP server. + * + * @return + * @throws AuthenticationException + */ + public InitialDirContext getDefaultIntialDirContext(AuthenticationDiagnostic diagnostic) throws AuthenticationException; + /** * Determines whether there is another page to fetch from the last search to be run in this context. Also prepares * the request controls so that the appropriate cookie will be passed in the next search. @@ -72,8 +96,8 @@ public interface LDAPInitialDirContextFactory * results of a search operation should be returned by the LDAP server in batches of the specified size. * @return true, if is ready for next page */ - public boolean hasNextPage(DirContext ctx, int pageSize); - + public boolean hasNextPage(DirContext ctx, int pageSize); + /** * Augment the connection environment with the identity and credentials and bind to the ldap server. * Mainly used to validate a user's credentials during authentication. @@ -84,4 +108,16 @@ public interface LDAPInitialDirContextFactory * @throws AuthenticationException */ public InitialDirContext getInitialDirContext(String principal, String credentials) throws AuthenticationException; + + /** + * Augment the connection environment with the identity and credentials and bind to the ldap server. + * Mainly used to validate a user's credentials during authentication. + * + * @param principal + * @param credentials + * @param diagnostic + * @return + * @throws AuthenticationException + */ + public InitialDirContext getInitialDirContext(String principal, String credentials, AuthenticationDiagnostic diagnostic) throws AuthenticationException; } diff --git a/source/java/org/alfresco/repo/security/authentication/ldap/LDAPInitialDirContextFactoryImpl.java b/source/java/org/alfresco/repo/security/authentication/ldap/LDAPInitialDirContextFactoryImpl.java index 4ff779df38..8302b135b9 100644 --- a/source/java/org/alfresco/repo/security/authentication/ldap/LDAPInitialDirContextFactoryImpl.java +++ b/source/java/org/alfresco/repo/security/authentication/ldap/LDAPInitialDirContextFactoryImpl.java @@ -26,6 +26,7 @@ import java.util.Map; 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; @@ -40,6 +41,7 @@ import javax.naming.ldap.LdapContext; import javax.naming.ldap.PagedResultsControl; import javax.naming.ldap.PagedResultsResponseControl; +import org.alfresco.repo.security.authentication.AuthenticationDiagnostic; import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.util.ApplicationContextHelper; import org.apache.commons.logging.Log; @@ -84,19 +86,38 @@ public class LDAPInitialDirContextFactoryImpl implements LDAPInitialDirContextFa public InitialDirContext getDefaultIntialDirContext() throws AuthenticationException { - return getDefaultIntialDirContext(0); + return getDefaultIntialDirContext(0, new AuthenticationDiagnostic()); + } + + 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) throws AuthenticationException + public InitialDirContext getDefaultIntialDirContext(int pageSize, AuthenticationDiagnostic diagnostic) throws AuthenticationException { Hashtable env = new Hashtable(defaultEnvironment.size()); env.putAll(defaultEnvironment); - return buildInitialDirContext(env, pageSize); + return buildInitialDirContext(env, pageSize, diagnostic); } - private InitialDirContext buildInitialDirContext(Hashtable env, int pageSize) + private InitialDirContext buildInitialDirContext(Hashtable env, int pageSize, AuthenticationDiagnostic diagnostic) throws AuthenticationException { + String securityPrincipal = env.get(Context.SECURITY_PRINCIPAL); + String providerURL = env.get(Context.PROVIDER_URL); + + if(diagnostic == null) + { + diagnostic = new AuthenticationDiagnostic(); + } try { // If a page size has been requested, use LDAP v3 paging @@ -111,20 +132,71 @@ public class LDAPInitialDirContextFactoryImpl implements LDAPInitialDirContextFa } else { - return new InitialDirContext(env); + 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) { - throw new AuthenticationException("LDAP authentication failed.", 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) { - throw new AuthenticationException("Unable to connect to LDAP Server; check LDAP configuration", 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) { - throw new AuthenticationException("Unable to encode LDAP v3 request controls; check LDAP configuration", 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); } } @@ -171,36 +243,58 @@ public class LDAPInitialDirContextFactoryImpl implements LDAPInitialDirContextFa } return false; } - - public InitialDirContext getInitialDirContext(String principal, String credentials) throws AuthenticationException + + @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) { - throw new AuthenticationException("Null user name provided."); + // 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) { - throw new AuthenticationException("Empty user name provided."); + diagnostic.addStep(AuthenticationDiagnostic.STEP_KEY_VALIDATION, false, null); + throw new AuthenticationException("Empty user name provided.", diagnostic); } if (credentials == null) { - throw new AuthenticationException("No credentials provided."); + diagnostic.addStep(AuthenticationDiagnostic.STEP_KEY_VALIDATION, false, null); + throw new AuthenticationException("No credentials provided.", diagnostic); } if (credentials.length() == 0) { - throw new AuthenticationException("Empty credentials provided."); + diagnostic.addStep(AuthenticationDiagnostic.STEP_KEY_VALIDATION, false, null); + throw new AuthenticationException("Empty credentials provided.", diagnostic); } + + diagnostic.addStep(AuthenticationDiagnostic.STEP_KEY_VALIDATION, true, null); Hashtable env = new Hashtable(authenticatedEnvironment.size()); env.putAll(authenticatedEnvironment); env.put(Context.SECURITY_PRINCIPAL, principal); env.put(Context.SECURITY_CREDENTIALS, credentials); - return buildInitialDirContext(env, 0); + return buildInitialDirContext(env, 0, diagnostic); } + + public static void main(String[] args) { @@ -291,6 +385,7 @@ public class LDAPInitialDirContextFactoryImpl implements LDAPInitialDirContextFa public void afterPropertiesSet() throws Exception { + logger.debug("after Properties Set"); // Check Anonymous bind Hashtable env = new Hashtable(authenticatedEnvironment.size()); @@ -417,4 +512,8 @@ public class LDAPInitialDirContextFactoryImpl implements LDAPInitialDirContextFa } } } + + + + } diff --git a/source/java/org/alfresco/repo/security/authentication/ldap/Monitor.java b/source/java/org/alfresco/repo/security/authentication/ldap/Monitor.java new file mode 100644 index 0000000000..e18108dbb1 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authentication/ldap/Monitor.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2005-2012 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.security.authentication.ldap; + +import java.util.HashMap; +import java.util.Map; + +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.CompositeDataSupport; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.OpenDataException; +import javax.management.openmbean.OpenType; +import javax.management.openmbean.SimpleType; +import javax.management.openmbean.TabularData; +import javax.management.openmbean.TabularDataSupport; +import javax.management.openmbean.TabularType; + +import org.alfresco.repo.security.authentication.AuthenticationException; +import org.alfresco.repo.security.authentication.AuthenticationStep; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Monitoring methods and properties to be exposed via the + * JMX admin console. + */ + +public class Monitor +{ + LDAPAuthenticationComponentImpl component; + + private static Log logger = LogFactory.getLog(Monitor.class); + + public void setLDAPAuthenticationComponent(LDAPAuthenticationComponentImpl component) + { + this.component = component; + } + + public String getAuthenticatorType() + { + return "ldap"; + } + + /** + * test authenticate + * + * @param userName + * @param credentials + * @throws AuthenticationException + */ + public CompositeData testAuthenticate(String userName, String credentials) + { + String stepKeys[] = {"id", "stepMessage", "success"}; + String stepDescriptions[] = {"id", "stepMessage", "success"}; + OpenType stepTypes[] = {SimpleType.INTEGER, SimpleType.STRING, SimpleType.BOOLEAN }; + + try + { + String[] key = {"id"}; + CompositeType sType = new CompositeType("Authentication Step", "Step", stepKeys, stepDescriptions, stepTypes); + TabularType tType = new TabularType("Diagnostic", "Authentication Steps", sType, key); + TabularData table = new TabularDataSupport(tType); + + String attributeKeys[] = {"authenticationMessage", "success", "diagnostic"}; + String attributeDescriptions[] = {"authenticationMessage", "success", "diagnostic"}; + OpenType attributeTypes[] = {SimpleType.STRING, SimpleType.BOOLEAN, tType}; + try + { + component.authenticate(userName, credentials.toCharArray()); + + CompositeType cType = new CompositeType("Authentication Result", "Result Success", attributeKeys, attributeDescriptions, attributeTypes); + Map value = new HashMap(); + value.put("authenticationMessage", "Test Passed"); + value.put("success", true); + value.put("diagnostic", table); + CompositeDataSupport row = new CompositeDataSupport(cType, value); + return row; + } + catch (AuthenticationException ae) + { + CompositeType cType = new CompositeType("Authentication Result", "Result Failed", attributeKeys, attributeDescriptions, attributeTypes); + Map value = new HashMap(); + value.put("authenticationMessage", ae.getMessage()); + value.put("success", false); + + if(ae.getDiagnostic() != null) + { + int i = 0; + for(AuthenticationStep step : ae.getDiagnostic().getSteps()) + { + Map x = new HashMap(); + x.put("id", i++); + x.put("stepMessage", step.getMessage()); + x.put("success", step.isSuccess()); + CompositeDataSupport row = new CompositeDataSupport(sType, x); + table.put(row); + + } + } + + value.put("diagnostic", table); + + CompositeDataSupport row = new CompositeDataSupport(cType, value); + + return row; + } + + } + catch (OpenDataException oe) + { + logger.error("", oe); + return null; + } + } + + public int getNumberFailedAuthentications() + { + return component.getNumberFailedAuthentications(); + } + + public int getNumberSuccessfulAuthentications() + { + return component.getNumberSuccessfulAuthentications(); + } +} diff --git a/source/java/org/alfresco/repo/security/authentication/ldap/MonitorBeanInfo.java b/source/java/org/alfresco/repo/security/authentication/ldap/MonitorBeanInfo.java new file mode 100644 index 0000000000..58fb85b2f8 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authentication/ldap/MonitorBeanInfo.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2005-2012 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.security.authentication.ldap; + +import java.beans.BeanInfo; +import java.beans.SimpleBeanInfo; +/** + * A BeanInfo providing metadata for the Monitor class + * + * @author mrogers + */ +public class MonitorBeanInfo extends SimpleBeanInfo +{ + /** + * Gets the beans BeanDescriptor. + * + * @return A BeanDescriptor providing overall information about + * the bean, such as its displayName, its customizer, etc. May + * return null if the information should be obtained by automatic + * analysis. + */ +// BeanDescriptor getBeanDescriptor(); + + /** + * Gets the beans EventSetDescriptors. + * + * @return An array of EventSetDescriptors describing the kinds of + * events fired by this bean. May return null if the information + * should be obtained by automatic analysis. + */ +// EventSetDescriptor[] getEventSetDescriptors(); + + /** + * A bean may have a "default" event that is the event that will + * mostly commonly be used by humans when using the bean. + * @return Index of default event in the EventSetDescriptor array + * returned by getEventSetDescriptors. + *

Returns -1 if there is no default event. + */ +// int getDefaultEventIndex(); + + /** + * Returns descriptors for all properties of the bean. + * May return {@code null} if the information + * should be obtained by automatic analysis. + *

+ * If a property is indexed, then its entry in the result array + * will belong to the {@link IndexedPropertyDescriptor} subclass + * of the {@link PropertyDescriptor} class. + * A client of the {@code getPropertyDescriptors} method + * can use "{@code instanceof}" to check + * whether a given {@code PropertyDescriptor} + * is an {@code IndexedPropertyDescriptor}. + * + * @return an array of {@code PropertyDescriptor}s + * describing all properties supported by the bean + * or {@code null} + */ +// PropertyDescriptor[] getPropertyDescriptors(); + + /** + * A bean may have a "default" property that is the property that will + * mostly commonly be initially chosen for update by human's who are + * customizing the bean. + * @return Index of default property in the PropertyDescriptor array + * returned by getPropertyDescriptors. + *

Returns -1 if there is no default property. + */ +// int getDefaultPropertyIndex(); + + /** + * Gets the beans MethodDescriptors. + * + * @return An array of MethodDescriptors describing the externally + * visible methods supported by this bean. May return null if + * the information should be obtained by automatic analysis. + */ +// MethodDescriptor[] getMethodDescriptors(); + + /** + * This method allows a BeanInfo object to return an arbitrary collection + * of other BeanInfo objects that provide additional information on the + * current bean. + *

+ * If there are conflicts or overlaps between the information provided + * by different BeanInfo objects, then the current BeanInfo takes precedence + * over the getAdditionalBeanInfo objects, and later elements in the array + * take precedence over earlier ones. + * + * @return an array of BeanInfo objects. May return null. + */ +// BeanInfo[] getAdditionalBeanInfo(); + +} diff --git a/source/java/org/alfresco/repo/security/authentication/subsystems/SubsystemChainingAuthenticationComponent.java b/source/java/org/alfresco/repo/security/authentication/subsystems/SubsystemChainingAuthenticationComponent.java index e70b057ffb..372ed1a84c 100644 --- a/source/java/org/alfresco/repo/security/authentication/subsystems/SubsystemChainingAuthenticationComponent.java +++ b/source/java/org/alfresco/repo/security/authentication/subsystems/SubsystemChainingAuthenticationComponent.java @@ -92,4 +92,25 @@ public class SubsystemChainingAuthenticationComponent extends AbstractChainingAu } return result; } + + @Override + protected AuthenticationComponent getAuthenticationComponent(String instanceId) + { + ApplicationContext context = this.applicationContextManager.getApplicationContext(instanceId); + if(context != null) + { + try + { + AuthenticationComponent authenticationComponent = (AuthenticationComponent) context + .getBean(sourceBeanName); + return authenticationComponent; + } + catch (NoSuchBeanDefinitionException e) + { + return null; + } + } + + return null; + } } diff --git a/source/java/org/alfresco/repo/security/sync/ldap/LDAPNameResolver.java b/source/java/org/alfresco/repo/security/sync/ldap/LDAPNameResolver.java index 1c2969c588..d89aed9618 100644 --- a/source/java/org/alfresco/repo/security/sync/ldap/LDAPNameResolver.java +++ b/source/java/org/alfresco/repo/security/sync/ldap/LDAPNameResolver.java @@ -18,6 +18,7 @@ */ package org.alfresco.repo.security.sync.ldap; +import org.alfresco.repo.security.authentication.AuthenticationDiagnostic; import org.alfresco.repo.security.authentication.AuthenticationException; /** @@ -37,5 +38,5 @@ public interface LDAPNameResolver * @throws AuthenticationException * if the user ID cannot be resolved */ - public String resolveDistinguishedName(String userId) throws AuthenticationException; + public String resolveDistinguishedName(String userId, AuthenticationDiagnostic diagnostic) throws AuthenticationException; } diff --git a/source/java/org/alfresco/repo/security/sync/ldap/LDAPUserRegistry.java b/source/java/org/alfresco/repo/security/sync/ldap/LDAPUserRegistry.java index 196a968709..290e827a1d 100644 --- a/source/java/org/alfresco/repo/security/sync/ldap/LDAPUserRegistry.java +++ b/source/java/org/alfresco/repo/security/sync/ldap/LDAPUserRegistry.java @@ -56,6 +56,7 @@ import javax.naming.ldap.LdapName; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.management.subsystems.ActivateableBean; +import org.alfresco.repo.security.authentication.AuthenticationDiagnostic; import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.security.authentication.ldap.LDAPInitialDirContextFactory; import org.alfresco.repo.security.sync.NodeDescription; @@ -920,8 +921,12 @@ public class LDAPUserRegistry implements UserRegistry, LDAPNameResolver, Initial * (non-Javadoc) * @see org.alfresco.repo.security.sync.ldap.LDAPNameResolver#resolveDistinguishedName(java.lang.String) */ - public String resolveDistinguishedName(String userId) throws AuthenticationException + public String resolveDistinguishedName(String userId, AuthenticationDiagnostic diagnostic) throws AuthenticationException { + if(logger.isDebugEnabled()) + { + logger.debug("resolveDistinguishedName userId:" + userId); + } SearchControls userSearchCtls = new SearchControls(); userSearchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE); @@ -930,14 +935,19 @@ public class LDAPUserRegistry implements UserRegistry, LDAPNameResolver, Initial { this.userIdAttributeName }); + + String query = this.userSearchBase + "(&" + this.personQuery + + "(" + this.userIdAttributeName + "= userId))"; + InitialDirContext ctx = null; try { - ctx = this.ldapInitialContextFactory.getDefaultIntialDirContext(); + ctx = this.ldapInitialContextFactory.getDefaultIntialDirContext(diagnostic); // Execute the user query with an additional condition that ensures only the user with the required ID is - // returned. Force RFC 2254 escaping of the user ID in the filter to avoid any manipulation + // returned. Force RFC 2254 escaping of the user ID in the filter to avoid any manipulation + NamingEnumeration searchResults = ctx.search(this.userSearchBase, "(&" + this.personQuery + "(" + this.userIdAttributeName + "={0}))", new Object[] { @@ -948,11 +958,22 @@ public class LDAPUserRegistry implements UserRegistry, LDAPNameResolver, Initial { return searchResults.next().getNameInNamespace(); } - throw new AuthenticationException("Failed to resolve user: " + userId); + + Object[] args = {userId, query}; + diagnostic.addStep(AuthenticationDiagnostic.STEP_KEY_LDAP_LOOKUP_USER, false, args); + + throw new AuthenticationException("authentication.err.connection.ldap.user.notfound", args, diagnostic); } catch (NamingException e) { - throw new AlfrescoRuntimeException("Failed to resolve user ID: " + userId, e); + // Connection is good here - AuthenticationException would be thrown by ldapInitialContextFactory + + Object[] args1 = {userId, query}; + diagnostic.addStep(AuthenticationDiagnostic.STEP_KEY_LDAP_SEARCH, false, args1); + + // failed to search + Object[] args = {e.getLocalizedMessage()}; + throw new AuthenticationException("authentication.err.connection.ldap.search", diagnostic, args, e); } finally { @@ -964,6 +985,7 @@ public class LDAPUserRegistry implements UserRegistry, LDAPNameResolver, Initial } catch (NamingException e) { + logger.debug("error when closing ldap context", e); } } } diff --git a/source/java/org/alfresco/repo/template/URLDecodeMethod.java b/source/java/org/alfresco/repo/template/URLDecodeMethod.java new file mode 100644 index 0000000000..42bd9c9ca7 --- /dev/null +++ b/source/java/org/alfresco/repo/template/URLDecodeMethod.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.template; + +import java.util.List; + +import org.springframework.extensions.surf.util.URLDecoder; + +import freemarker.template.TemplateMethodModelEx; +import freemarker.template.TemplateModelException; +import freemarker.template.TemplateScalarModel; + +/** + * @author Kevin Roast + * @since 4.2 + * + * Custom FreeMarker Template language method. + *

+ * Decodes a URL encoded string. The empty string is returned for invalid input values. + *

+ * Usage: urldecode(String s) + */ +public class URLDecodeMethod extends BaseTemplateProcessorExtension implements TemplateMethodModelEx +{ + /** + * @see freemarker.template.TemplateMethodModel#exec(java.util.List) + */ + public Object exec(List args) throws TemplateModelException + { + String result = ""; + + if (args.size() != 0) + { + String s = ""; + Object arg0 = args.get(0); + if (arg0 instanceof TemplateScalarModel) + { + s = ((TemplateScalarModel)arg0).getAsString(); + } + + if (s != null) + { + result = URLDecoder.decode(s); + } + } + + return result; + } +} diff --git a/source/java/org/alfresco/service/cmr/module/ModuleService.java b/source/java/org/alfresco/service/cmr/module/ModuleService.java index 84da4bc1b6..b5e222c976 100644 --- a/source/java/org/alfresco/service/cmr/module/ModuleService.java +++ b/source/java/org/alfresco/service/cmr/module/ModuleService.java @@ -19,6 +19,7 @@ package org.alfresco.service.cmr.module; import java.util.List; +import java.util.Map; import org.alfresco.repo.module.ModuleComponent; @@ -47,6 +48,13 @@ public interface ModuleService */ List getAllModules(); + /** + * Gets a list of the modules missing from the system. + * + * @return module details of the modules missing from the system. + */ + List getMissingModules(); + /** * Register a component of a module for execution. * diff --git a/source/java/org/alfresco/service/cmr/webdav/WebDavService.java b/source/java/org/alfresco/service/cmr/webdav/WebDavService.java index 1119024c0c..3a16788cfe 100644 --- a/source/java/org/alfresco/service/cmr/webdav/WebDavService.java +++ b/source/java/org/alfresco/service/cmr/webdav/WebDavService.java @@ -37,4 +37,10 @@ public interface WebDavService * @return true if activity generation is enabled. */ public boolean activitiesEnabled(); + + /** + * Is the web dav service enabled? + * @return true, is enabled + */ + public boolean getEnabled(); }