diff --git a/source/java/org/alfresco/web/app/Application.java b/source/java/org/alfresco/web/app/Application.java index 3c37eb8724..bbff14743e 100644 --- a/source/java/org/alfresco/web/app/Application.java +++ b/source/java/org/alfresco/web/app/Application.java @@ -28,12 +28,15 @@ import java.util.ResourceBundle; import javax.faces.context.FacesContext; import javax.servlet.ServletContext; import javax.servlet.ServletException; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; +import org.alfresco.repo.SessionUser; import org.alfresco.repo.importer.ImporterBootstrap; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.security.AuthenticationService; import org.alfresco.web.app.portlet.AlfrescoFacesPortlet; import org.alfresco.web.app.servlet.AuthenticationHelper; import org.alfresco.web.app.servlet.FacesHelper; @@ -69,6 +72,7 @@ public class Application public static final String BEAN_CONFIG_SERVICE = "webClientConfigService"; public static final String BEAN_DATA_DICTIONARY = "dataDictionary"; public static final String BEAN_IMPORTER_BOOTSTRAP = "spacesBootstrap"; + private static final String BEAN_UNPROTECTED_AUTH_SERVICE = "authenticationService"; public static final String MESSAGE_BUNDLE = "alfresco.messages.webclient"; @@ -289,6 +293,60 @@ public class Application } } + /** + * Invalidate Alfresco ticket and Web/Portlet session and clear the Security context for this thread. + * @param context + */ + public static void logOut(FacesContext context) + { + String ticket = null; + if (Application.inPortalServer()) + { + ticket = AlfrescoFacesPortlet.onLogOut(context.getExternalContext().getRequest()); + } + else + { + SessionUser user = getCurrentUser(context); + if (user != null) + { + ticket = user.getTicket(); + } + HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest(); + HttpSession session = request.getSession(false); + if (session != null) + { + session.invalidate(); + } + + // remove the username cookie value + Cookie authCookie = AuthenticationHelper.getAuthCookie(request); + if (authCookie != null) + { + HttpServletResponse response = (HttpServletResponse) context.getExternalContext().getResponse(); + if (response.isCommitted()) + { + // It's too late to do it now, but we can ask the login page to do it + request.getSession().setAttribute(AuthenticationHelper.SESSION_INVALIDATED, true); + } + else + { + authCookie.setMaxAge(0); + response.addCookie(authCookie); + } + } + } + + // Explicitly invalidate the Alfresco ticket. This no longer happens on session expiry to allow for ticket + // 'sharing' + WebApplicationContext wc = FacesContextUtils.getRequiredWebApplicationContext(context); + AuthenticationService unprotAuthService = (AuthenticationService) wc.getBean(BEAN_UNPROTECTED_AUTH_SERVICE); + if (ticket != null) + { + unprotAuthService.invalidateTicket(ticket); + } + unprotAuthService.clearCurrentSecurityContext(); + } + /** * @return Returns the repository store URL (retrieved from config service) */ diff --git a/source/java/org/alfresco/web/app/ContextListener.java b/source/java/org/alfresco/web/app/ContextListener.java index 1cda879b13..69763287bc 100644 --- a/source/java/org/alfresco/web/app/ContextListener.java +++ b/source/java/org/alfresco/web/app/ContextListener.java @@ -18,8 +18,6 @@ */ package org.alfresco.web.app; -import java.util.Enumeration; - import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; @@ -28,7 +26,6 @@ import javax.servlet.http.HttpSessionListener; import javax.transaction.UserTransaction; import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.SessionUser; import org.alfresco.repo.cache.InternalEhCacheManagerFactoryBean; import org.alfresco.repo.security.authentication.AuthenticationContext; import org.alfresco.service.ServiceRegistry; @@ -36,10 +33,8 @@ import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.cmr.security.AuthenticationService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.transaction.TransactionService; -import org.alfresco.web.app.servlet.AuthenticationHelper; import org.alfresco.web.bean.repository.Repository; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -156,16 +151,5 @@ public class ContextListener implements ServletContextListener, HttpSessionListe { if (logger.isDebugEnabled()) logger.debug("HTTP session destroyed: " + event.getSession().getId()); - - SessionUser user = (SessionUser)event.getSession().getAttribute(AuthenticationHelper.AUTHENTICATION_USER); - if (user != null) - { - // invalidate ticket and clear the Security context for this thread - WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext); - AuthenticationService authService = (AuthenticationService)ctx.getBean("authenticationService"); - authService.invalidateTicket(user.getTicket(), event.getSession().getId()); - authService.clearCurrentSecurityContext(); - event.getSession().removeAttribute(AuthenticationHelper.AUTHENTICATION_USER); - } } } diff --git a/source/java/org/alfresco/web/app/portlet/AlfrescoFacesPortlet.java b/source/java/org/alfresco/web/app/portlet/AlfrescoFacesPortlet.java index c6758f1862..d7ce71c168 100644 --- a/source/java/org/alfresco/web/app/portlet/AlfrescoFacesPortlet.java +++ b/source/java/org/alfresco/web/app/portlet/AlfrescoFacesPortlet.java @@ -20,6 +20,7 @@ package org.alfresco.web.app.portlet; import java.io.File; import java.io.IOException; +import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Locale; @@ -179,7 +180,7 @@ public class AlfrescoFacesPortlet extends MyFacesGenericPortlet WebApplicationContext ctx = (WebApplicationContext)getPortletContext().getAttribute( WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); AuthenticationService auth = (AuthenticationService)ctx.getBean("AuthenticationService"); - auth.validate(user.getTicket(), null); + auth.validate(user.getTicket()); // save last username into portlet preferences, get from LoginBean state LoginBean loginBean = (LoginBean)request.getPortletSession().getAttribute(AuthenticationHelper.LOGIN_BEAN); @@ -361,7 +362,7 @@ public class AlfrescoFacesPortlet extends MyFacesGenericPortlet logger.debug("Validating ticket: " + user.getTicket()); // setup the authentication context - auth.validate(user.getTicket(), null); + auth.validate(user.getTicket()); } // do the normal JSF processing @@ -580,6 +581,38 @@ public class AlfrescoFacesPortlet extends MyFacesGenericPortlet return FacesHelper.getFacesContext(portletReq, portletRes, portletConfig.getPortletContext()); } + public static String onLogOut(Object req) + { + PortletRequest portletReq = null; + if (req instanceof ServletRequest) + { + portletReq = (PortletRequest) ((ServletRequest) req).getAttribute("javax.portlet.request"); + } + else if (req instanceof PortletRequest) + { + portletReq = (PortletRequest) req; + } + + if (portletReq == null) + { + return null; + } + + // remove all objects from our session by hand + // we do this as invalidating the Portal session would invalidate all other portlets! + PortletSession session = portletReq.getPortletSession(); + SessionUser user = (SessionUser) session.getAttribute(AuthenticationHelper.AUTHENTICATION_USER, + PortletSession.APPLICATION_SCOPE); + Enumeration i = session.getAttributeNames(); + while (i.hasMoreElements()) + { + session.removeAttribute(i.nextElement()); + } + session.setAttribute(AuthenticationHelper.SESSION_INVALIDATED, true); + + return user == null ? null : user.getTicket(); + } + /** * Handles errors that occur during a render request */ @@ -641,7 +674,7 @@ public class AlfrescoFacesPortlet extends MyFacesGenericPortlet */ private static User portalGuestAuthenticate(WebApplicationContext ctx, PortletSession session, AuthenticationService auth) { - User user = AuthenticationHelper.portalGuestAuthenticate(ctx, session.getId(), auth); + User user = AuthenticationHelper.portalGuestAuthenticate(ctx, auth); if (user != null) { diff --git a/source/java/org/alfresco/web/app/servlet/AuthenticationHelper.java b/source/java/org/alfresco/web/app/servlet/AuthenticationHelper.java index 9736628814..bc325f2f64 100644 --- a/source/java/org/alfresco/web/app/servlet/AuthenticationHelper.java +++ b/source/java/org/alfresco/web/app/servlet/AuthenticationHelper.java @@ -204,7 +204,7 @@ public final class AuthenticationHelper auth.authenticateAsGuest(); // if we get here then Guest access was allowed and successful - setUser(sc, req, AuthenticationUtil.getGuestUserName(), auth.getCurrentTicket(session.getId()), false); + setUser(sc, req, AuthenticationUtil.getGuestUserName(), auth.getCurrentTicket(), false); // Set up the thread context setupThread(sc, req, res); @@ -223,8 +223,7 @@ public final class AuthenticationHelper { // Guest is unable to access either properties on Person AuthenticationService unprotAuthService = (AuthenticationService)wc.getBean(UNPROTECTED_AUTH_SERVICE); - String sessionId = session.getId(); - unprotAuthService.invalidateTicket(unprotAuthService.getCurrentTicket(sessionId), sessionId); + unprotAuthService.invalidateTicket(unprotAuthService.getCurrentTicket()); unprotAuthService.clearCurrentSecurityContext(); logger.warn("Unable to login as Guest: " + accessError.getMessage()); } @@ -232,8 +231,7 @@ public final class AuthenticationHelper { // Some other kind of serious failure to report AuthenticationService unprotAuthService = (AuthenticationService)wc.getBean(UNPROTECTED_AUTH_SERVICE); - String sessionId = session.getId(); - unprotAuthService.invalidateTicket(unprotAuthService.getCurrentTicket(sessionId), sessionId); + unprotAuthService.invalidateTicket(unprotAuthService.getCurrentTicket()); unprotAuthService.clearCurrentSecurityContext(); throw new AlfrescoRuntimeException("Failed to authenticate as Guest user.", e); } @@ -288,7 +286,7 @@ public final class AuthenticationHelper } // Validate the ticket and associate it with the session - auth.validate(ticket, session.getId()); + auth.validate(ticket); // Cache a new user in the session if required if (user == null) @@ -309,8 +307,7 @@ public final class AuthenticationHelper { // Some other kind of serious failure AuthenticationService unprotAuthService = (AuthenticationService)wc.getBean(UNPROTECTED_AUTH_SERVICE); - String sessionId = session.getId(); - unprotAuthService.invalidateTicket(unprotAuthService.getCurrentTicket(sessionId), sessionId); + unprotAuthService.invalidateTicket(unprotAuthService.getCurrentTicket()); unprotAuthService.clearCurrentSecurityContext(); return AuthenticationStatus.Failure; } @@ -414,13 +411,13 @@ public final class AuthenticationHelper * @param auth * AuthenticationService */ - public static User portalGuestAuthenticate(WebApplicationContext ctx, String sessionId, AuthenticationService auth) + public static User portalGuestAuthenticate(WebApplicationContext ctx, AuthenticationService auth) { try { auth.authenticateAsGuest(); - return createUser(ctx, AuthenticationUtil.getGuestUserName(), auth.getCurrentTicket(sessionId)); + return createUser(ctx, AuthenticationUtil.getGuestUserName(), auth.getCurrentTicket()); } catch (AuthenticationException guestError) { @@ -430,7 +427,7 @@ public final class AuthenticationHelper { // Guest is unable to access either properties on Person AuthenticationService unprotAuthService = (AuthenticationService) ctx.getBean(UNPROTECTED_AUTH_SERVICE); - unprotAuthService.invalidateTicket(unprotAuthService.getCurrentTicket(sessionId), sessionId); + unprotAuthService.invalidateTicket(unprotAuthService.getCurrentTicket()); unprotAuthService.clearCurrentSecurityContext(); logger.warn("Unable to login as Guest: " + accessError.getMessage()); } @@ -438,7 +435,7 @@ public final class AuthenticationHelper { // Some other kind of serious failure to report AuthenticationService unprotAuthService = (AuthenticationService) ctx.getBean(UNPROTECTED_AUTH_SERVICE); - unprotAuthService.invalidateTicket(unprotAuthService.getCurrentTicket(sessionId), sessionId); + unprotAuthService.invalidateTicket(unprotAuthService.getCurrentTicket()); unprotAuthService.clearCurrentSecurityContext(); throw new AlfrescoRuntimeException("Failed to authenticate as Guest user.", e); } @@ -481,7 +478,7 @@ public final class AuthenticationHelper AuthenticationService auth = (AuthenticationService) wc.getBean(AUTHENTICATION_SERVICE); try { - auth.validate(sessionUser.getTicket(), session.getId()); + auth.validate(sessionUser.getTicket()); if (sessionUser instanceof User) { user = (User)sessionUser; @@ -523,8 +520,7 @@ public final class AuthenticationHelper .getBean(AUTHENTICATION_COMPONENT); authenticationComponent.setCurrentUser(userId); AuthenticationService authenticationService = (AuthenticationService) wc.getBean(AUTHENTICATION_SERVICE); - session = httpRequest.getSession(); - user = setUser(sc, httpRequest, userId, authenticationService.getCurrentTicket(session.getId()), true); + user = setUser(sc, httpRequest, userId, authenticationService.getCurrentTicket(), true); } } return user; diff --git a/source/java/org/alfresco/web/app/servlet/ExternalAccessServlet.java b/source/java/org/alfresco/web/app/servlet/ExternalAccessServlet.java index f297ee26a4..4357961795 100644 --- a/source/java/org/alfresco/web/app/servlet/ExternalAccessServlet.java +++ b/source/java/org/alfresco/web/app/servlet/ExternalAccessServlet.java @@ -327,7 +327,8 @@ public class ExternalAccessServlet extends BaseServlet else if (OUTCOME_LOGOUT.equals(outcome)) { // special case for logout - req.getSession().invalidate(); + // invalidate ticket and clear the Security context for this thread + Application.logOut(fc); res.sendRedirect(req.getContextPath() + FACES_SERVLET + Application.getLoginPage(getServletContext())); return; } diff --git a/source/java/org/alfresco/web/app/servlet/HTTPRequestAuthenticationFilter.java b/source/java/org/alfresco/web/app/servlet/HTTPRequestAuthenticationFilter.java index 3dff1f3a93..14422b9aaa 100644 --- a/source/java/org/alfresco/web/app/servlet/HTTPRequestAuthenticationFilter.java +++ b/source/java/org/alfresco/web/app/servlet/HTTPRequestAuthenticationFilter.java @@ -230,8 +230,7 @@ public class HTTPRequestAuthenticationFilter implements Filter authComponent.setCurrentUser(userName); // Set up the user information - AuthenticationHelper.setUser(context, req, userName, authenticationService.getCurrentTicket(req.getSession() - .getId()), true); + AuthenticationHelper.setUser(context, req, userName, authenticationService.getCurrentTicket(), true); // Set the locale using the session AuthenticationHelper.setupThread(this.context, req, res); diff --git a/source/java/org/alfresco/web/bean/LoginBean.java b/source/java/org/alfresco/web/bean/LoginBean.java index 41bee76088..9765b324f1 100644 --- a/source/java/org/alfresco/web/bean/LoginBean.java +++ b/source/java/org/alfresco/web/bean/LoginBean.java @@ -28,11 +28,8 @@ import javax.faces.application.FacesMessage; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.validator.ValidatorException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; import org.alfresco.model.ContentModel; -import org.alfresco.repo.SessionUser; import org.alfresco.repo.security.authentication.AuthenticationDisallowedException; import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.security.authentication.AuthenticationMaxUsersException; @@ -295,19 +292,10 @@ public class LoginBean implements Serializable // remove the session invalidated flag (used to remove last username cookie by AuthenticationFilter) session.remove(AuthenticationHelper.SESSION_INVALIDATED); - // Try to make an association between the session ID and the ticket ID (if not possible here, it will - // happen during first pass through security filters) - String sessionId = null; - Object httpSession = fc.getExternalContext().getSession(false); - if (httpSession != null && httpSession instanceof HttpSession) - { - sessionId = ((HttpSession) httpSession).getId(); - } - // setup User object and Home space ID User user = new User( this.username, - this.getAuthenticationService().getCurrentTicket(sessionId), + this.getAuthenticationService().getCurrentTicket(), getPersonService().getPerson(this.username)); NodeRef homeSpaceRef = (NodeRef) this.getNodeService().getProperty(getPersonService().getPerson(this.username), ContentModel.PROP_HOMEFOLDER); @@ -417,37 +405,8 @@ public class LoginBean implements Serializable Locale language = Application.getLanguage(context); - // Invalidate Session for this user. - if (Application.inPortalServer() == false) - { - // This causes the sessionDestroyed() event to be processed by ContextListener - // which is responsible for invalidating the ticket and clearing the security context - HttpServletRequest request = (HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest(); - request.getSession().invalidate(); - } - else - { - Map session = context.getExternalContext().getSessionMap(); - SessionUser user = Application.getCurrentUser(context); - if (user != null) - { - // invalidate ticket and clear the Security context for this thread - getAuthenticationService().invalidateTicket(user.getTicket(), null); - getAuthenticationService().clearCurrentSecurityContext(); - } - // remove all objects from our session by hand - // we do this as invalidating the Portal session would invalidate all other portlets! - for (Object key : session.keySet()) - { - session.remove(key); - } - } - - // Request that the username cookie state is removed - this is not - // possible from JSF - so instead we setup a session variable - // which will be detected by the login.jsp/Portlet as appropriate. - Map session = context.getExternalContext().getSessionMap(); - session.put(AuthenticationHelper.SESSION_INVALIDATED, true); + // Perform log out actions + Application.logOut(context); // set language to last used on the login page Application.setLanguage(context, language.toString()); diff --git a/source/java/org/alfresco/web/forms/xforms/Schema2XForms.java b/source/java/org/alfresco/web/forms/xforms/Schema2XForms.java index e3d80434f2..37a4a00507 100644 --- a/source/java/org/alfresco/web/forms/xforms/Schema2XForms.java +++ b/source/java/org/alfresco/web/forms/xforms/Schema2XForms.java @@ -71,8 +71,10 @@ import org.w3c.dom.Attr; import org.w3c.dom.Comment; import org.w3c.dom.Document; import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import org.w3c.dom.Text; /** * An abstract implementation of the Schema2XForms interface allowing @@ -234,7 +236,7 @@ public class Schema2XForms implements Serializable { continue; } - final String prefix = this.addNamespace(xformsDocument.getDocumentElement(), + final String prefix = addNamespace(xformsDocument.getDocumentElement(), schemaDocument.lookupPrefix(schemaNamespaces.item(i)), schemaNamespaces.item(i)); if (LOGGER.isDebugEnabled()) @@ -273,7 +275,7 @@ public class Schema2XForms implements Serializable this.setXFormsId(instanceElement); final Element defaultInstanceDocumentElement = xformsDocument.createElement(rootElementName); - this.addNamespace(defaultInstanceDocumentElement, + addNamespace(defaultInstanceDocumentElement, NamespaceConstants.XMLSCHEMA_INSTANCE_PREFIX, NamespaceConstants.XMLSCHEMA_INSTANCE_NS); if (this.targetNamespace != null) @@ -287,21 +289,21 @@ public class Schema2XForms implements Serializable " to xform and default instance element"); } - this.addNamespace(defaultInstanceDocumentElement, + addNamespace(defaultInstanceDocumentElement, targetNamespacePrefix, this.targetNamespace); - this.addNamespace(xformsDocument.getDocumentElement(), + addNamespace(xformsDocument.getDocumentElement(), targetNamespacePrefix, this.targetNamespace); } - Element importedInstanceDocumentElement = null; + Element prototypeInstanceElement = null; if (instanceDocument == null || instanceDocument.getDocumentElement() == null) { instanceElement.appendChild(defaultInstanceDocumentElement); } else - { + { Element instanceDocumentElement = instanceDocument.getDocumentElement(); if (!instanceDocumentElement.getNodeName().equals(rootElementName)) { @@ -312,16 +314,8 @@ public class Schema2XForms implements Serializable if (LOGGER.isDebugEnabled()) LOGGER.debug("[buildXForm] importing rootElement from other document"); - - importedInstanceDocumentElement = (Element) - xformsDocument.importNode(instanceDocumentElement, true); - //add XMLSchema instance NS - this.addNamespace(importedInstanceDocumentElement, - NamespaceConstants.XMLSCHEMA_INSTANCE_PREFIX, - NamespaceConstants.XMLSCHEMA_INSTANCE_NS); - instanceElement.appendChild(importedInstanceDocumentElement); - final Element prototypeInstanceElement = + prototypeInstanceElement = xformsDocument.createElementNS(NamespaceConstants.XFORMS_NS, NamespaceConstants.XFORMS_PREFIX + ":instance"); modelSection.appendChild(prototypeInstanceElement); @@ -349,18 +343,10 @@ public class Schema2XForms implements Serializable } this.setXFormsId(rootGroup, "alfresco-xforms-root-group"); - if (importedInstanceDocumentElement != null) + if (prototypeInstanceElement != null) { - Schema2XForms.removeRemovedNodes(importedInstanceDocumentElement, - defaultInstanceDocumentElement, - schemaNamespacesMap); - Schema2XForms.insertUpdatedNodes(importedInstanceDocumentElement, - defaultInstanceDocumentElement, - schemaNamespacesMap); - Schema2XForms.insertPrototypeNodes(importedInstanceDocumentElement, - defaultInstanceDocumentElement, - schemaNamespacesMap); - + Schema2XForms.rebuildInstance(prototypeInstanceElement, instanceDocument, + instanceElement, schemaNamespacesMap); } this.createSubmitElements(xformsDocument, modelSection, rootGroup); @@ -388,364 +374,179 @@ public class Schema2XForms implements Serializable this.counter.clear(); } - /** - * Inserts nodes that exist in the prototype document that are absent in the imported instance. - * This is to handle the case where a schema has been updated since the last time the - * imported document was modified. - * - * @param instanceDocumentElement the user provided instance document - * @param prototypeInstanceElement the generated prototype instance document - * @param schemaNamespaces the namespaces used by the instance document needed for - * initializing the xpath context. - */ @SuppressWarnings("unchecked") - public static void insertUpdatedNodes(final Element instanceDocumentElement, - final Element prototypeDocumentElement, - final HashMap schemaNamespaces) + public static void rebuildInstance(final Node prototypeNode, final Node oldInstanceNode, + final Node newInstanceNode, + + final HashMap schemaNamespaces) { - if (LOGGER.isDebugEnabled()) - LOGGER.debug("[insertUpdatedNodes] updating imported instance document"); - - final JXPathContext prototypeContext = - JXPathContext.newContext(prototypeDocumentElement); - prototypeContext.registerNamespace(NamespaceService.ALFRESCO_PREFIX, - NamespaceService.ALFRESCO_URI); - final JXPathContext instanceContext = - JXPathContext.newContext(instanceDocumentElement); - instanceContext.registerNamespace(NamespaceService.ALFRESCO_PREFIX, - NamespaceService.ALFRESCO_URI); + final JXPathContext prototypeContext = JXPathContext.newContext(prototypeNode); + prototypeContext.registerNamespace(NamespaceService.ALFRESCO_PREFIX, NamespaceService.ALFRESCO_URI); + final JXPathContext instanceContext = JXPathContext.newContext(oldInstanceNode); + instanceContext.registerNamespace(NamespaceService.ALFRESCO_PREFIX, NamespaceService.ALFRESCO_URI); - // identify all non prototype elements in the prototypeDocument for (final String prefix : schemaNamespaces.keySet()) { prototypeContext.registerNamespace(prefix, schemaNamespaces.get(prefix)); instanceContext.registerNamespace(prefix, schemaNamespaces.get(prefix)); } - final Iterator it = - prototypeContext.iteratePointers("//*[not(@" + NamespaceService.ALFRESCO_PREFIX + - ":prototype='true')] | //@*[name()!='" + NamespaceService.ALFRESCO_PREFIX + - ":prototype']"); + // Evaluate non-recursive XPaths for all prototype elements at this level + final Iterator it = prototypeContext.iteratePointers("*"); while (it.hasNext()) { - final Pointer p = (Pointer)it.next(); - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("[insertUpdatedNodes] evaluating prototype node " + p.asPath() + - " normalized " + p.asPath().replaceAll("\\[\\d+\\]", "")); - } + final Pointer p = it.next(); + Element proto = (Element) p.getNode(); + String path = p.asPath(); + // check if this is a prototype element with the attribute set + boolean isPrototype = proto.hasAttributeNS(NamespaceService.ALFRESCO_URI, "prototype") + && proto.getAttributeNS(NamespaceService.ALFRESCO_URI, "prototype").equals("true"); - String path = p.asPath().replaceAll("\\[\\d+\\]", ""); - if (path.lastIndexOf("/") == 0) + // We shouldn't locate a repeatable child with a fixed path + if (isPrototype) { - if (instanceContext.selectNodes(path).size() == 0) + path = path.replaceAll("\\[(\\d+)\\]", "[position() >= $1]"); + if (LOGGER.isDebugEnabled()) { - if (LOGGER.isDebugEnabled()) - LOGGER.debug("[insertUpdatedNodes] copying " + path + " into imported instance"); - - // remove child elements - we want attributes but don't want to - // copy any potential prototyp nodes - final Node clone = ((Node)p.getNode()).cloneNode(true); - if (clone instanceof Attr) - { - instanceDocumentElement.setAttributeNode((Attr)clone); - } - else - { - final NodeList children = clone.getChildNodes(); - for (int i = 0; i < children.getLength(); i++) - { - if (children.item(i) instanceof Element) - { - clone.removeChild(children.item(i)); - } - } - instanceDocumentElement.appendChild(clone); - } + LOGGER.debug("[rebuildInstance] evaluating prototyped nodes " + path); } } else { - // change path /foo/bar into /foo[not(child::bar)] - if (path.indexOf("@") >= 0) - { - path = path.replaceAll("\\/(@.+)$", "[not($1)]"); - } - else - { - path = path.replaceAll("\\/([^/]+)$", "[not(child::$1)]"); - } - final List l = (List)instanceContext.selectNodes(path); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("[insertUpdatedNodes] appending node " + ((Node)p.getNode()).getNodeName() + - " to the " + l.size() + " selected nodes matching path " + path); - } - - for (Node n : l) - { - // remove child elements - we want attributes but don't want to - // copy any potential prototyp nodes - final Node clone = ((Node)p.getNode()).cloneNode(true); - if (clone instanceof Attr) - { - ((Element)n).setAttributeNode((Attr)clone); - } - else - { - final NodeList children = clone.getChildNodes(); - for (int i = 0; i < children.getLength(); i++) - { - if (children.item(i) instanceof Element) - { - clone.removeChild(children.item(i)); - } - } - n.appendChild(clone); - } - } + LOGGER.debug("[rebuildInstance] evaluating child node with positional path " + path); + } } - } - } - @SuppressWarnings("unchecked") - public static void removeRemovedNodes(final Element instanceDocumentElement, final Element prototypeDocumentElement, - final HashMap schemaNamespaces) - { - if (LOGGER.isDebugEnabled()) - LOGGER.debug("[removeRemovedNodes] updating imported instance document"); + Document newInstanceDocument = newInstanceNode.getOwnerDocument(); - final JXPathContext prototypeContext = JXPathContext.newContext(prototypeDocumentElement); - prototypeContext.registerNamespace(NamespaceService.ALFRESCO_PREFIX, NamespaceService.ALFRESCO_URI); - final JXPathContext instanceContext = JXPathContext.newContext(instanceDocumentElement); - instanceContext.registerNamespace(NamespaceService.ALFRESCO_PREFIX, NamespaceService.ALFRESCO_URI); - - for (final String prefix : schemaNamespaces.keySet()) - { - prototypeContext.registerNamespace(prefix, schemaNamespaces.get(prefix)); - instanceContext.registerNamespace(prefix, schemaNamespaces.get(prefix)); - } - - // Check all elements and attributes in the instance document - OUTER: for (;;) - { - final Iterator it = instanceContext.iteratePointers("//* | //@*"); - while (it.hasNext()) + // Locate the corresponding nodes in the instance document + List l = (List) instanceContext.selectNodes(path); + + // If the prototype node isn't a prototype element, copy it in as a missing node, complete with all its children. We won't need to recurse on this node + if (l.isEmpty()) { - final Pointer p = it.next(); - String path = p.asPath().replaceAll("\\[\\d+\\]", ""); - if (LOGGER.isDebugEnabled()) + if (!isPrototype) { - LOGGER.debug("[removeRemovedNodes] evaluating instance node " + p.asPath() + " normalized " - + path + " in prototype document"); - } + LOGGER.debug("[rebuildInstance] copying in missing node " + proto.getNodeName() + " to " + + XMLUtil.buildXPath(newInstanceNode, newInstanceDocument.getDocumentElement())); - final List l = (List) prototypeContext.selectNodes(path); - if (l.isEmpty()) - { - final Node node = (Node) p.getNode(); - if (LOGGER.isDebugEnabled()) + // Clone the prototype node and all its children + Element clone = (Element)proto.cloneNode(true); + newInstanceNode.appendChild(clone); + + if (oldInstanceNode instanceof Document) { - LOGGER.debug("[removeRemovedNodes] removing instance node " + node.getNodeName() +" with no prototype nodes matching path " + path); - } - if (node instanceof Attr) - { - ((Attr) node).getOwnerElement().removeAttributeNode((Attr) node); - } - else - { - node.getParentNode().removeChild(node); - } - - // We have removed a node and potentially an entire sub-tree of paths. Restart the search - continue OUTER; + // add XMLSchema instance NS + addNamespace(clone, NamespaceConstants.XMLSCHEMA_INSTANCE_PREFIX, + NamespaceConstants.XMLSCHEMA_INSTANCE_NS); + } } } - // We completed the search - break OUTER; - } - - } + else + { + // Otherwise, append the matches from the old instance document in order + for (Node old : l) + { + Element oldEl = (Element)old; - /** - * Inserts prototype nodes into the provided instance document by aggregating insertion - * points from the generated prototype instance docment. - * - * @param instanceDocumentElement the user provided instance document - * @param prototypeInstanceElement the generated prototype instance document - * @param schemaNamespaces the namespaces used by the instance document needed for - * initializing the xpath context. - */ - @SuppressWarnings("unchecked") - public static void insertPrototypeNodes(final Element instanceDocumentElement, - final Element prototypeDocumentElement, - final HashMap schemaNamespaces) - { - final JXPathContext prototypeContext = - JXPathContext.newContext(prototypeDocumentElement); - prototypeContext.registerNamespace(NamespaceService.ALFRESCO_PREFIX, - NamespaceService.ALFRESCO_URI); - final JXPathContext instanceContext = - JXPathContext.newContext(instanceDocumentElement); - instanceContext.registerNamespace(NamespaceService.ALFRESCO_PREFIX, - NamespaceService.ALFRESCO_URI); - for (final String prefix : schemaNamespaces.keySet()) - { - prototypeContext.registerNamespace(prefix, schemaNamespaces.get(prefix)); - instanceContext.registerNamespace(prefix, schemaNamespaces.get(prefix)); - } + // Copy the old instance element rather than cloning it, so we don't copy over attributes + Element clone = newInstanceDocument.createElementNS(oldEl.getNamespaceURI(), oldEl.getTagName()); + newInstanceNode.appendChild(clone); + + if (oldInstanceNode instanceof Document) + { + // add XMLSchema instance NS + addNamespace(clone, NamespaceConstants.XMLSCHEMA_INSTANCE_PREFIX, + NamespaceConstants.XMLSCHEMA_INSTANCE_NS); + } + + // Copy over child text if this is not a complex type + boolean isEmpty = true; + for (Node n = old.getFirstChild(); n != null; n = n.getNextSibling()) + { + if (n instanceof Text) + { + clone.appendChild(newInstanceDocument.importNode(n, false)); + isEmpty = false; + } + else if (n instanceof Element) + { + break; + } + } + + // Check the nil attribute + if (oldEl.getAttributeNS(NamespaceConstants.XMLSCHEMA_INSTANCE_NS, "nil").equals("true")) + { + clone.setAttributeNS(NamespaceConstants.XMLSCHEMA_INSTANCE_NS, + NamespaceConstants.XMLSCHEMA_INSTANCE_PREFIX + ":nil", "true"); + } + + // Copy over attributes present in the prototype + NamedNodeMap attributes = proto.getAttributes(); + for (int i = 0; i < attributes.getLength(); i++) + { + Attr attribute = (Attr) attributes.item(i); + String localName = attribute.getLocalName(); + if (localName == null) + { + String name = attribute.getName(); + if (oldEl.hasAttribute(name)) + { + clone.setAttributeNode((Attr) newInstanceDocument.importNode(oldEl.getAttributeNode(name), + false)); + } + else + { + LOGGER.debug("[rebuildInstance] copying in missing attribute " + attribute.getNodeName() + + " to " + XMLUtil.buildXPath(clone, newInstanceDocument.getDocumentElement())); + + clone.setAttributeNode((Attr) attribute.cloneNode(false)); + } + } + else + { + String namespace = attribute.getNamespaceURI(); + if (!((!isEmpty + && (namespace.equals(NamespaceConstants.XMLSCHEMA_INSTANCE_NS) && localName.equals("nil")) || (namespace + .equals(NamespaceService.ALFRESCO_URI) && localName.equals("prototype"))))) + { + if (oldEl.hasAttributeNS(namespace, localName)) + { + clone.setAttributeNodeNS((Attr) newInstanceDocument.importNode(oldEl.getAttributeNodeNS( + namespace, localName), false)); + } + else + { + LOGGER.debug("[rebuildInstance] copying in missing attribute " + attribute.getNodeName() + + " to " + XMLUtil.buildXPath(clone, newInstanceDocument.getDocumentElement())); + + clone.setAttributeNodeNS((Attr) attribute.cloneNode(false)); + } + } + } + } - class PrototypeInsertionData - { - final Node prototype; - final List nodes; - final boolean append; - - PrototypeInsertionData(final Node prototype, - final List nodes, - final boolean append) - { - this.prototype = prototype; - this.nodes = nodes; - this.append = append; + // recurse on children + rebuildInstance(proto, oldEl, clone, schemaNamespaces); + } } - }; - final HashMap prototypesToInsert = - new HashMap(); - // find all prototype nodes - final Iterator it = - prototypeContext.iteratePointers("//*[@" + NamespaceService.ALFRESCO_PREFIX + - ":prototype='true'][ancestor::*[not(@" + NamespaceService.ALFRESCO_PREFIX + - ":prototype)]]"); - - // find all relevant insertion points within the instance document - while (it.hasNext()) - { - final Pointer p = (Pointer)it.next(); - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("[insertPrototypeNodes] evaluating prototype node " + p.asPath()); - } - String path = p.asPath().replaceAll("\\[\\d+\\]", "") + "[last()]"; - if (prototypesToInsert.containsKey(path)) + // Now add in a new copy of the prototype + if (isPrototype) { if (LOGGER.isDebugEnabled()) { - LOGGER.debug("[insertPrototypeNodes] already checked path " + path + " - ignoring."); + LOGGER.debug("[rebuildInstance] appending " + + proto.getNodeName() + + " to " + + XMLUtil.buildXPath(newInstanceNode, newInstanceDocument + .getDocumentElement())); } - continue; - } - - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("[insertPrototypeNodes] evaluating " + path + " against instance document"); - } - - List l = (List)instanceContext.selectNodes(path); - if (l.size() != 0) - { - // this is a 1 to n repeat - add a prototype node to the list of repeat instances - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("[insertPrototypeNodes] path " + path + " evaluated to " + l.size() + " nodes"); - } - prototypesToInsert.put(path, new PrototypeInsertionData((Node)p.getNode(), - l, - false)); - } - - if (path.lastIndexOf("/") != 0) - { - // this could be a 0 to n repeat - check if there are any relevant parent - // insertion points - path = path.replaceAll("\\/([^/]+)\\[last\\(\\)\\]$", "[not(child::$1)]"); - - l = (List)instanceContext.selectNodes(path); - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("[insertPrototypeNodes] path " + path + " evaluated to " + l.size() + " nodes"); - } - prototypesToInsert.put(path, new PrototypeInsertionData((Node)p.getNode(), - l, - true)); - } - else - { - // this could be a repeat at the root of the document - path = path.replaceAll("\\[last\\(\\)\\]$", ""); - l = (List)instanceContext.selectNodes(path); - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("[insertPrototypeNodes] path " + path + " evaluated to " + l.size() + " nodes"); - } - if (l.size() == 0) - { - l.add(instanceDocumentElement); - prototypesToInsert.put(path, new PrototypeInsertionData((Node)p.getNode(), - l, - true)); - } - } - } - - // apply prototype nodes to all discovered insertion points - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("[insertPrototypeNodes] instance dcoument before mutation =\n" + - XMLUtil.toString(instanceDocumentElement, true)); - } - for (Map.Entry me : prototypesToInsert.entrySet()) - { - final PrototypeInsertionData data = me.getValue(); - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("[insertPrototypeNodes] adding prototype for " + data.prototype.getNodeName() + - " from path " + me.getKey() + - " to " + data.nodes.size() + " nodes"); - } - - for (final Node n : data.nodes) - { - if (data.append) - { - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("[insertPrototypeNodes] appending " + data.prototype.getNodeName() + - " to " + XMLUtil.buildXPath(n, instanceDocumentElement)); - } - n.appendChild(data.prototype.cloneNode(true)); - } - else if (n.getNextSibling() != null) - { - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("[insertPrototypeNodes] inserting " + data.prototype.getNodeName() + - " into " + XMLUtil.buildXPath(n.getParentNode(), - instanceDocumentElement) + - " before " + XMLUtil.buildXPath(n.getNextSibling(), - instanceDocumentElement)); - } - n.getParentNode().insertBefore(data.prototype.cloneNode(true), - n.getNextSibling()); - } - else - { - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("[insertPrototypeNodes] appending " + data.prototype.getNodeName() + - " to " + XMLUtil.buildXPath(n.getParentNode(), - instanceDocumentElement)); - } - n.getParentNode().appendChild(data.prototype.cloneNode(true)); - } - } - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("[insertPrototypeNodes] instance dcoument after mutation =\n" + - XMLUtil.toString(instanceDocumentElement, true)); - } + newInstanceNode.appendChild(proto.cloneNode(true)); + } } } @@ -2349,19 +2150,19 @@ public class Schema2XForms implements Serializable xformsDocument.appendChild(envelopeElement); //set namespace attribute - this.addNamespace(envelopeElement, + addNamespace(envelopeElement, NamespaceConstants.XHTML_PREFIX, NamespaceConstants.XHTML_NS); - this.addNamespace(envelopeElement, + addNamespace(envelopeElement, NamespaceConstants.XFORMS_PREFIX, NamespaceConstants.XFORMS_NS); - this.addNamespace(envelopeElement, + addNamespace(envelopeElement, NamespaceConstants.XMLEVENTS_PREFIX, NamespaceConstants.XMLEVENTS_NS); - this.addNamespace(envelopeElement, + addNamespace(envelopeElement, NamespaceConstants.XMLSCHEMA_INSTANCE_PREFIX, NamespaceConstants.XMLSCHEMA_INSTANCE_NS); - this.addNamespace(envelopeElement, + addNamespace(envelopeElement, NamespaceService.ALFRESCO_PREFIX, NamespaceService.ALFRESCO_URI); @@ -3164,9 +2965,7 @@ public class Schema2XForms implements Serializable return elementName; } - private String addNamespace(final Element e, - String nsPrefix, - final String ns) + private static String addNamespace(final Element e, String nsPrefix, final String ns) { String prefix; if ((prefix = NamespaceResolver.getPrefix(e, ns)) != null) diff --git a/source/java/org/alfresco/web/sharepoint/auth/BasicAuthenticationHandler.java b/source/java/org/alfresco/web/sharepoint/auth/BasicAuthenticationHandler.java index dc541bbfea..102fe07ad6 100644 --- a/source/java/org/alfresco/web/sharepoint/auth/BasicAuthenticationHandler.java +++ b/source/java/org/alfresco/web/sharepoint/auth/BasicAuthenticationHandler.java @@ -79,7 +79,7 @@ public class BasicAuthenticationHandler extends AbstractAuthenticationHandler if (mapper.isSiteMember(request, alfrescoContext, username)) { - user = new User(username, authenticationService.getCurrentTicket(session.getId()), personService.getPerson(username)); + user = new User(username, authenticationService.getCurrentTicket(), personService.getPerson(username)); if (session != null) session.setAttribute(USER_SESSION_ATTRIBUTE, user); } diff --git a/source/java/org/alfresco/web/sharepoint/auth/ntlm/NtlmAuthenticationHandler.java b/source/java/org/alfresco/web/sharepoint/auth/ntlm/NtlmAuthenticationHandler.java index 472662bee2..8636cc8a77 100644 --- a/source/java/org/alfresco/web/sharepoint/auth/ntlm/NtlmAuthenticationHandler.java +++ b/source/java/org/alfresco/web/sharepoint/auth/ntlm/NtlmAuthenticationHandler.java @@ -153,7 +153,7 @@ public class NtlmAuthenticationHandler extends AbstractAuthenticationHandler imp { try { - authenticationService.validate(user.getTicket(), session.getId()); + authenticationService.validate(user.getTicket()); needToAuthenticate = false; } catch (AuthenticationException e) @@ -246,7 +246,7 @@ public class NtlmAuthenticationHandler extends AbstractAuthenticationHandler imp { try { - authenticationService.validate(user.getTicket(), session.getId()); + authenticationService.validate(user.getTicket()); return user; } catch (AuthenticationException ae) @@ -437,7 +437,7 @@ public class NtlmAuthenticationHandler extends AbstractAuthenticationHandler imp // user already exists - revalidate ticket to authenticate the current user thread try { - authenticationService.validate(user.getTicket(), session.getId()); + authenticationService.validate(user.getTicket()); } catch (AuthenticationException ex) { @@ -517,7 +517,7 @@ public class NtlmAuthenticationHandler extends AbstractAuthenticationHandler imp String username = AuthenticationUtil.runAs(getUserNameRunAsWork, AuthenticationUtil.SYSTEM_USER_NAME); authenticationComponent.setCurrentUser(userName); - String currentTicket = authenticationService.getCurrentTicket(session.getId()); + String currentTicket = authenticationService.getCurrentTicket(); // Create the user object to be stored in the session user = new User(username, currentTicket, personNodeRef); diff --git a/source/web/index.jsp b/source/web/index.jsp index 353f5c8c5e..76f848a41d 100644 --- a/source/web/index.jsp +++ b/source/web/index.jsp @@ -54,7 +54,7 @@ if (user != null) tx.begin(); try { - authService.validate(user.getTicket(), session.getId()); + authService.validate(user.getTicket()); // ensure construction of the FacesContext before attemping a service call FacesContext fc = FacesHelper.getFacesContext(request, response, application); @@ -72,7 +72,7 @@ if (user != null) // expired ticket AuthenticationService unpAuth = (AuthenticationService)context.getBean("authenticationService"); - unpAuth.invalidateTicket(unpAuth.getCurrentTicket(session.getId()), session.getId()); + unpAuth.invalidateTicket(unpAuth.getCurrentTicket()); unpAuth.clearCurrentSecurityContext(); } catch (Throwable e) @@ -89,7 +89,7 @@ else authService.authenticateAsGuest(); PersonService personService = (PersonService)context.getBean("personService"); NodeRef guestRef = personService.getPerson(PermissionService.GUEST_AUTHORITY); - user = new User(authService.getCurrentUserName(), authService.getCurrentTicket(session.getId()), guestRef); + user = new User(authService.getCurrentUserName(), authService.getCurrentTicket(), guestRef); session.setAttribute(AuthenticationHelper.AUTHENTICATION_USER, user); // ensure construction of the FacesContext before attemping a service call