From 737c4ade1e13add51df70f7c5b42efbb32eb1b7d Mon Sep 17 00:00:00 2001 From: Derek Hulley Date: Thu, 27 Sep 2007 12:47:37 +0000 Subject: [PATCH] Merged V2.1 to HEAD 6833: Kerberos web filter for the web client. 6834: Kerberos web filter for WebDAV 6835: Updates to CIFS Kerberos logon support. 6836: Fix issue with editing properties of AVM nodes and changed clipboard to use lock aware AVM service 6837: Commented out the unknown opcode reporting as it can quickly fill the log files. AR-1742. 6839: Patch to allow * and ? wildcard characters within a term in any web-client search 6840: Fixed AR-1769: InvalidNameEndingPatch fails when running on 2.1 6841: AR-1761. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@6873 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../servlet/KerberosAuthenticationFilter.java | 908 ++++++++++++++++++ .../org/alfresco/web/bean/SearchContext.java | 97 +- .../web/bean/clipboard/AVMClipboardItem.java | 2 +- .../bean/wcm/EditFilePropertiesDialog.java | 23 +- 4 files changed, 948 insertions(+), 82 deletions(-) create mode 100644 source/java/org/alfresco/web/app/servlet/KerberosAuthenticationFilter.java diff --git a/source/java/org/alfresco/web/app/servlet/KerberosAuthenticationFilter.java b/source/java/org/alfresco/web/app/servlet/KerberosAuthenticationFilter.java new file mode 100644 index 0000000000..7a2116e458 --- /dev/null +++ b/source/java/org/alfresco/web/app/servlet/KerberosAuthenticationFilter.java @@ -0,0 +1,908 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.app.servlet; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.List; +import java.util.Locale; +import java.util.Random; +import java.util.Vector; + +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; +import javax.security.sasl.RealmCallback; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import javax.transaction.UserTransaction; + +import org.alfresco.config.ConfigService; +import org.alfresco.filesys.server.auth.kerberos.KerberosDetails; +import org.alfresco.filesys.server.auth.kerberos.SessionSetupPrivilegedAction; +import org.alfresco.filesys.server.auth.spnego.NegTokenInit; +import org.alfresco.filesys.server.auth.spnego.NegTokenTarg; +import org.alfresco.filesys.server.auth.spnego.OID; +import org.alfresco.filesys.server.auth.spnego.SPNEGO; +import org.alfresco.filesys.server.config.ServerConfiguration; +import org.alfresco.i18n.I18NUtil; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.authentication.AuthenticationException; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.NTLMMode; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.web.app.Application; +import org.alfresco.web.bean.LoginBean; +import org.alfresco.web.bean.repository.User; +import org.alfresco.web.config.LanguagesConfigElement; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.ietf.jgss.Oid; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; + +/** + * Kerberos Authentication Filter Class + * + * @author GKSpencer + */ +public class KerberosAuthenticationFilter extends AbstractAuthenticationFilter implements Filter, CallbackHandler +{ + // Constants + // + // Default login configuration entry name + + private static final String LoginConfigEntry = "AlfrescoHTTP"; + + // Locale object stored in the session + + private static final String LOCALE = "locale"; + public static final String MESSAGE_BUNDLE = "alfresco.messages.webclient"; + + // Debug logging + + private static Log logger = LogFactory.getLog(KerberosAuthenticationFilter.class); + + // Servlet context, required to get authentication service + + private ServletContext m_context; + + // File server configuration + + private ServerConfiguration m_srvConfig; + + // Various services required by the Kerberos authenticator + + private AuthenticationService m_authService; + private AuthenticationComponent m_authComponent; + private PersonService m_personService; + private NodeService m_nodeService; + private TransactionService m_transactionService; + private ConfigService m_configService; + + // Allow guest access + + private boolean m_allowGuest; + + // Login page address + + private String m_loginPage; + + // Random number generator used to generate challenge keys + + private Random m_random = new Random(System.currentTimeMillis()); + + // Local server name, from either the file servers config or DNS host name + + private String m_srvName; + + // List of available locales (from the web-client configuration) + + private List m_languages; + + // Kerberos settings + // + // Account name and password for server ticket + // + // The account name must be built from the HTTP server name, in the format :- + // + // HTTP/@ + + private String m_accountName; + private String m_password; + + // Kerberos realm and KDC address + + private String m_krbRealm; + private String m_krbKDC; + + // Login configuration entry name + + private String m_loginEntryName = LoginConfigEntry; + + // Server login context + + private LoginContext m_loginContext; + + // SPNEGO NegTokenInit blob, sent to the client in the SMB negotiate response + + private byte[] m_negTokenInit; + + /** + * Initialize the filter + * + * @param args FilterConfig + * @exception ServletException + */ + public void init(FilterConfig args) throws ServletException + { + // Save the servlet context, needed to get hold of the authentication service + + m_context = args.getServletContext(); + + // Setup the authentication context + + WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(m_context); + + ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); + m_nodeService = serviceRegistry.getNodeService(); + m_transactionService = serviceRegistry.getTransactionService(); + + m_authService = (AuthenticationService) ctx.getBean("AuthenticationService"); + m_authComponent = (AuthenticationComponent) ctx.getBean("AuthenticationComponent"); + m_personService = (PersonService) ctx.getBean("personService"); + m_configService = (ConfigService) ctx.getBean("webClientConfigService"); + + m_srvConfig = (ServerConfiguration) ctx.getBean(ServerConfiguration.SERVER_CONFIGURATION); + + // Check that the authentication component supports the required mode + + if ( m_authComponent.getNTLMMode() != NTLMMode.MD4_PROVIDER && + m_authComponent.getNTLMMode() != NTLMMode.PASS_THROUGH) + { + throw new ServletException("Required authentication mode not available"); + } + + // Get the local server name, try the file server config first + + if ( m_srvConfig != null) + { + m_srvName = m_srvConfig.getServerName(); + + if ( m_srvName == null) + { + // CIFS server may not be running so the local server name has not been set, generate + // a server name + + m_srvName = m_srvConfig.getLocalServerName(true) + "_A"; + } + } + else + { + // Get the host name + + try + { + // Get the local host name + + m_srvName = InetAddress.getLocalHost().getHostName(); + + // Strip any domain name + + int pos = m_srvName.indexOf("."); + if ( pos != -1) + m_srvName = m_srvName.substring(0, pos - 1); + } + catch (UnknownHostException ex) + { + // Log the error + + if ( logger.isErrorEnabled()) + logger.error("Kerberos filter, error getting local host name", ex); + } + + } + + // Check if the server name is valid + + if ( m_srvName == null || m_srvName.length() == 0) + throw new ServletException("Failed to get local server name"); + + // Get a list of the available locales + + LanguagesConfigElement config = (LanguagesConfigElement) m_configService. + getConfig("Languages").getConfigElement(LanguagesConfigElement.CONFIG_ELEMENT_ID); + + m_languages = config.getLanguages(); + + // Check if Kerberos is enabled, get the Kerberos KDC address + + String kdcAddress = args.getInitParameter("KDC"); + + if (kdcAddress != null && kdcAddress.length() > 0) + { + // Set the Kerberos KDC address + + m_krbKDC = kdcAddress; + + // Get the Kerberos realm + + String krbRealm = args.getInitParameter("Realm"); + if ( krbRealm != null && krbRealm.length() > 0) + { + // Set the Kerberos realm + + m_krbRealm = krbRealm; + } + else + throw new ServletException("Kerberos realm not specified"); + + // Get the CIFS service account password + + String srvPassword = args.getInitParameter("Password"); + if ( srvPassword != null && srvPassword.length() > 0) + { + // Set the CIFS service account password + + m_password = srvPassword; + } + else + throw new ServletException("HTTP service account password not specified"); + + // Get the login configuration entry name + + String loginEntry = args.getInitParameter("LoginEntry"); + + if ( loginEntry != null) + { + if ( loginEntry.length() > 0) + { + // Set the login configuration entry name to use + + m_loginEntryName = loginEntry; + } + else + throw new ServletException("Invalid login entry specified"); + } + + // Get the local host name + + String localName = null; + + try + { + localName = InetAddress.getLocalHost().getCanonicalHostName(); + } + catch ( UnknownHostException ex) + { + throw new ServletException( "Failed to get local host name"); + } + + // Get the server principal name + + String principal = args.getInitParameter( "Principal"); + + if ( principal != null) { + + // Use the supplied principal name to build the account name + + StringBuffer httpAccount = new StringBuffer(); + + httpAccount.append( principal); + httpAccount.append("@"); + httpAccount.append(m_krbRealm); + + m_accountName = httpAccount.toString(); + } + else { + + // Build the HTTP service account name + + StringBuilder httpAccount = new StringBuilder(); + + httpAccount.append("HTTP/"); + httpAccount.append( localName); + httpAccount.append("@"); + httpAccount.append(m_krbRealm); + + m_accountName = httpAccount.toString(); + } + + // Create a login context for the CIFS server service + + try + { + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug( "HTTP Kerberos login using account " + m_accountName); + + // Login the CIFS server service + + m_loginContext = new LoginContext( m_loginEntryName, this); + m_loginContext.login(); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug( "HTTP Kerberos login successful"); + } + catch ( LoginException ex) + { + // Debug + + if ( logger.isErrorEnabled()) + logger.error("HTTP Kerberos web filter error", ex); + + throw new ServletException("Failed to login HTTP server service"); + } + + // Create the Oid list for the SPNEGO NegTokenInit, include NTLMSSP for fallback + + Vector mechTypes = new Vector(); + + mechTypes.add(OID.KERBEROS5); + mechTypes.add(OID.MSKERBEROS5); + + // Build the SPNEGO NegTokenInit blob + + try + { + // Build the mechListMIC principle + // + // Note: This field is not as specified + + String mecListMIC = null; + + StringBuilder mic = new StringBuilder(); + mic.append( localName); + mic.append("$@"); + mic.append( m_krbRealm); + + mecListMIC = mic.toString(); + + // Build the SPNEGO NegTokenInit that contains the authentication types that the HTTP server accepts + + NegTokenInit negTokenInit = new NegTokenInit(mechTypes, mecListMIC); + + // Encode the NegTokenInit blob + + m_negTokenInit = negTokenInit.encode(); + } + catch (IOException ex) + { + // Debug + + if ( logger.isErrorEnabled()) + logger.error("Error creating SPNEGO NegTokenInit blob", ex); + + throw new ServletException("Failed to create SPNEGO NegTokenInit blob"); + } + } + } + + /** + * Run the filter + * + * @param sreq ServletRequest + * @param sresp ServletResponse + * @param chain FilterChain + * @exception IOException + * @exception ServletException + */ + public void doFilter(ServletRequest sreq, ServletResponse sresp, FilterChain chain) throws IOException, + ServletException + { + // Get the HTTP request/response/session + + HttpServletRequest req = (HttpServletRequest) sreq; + HttpServletResponse resp = (HttpServletResponse) sresp; + + HttpSession httpSess = req.getSession(true); + + // Check if there is an authorization header with an SPNEGO security blob + + String authHdr = req.getHeader("Authorization"); + boolean reqAuth = false; + + if ( authHdr != null && authHdr.startsWith("Negotiate")) + reqAuth = true; + + // Check if the user is already authenticated + + User user = (User) httpSess.getAttribute(AuthenticationHelper.AUTHENTICATION_USER); + + if ( user != null && reqAuth == false) + { + try + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("User " + user.getUserName() + " validate ticket"); + + // Validate the user ticket + + m_authService.validate( user.getTicket()); + reqAuth = false; + + // Set the current locale + + I18NUtil.setLocale(Application.getLanguage(httpSess)); + } + catch (AuthenticationException ex) + { + if ( logger.isErrorEnabled()) + logger.error("Failed to validate user " + user.getUserName(), ex); + + reqAuth = true; + } + } + + // If the user has been validated and we do not require re-authentication then continue to + // the next filter + + if ( reqAuth == false && user != null) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Authentication not required, chaining ..."); + + // Chain to the next filter + + chain.doFilter(sreq, sresp); + return; + } + + // Check if the login page is being accessed, do not intercept the login page + + if ( req.getRequestURI().endsWith(getLoginPage()) == true) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Login page requested, chaining ..."); + + // Chain to the next filter + + chain.doFilter( sreq, sresp); + return; + } + + // Check if the browser is Opera, if so then display the login page as Opera does not + // support SPNEGO + + String userAgent = req.getHeader("user-agent"); + + if ( userAgent != null && userAgent.indexOf("Opera ") != -1) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Opera detected, redirecting to login page"); + + // Redirect to the login page + + resp.sendRedirect(req.getContextPath() + "/faces" + getLoginPage()); + return; + } + + // Check the authorization header + + if ( authHdr == null) { + + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("New Kerberos auth request from " + req.getRemoteHost() + " (" + + req.getRemoteAddr() + ":" + req.getRemotePort() + ")"); + + // Send back a request for SPNEGO authentication + + resp.setHeader("WWW-Authenticate", "Negotiate"); + resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + + resp.flushBuffer(); + } + else + { + // Decode the received SPNEGO blob and validate + + final byte[] spnegoByts = Base64.decodeBase64( authHdr.substring(10).getBytes()); + + // Check the received SPNEGO token type + + int tokType = -1; + + try + { + tokType = SPNEGO.checkTokenType( spnegoByts, 0, spnegoByts.length); + } + catch ( IOException ex) + { + } + + // Check for a NegTokenInit blob + + if ( tokType == SPNEGO.NegTokenInit) + { + // Parse the SPNEGO security blob to get the Kerberos ticket + + NegTokenInit negToken = new NegTokenInit(); + + try + { + // Decode the security blob + + negToken.decode( spnegoByts, 0, spnegoByts.length); + + // Determine the authentication mechanism the client is using and logon + + String oidStr = null; + if ( negToken.numberOfOids() > 0) + oidStr = negToken.getOidAt( 0).toString(); + + if ( oidStr != null && (oidStr.equals( OID.ID_MSKERBEROS5) || oidStr.equals(OID.ID_KERBEROS5))) + { + // Kerberos logon + + if ( doKerberosLogon( negToken, req, resp, httpSess) != null) + { + // If the original URL requested was the login page then redirect to the browse view + + if (req.getRequestURI().endsWith(getLoginPage()) == true) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Login page requested, redirecting to browse page"); + + // Redirect to the browse view + + resp.sendRedirect(req.getContextPath() + "/faces/jsp/browse/browse.jsp"); + } + else + { + // Allow the user to access the requested page + + chain.doFilter( req, resp); + } + } + else + { + // Send back a request for SPNEGO authentication + + resp.setHeader("WWW-Authenticate", "Negotiate"); + resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + + resp.flushBuffer(); + } + } + } + catch ( IOException ex) + { + // Log the error + + if ( logger.isDebugEnabled()) + logger.debug(ex); + } + } + else + { + // Unknown SPNEGO token type + + if ( logger.isDebugEnabled()) + logger.debug( "Unknown SPNEGO token type"); + + // Redirect to the login page + + resp.sendRedirect(req.getContextPath() + "/faces" + getLoginPage()); + } + } + } + + /** + * Return the login page address + * + * @return String + */ + private String getLoginPage() + { + if (m_loginPage == null) + { + m_loginPage = Application.getLoginPage(m_context); + } + + return m_loginPage; + } + + /** + * Delete the servlet filter + */ + public void destroy() + { + } + + /** + * JAAS callback handler + * + * @param callbacks Callback[] + * @exception IOException + * @exception UnsupportedCallbackException + */ + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException + { + // Process the callback list + + for (int i = 0; i < callbacks.length; i++) + { + // Request for user name + + if (callbacks[i] instanceof NameCallback) + { + NameCallback cb = (NameCallback) callbacks[i]; + cb.setName(m_accountName); + } + + // Request for password + else if (callbacks[i] instanceof PasswordCallback) + { + PasswordCallback cb = (PasswordCallback) callbacks[i]; + cb.setPassword(m_password.toCharArray()); + } + + // Request for realm + + else if (callbacks[i] instanceof RealmCallback) + { + RealmCallback cb = (RealmCallback) callbacks[i]; + cb.setText(m_krbRealm); + } + else + { + throw new UnsupportedCallbackException(callbacks[i]); + } + } + } + + /** + * Perform a Kerberos login and return an SPNEGO response + * + * @param negToken NegTokenInit + * @param req HttpServletRequest + * @param resp HttpServletResponse + * @param httpSess HttpSession + * @return NegTokenTarg + */ + private final NegTokenTarg doKerberosLogon( NegTokenInit negToken, HttpServletRequest req, HttpServletResponse resp, HttpSession httpSess) + { + // Authenticate the user + + KerberosDetails krbDetails = null; + NegTokenTarg negTokenTarg = null; + + UserTransaction tx = null; + + try + { + // Run the session setup as a privileged action + + SessionSetupPrivilegedAction sessSetupAction = new SessionSetupPrivilegedAction( m_accountName, negToken.getMechtoken()); + Object result = Subject.doAs( m_loginContext.getSubject(), sessSetupAction); + + if ( result != null) + { + // Access the Kerberos response + + krbDetails = (KerberosDetails) result; + + // Create the NegTokenTarg response blob + + negTokenTarg = new NegTokenTarg( SPNEGO.AcceptCompleted, OID.KERBEROS5, krbDetails.getResponseToken()); + + // Check if the user has been authenticated, if so then setup the user environment + + if ( negTokenTarg != null) + { + // Create a read transaction + + tx = m_transactionService.getUserTransaction(); + + NodeRef homeSpaceRef = null; + User user = null; + String userName = null; + + try + { + // Start the transaction + + tx.begin(); + + // Setup User object and Home space ID etc. + + NodeRef personNodeRef = m_personService.getPerson( krbDetails.getUserName()); + + // Use the system user to do the user name lookup + + m_authComponent.setSystemUserAsCurrentUser(); + + // User name should match the uid in the person entry found + + userName = (String) m_nodeService.getProperty(personNodeRef, ContentModel.PROP_USERNAME); + AuthenticationUtil.setCurrentUser( userName); + String currentTicket = m_authService.getCurrentTicket(); + user = new User(userName, currentTicket, personNodeRef); + + homeSpaceRef = (NodeRef) m_nodeService.getProperty( personNodeRef, ContentModel.PROP_HOMEFOLDER); + user.setHomeSpaceId(homeSpaceRef.getId()); + + // Commit + + tx.commit(); + } + catch (Throwable ex) + { + try + { + tx.rollback(); + } + catch (Exception ex2) + { + logger.error("Failed to rollback transaction", ex2); + } + if(ex instanceof RuntimeException) + { + throw (RuntimeException)ex; + } + else if(ex instanceof IOException) + { + throw (IOException)ex; + } + else if(ex instanceof ServletException) + { + throw (ServletException)ex; + } + else + { + throw new RuntimeException("Authentication setup failed", ex); + } + } + + // Store the user + + httpSess.setAttribute(AuthenticationHelper.AUTHENTICATION_USER, user); + httpSess.setAttribute(LoginBean.LOGIN_EXTERNAL_AUTH, Boolean.TRUE); + + // Set the current locale from the Accept-Lanaguage header if available + + Locale userLocale = parseAcceptLanguageHeader(req, m_languages); + + if ( userLocale != null) + { + httpSess.setAttribute(LOCALE, userLocale); + httpSess.removeAttribute(MESSAGE_BUNDLE); + } + + // Set the locale using the session + + I18NUtil.setLocale(Application.getLanguage(httpSess)); + + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("User " + userName + " logged on via Kerberos"); + + } + } + else + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug( "No SPNEGO response, Kerberos logon failed"); + } + } + catch (Exception ex) + { + // Log the error + + if ( logger.isDebugEnabled()) + logger.debug("Kerberos logon error", ex); + } + + // Return the response SPNEGO blob + + return negTokenTarg; + } + + /** + * Map the case insensitive logon name to the internal person object user name + * + * @param userName String + * @return String + */ + protected final String mapUserNameToPerson(String userName) + { + // Get the home folder for the user + + UserTransaction tx = m_transactionService.getUserTransaction(); + String personName = null; + + try + { + tx.begin(); + personName = m_personService.getUserIdentifier( userName); + tx.commit(); + } + catch (Throwable ex) + { + try + { + tx.rollback(); + } + catch (Throwable ex2) + { + logger.error("Failed to rollback transaction", ex2); + } + + // Re-throw the exception + + if (ex instanceof RuntimeException) + { + throw (RuntimeException) ex; + } + else + { + throw new RuntimeException("Error during execution of transaction.", ex); + } + } + + // Return the person name + + return personName; + } +} diff --git a/source/java/org/alfresco/web/bean/SearchContext.java b/source/java/org/alfresco/web/bean/SearchContext.java index 8ee5e11f53..fd81340f23 100644 --- a/source/java/org/alfresco/web/bean/SearchContext.java +++ b/source/java/org/alfresco/web/bean/SearchContext.java @@ -518,44 +518,9 @@ public final class SearchContext implements Serializable { if (value.indexOf(' ') == -1) { - String safeValue; - String prefix = ""; - String suffix = ""; - - // look for a wildcard suffix - if (value.charAt(value.length() - 1) != OP_WILDCARD) - { - // look for wildcard prefix - if (value.charAt(0) != OP_WILDCARD) - { - safeValue = QueryParser.escape(value); - } - else - { - safeValue = QueryParser.escape(value.substring(1)); - prefix = STR_OP_WILDCARD; - } - } - else - { - // found a wildcard suffix - append it again after escaping the other characters - suffix = STR_OP_WILDCARD; - - // look for wildcard prefix - if (value.charAt(0) != OP_WILDCARD) - { - safeValue = QueryParser.escape(value.substring(0, value.length() - 1)); - } - else - { - safeValue = QueryParser.escape(value.substring(1, value.length() - 1)); - prefix = STR_OP_WILDCARD; - } - } - if (andOp) buf.append('+'); buf.append('@').append(Repository.escapeQName(qname)).append(":") - .append(prefix).append(safeValue).append(suffix).append(' '); + .append(SearchContext.escape(value)).append(' '); } else { @@ -577,44 +542,30 @@ public final class SearchContext implements Serializable */ private static void processSearchTextAttribute(String qname, String value, StringBuilder attrBuf, StringBuilder textBuf) { - String safeValue; - String suffix = ""; - String prefix = ""; - - if (value.charAt(value.length() - 1) != OP_WILDCARD) - { - // look for wildcard prefix - if (value.charAt(0) != OP_WILDCARD) - { - safeValue = QueryParser.escape(value); - } - else - { - // found a leading wildcard - prepend it again after escaping the other characters - prefix = STR_OP_WILDCARD; - safeValue = QueryParser.escape(value.substring(1)); - } - } - else - { - suffix = STR_OP_WILDCARD; - - // look for wildcard prefix - if (value.charAt(0) != OP_WILDCARD) - { - safeValue = QueryParser.escape(value.substring(0, value.length() - 1)); - } - else - { - if (value.length() == 1) return; // handle just opcode - prefix = STR_OP_WILDCARD; - safeValue = QueryParser.escape(value.substring(1, value.length() - 1)); - } - } - - textBuf.append("TEXT:").append(prefix).append(safeValue).append(suffix); + textBuf.append("TEXT:").append(SearchContext.escape(value)); attrBuf.append("@").append(qname).append(":") - .append(prefix).append(safeValue).append(suffix); + .append(SearchContext.escape(value)); + } + + /** + * Returns a String where those characters that QueryParser + * expects to be escaped are escaped by a preceding \. + * '*' and '?' are not escaped. + */ + private static String escape(String s) + { + StringBuffer sb = new StringBuffer(s.length() + 4); + for (int i = 0; i < s.length(); i++) + { + char c = s.charAt(i); + if (c == '\\' || c == '+' || c == '-' || c == '!' || c == '(' || c == ')' || c == ':' || + c == '^' || c == '[' || c == ']' || c == '\"' || c == '{' || c == '}' || c == '~') + { + sb.append('\\'); + } + sb.append(c); + } + return sb.toString(); } /** diff --git a/source/java/org/alfresco/web/bean/clipboard/AVMClipboardItem.java b/source/java/org/alfresco/web/bean/clipboard/AVMClipboardItem.java index ac13053cc1..8eaed45c36 100644 --- a/source/java/org/alfresco/web/bean/clipboard/AVMClipboardItem.java +++ b/source/java/org/alfresco/web/bean/clipboard/AVMClipboardItem.java @@ -97,7 +97,7 @@ public class AVMClipboardItem extends AbstractClipboardItem String sourcePath = AVMNodeConverter.ToAVMVersionPath(getNodeRef()).getSecond(); FileFolderService fileFolderService = getServiceRegistry().getFileFolderService(); - AVMService avmService = getServiceRegistry().getAVMService(); + AVMService avmService = getServiceRegistry().getAVMLockingAwareService(); // initial name to attempt the copy of the item with String name = getName(); diff --git a/source/java/org/alfresco/web/bean/wcm/EditFilePropertiesDialog.java b/source/java/org/alfresco/web/bean/wcm/EditFilePropertiesDialog.java index bc0c092b0b..1a5d01c456 100644 --- a/source/java/org/alfresco/web/bean/wcm/EditFilePropertiesDialog.java +++ b/source/java/org/alfresco/web/bean/wcm/EditFilePropertiesDialog.java @@ -28,7 +28,6 @@ import java.io.Serializable; import java.text.MessageFormat; import java.util.HashMap; import java.util.Iterator; -import java.util.List; import java.util.Map; import javax.faces.context.FacesContext; @@ -50,7 +49,9 @@ import org.alfresco.web.app.Application; import org.alfresco.web.bean.content.EditContentPropertiesDialog; import org.alfresco.web.bean.repository.Node; import org.alfresco.web.bean.repository.Repository; -import org.alfresco.web.forms.*; +import org.alfresco.web.forms.FormInstanceData; +import org.alfresco.web.forms.FormsService; +import org.alfresco.web.forms.Rendition; import org.alfresco.web.ui.common.Utils; /** @@ -186,8 +187,12 @@ public class EditFilePropertiesDialog extends EditContentPropertiesDialog // send the properties back to the repository this.avmService.setNodeProperties(AVMNodeConverter.ToAVMVersionPath(nodeRef).getSecond(), avmProps); - // perform the rename last as for an AVM it changes the NodeRef - if (name != null) + // perform the rename last as for an AVM it changes the NodeRef, but only if the name has changed! + String path = AVMNodeConverter.ToAVMVersionPath(nodeRef).getSecond(); + final String parentPath = AVMNodeConverter.SplitBase(path)[0]; + final String oldName = AVMNodeConverter.SplitBase(path)[1]; + + if (name != null && name.equals(oldName) == false) { if (this.nodeService.hasAspect(nodeRef, WCMAppModel.ASPECT_RENDITION)) { @@ -209,12 +214,8 @@ public class EditFilePropertiesDialog extends EditContentPropertiesDialog this.nodeService.removeProperty(nodeRef, WCMAppModel.PROP_RENDITIONS); } - String path = AVMNodeConverter.ToAVMVersionPath(nodeRef).getSecond(); - final String parentPath = AVMNodeConverter.SplitBase(path)[0]; - final String oldName = AVMNodeConverter.SplitBase(path)[1]; this.avmService.rename(parentPath, oldName, parentPath, name); nodeRef = AVMNodeConverter.ToNodeRef(-1, AVMNodeConverter.ExtendAVMPath(parentPath, name)); - editedProps.put(ContentModel.PROP_NAME.toString(), name); if (this.nodeService.hasAspect(nodeRef, WCMAppModel.ASPECT_FORM_INSTANCE_DATA)) { @@ -232,6 +233,12 @@ public class EditFilePropertiesDialog extends EditContentPropertiesDialog } } + // add the name property back to the properties map + if (name != null) + { + editedProps.put(ContentModel.PROP_NAME.toString(), name); + } + return outcome; }