mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-21 18:09:20 +00:00
Merge V1.4 to HEAD
- Ignored Enterprise-specific changes svn merge svn://svn.alfresco.com:3691/alfresco/BRANCHES/V1.4@3701 svn://svn.alfresco.com:3691/alfresco/BRANCHES/V1.4@3703 . svn merge svn://svn.alfresco.com:3691/alfresco/BRANCHES/V1.4@3704 svn://svn.alfresco.com:3691/alfresco/BRANCHES/V1.4@3705 . svn merge svn://svn.alfresco.com:3691/alfresco/BRANCHES/V1.4@3707 svn://svn.alfresco.com:3691/alfresco/BRANCHES/V1.4@3876 . svn revert root\projects\web-client\source\web\jsp\admin\admin-console.jsp git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@3879 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Mozilla Public License version 1.1
|
||||
* with a permitted attribution clause. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfresco.org/legal/license.txt
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific
|
||||
* language governing permissions and limitations under the
|
||||
* License.
|
||||
*/
|
||||
package org.alfresco.web.app.servlet;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.alfresco.web.bean.repository.User;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* This servlet filter is used to restrict direct URL access to administration
|
||||
* resource in the web client, for example the admin and jBPM consoles.
|
||||
*
|
||||
* @author gavinc
|
||||
*/
|
||||
public class AdminAuthenticationFilter implements Filter
|
||||
{
|
||||
private static final Log logger = LogFactory.getLog(AdminAuthenticationFilter.class);
|
||||
|
||||
/**
|
||||
* @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
|
||||
*/
|
||||
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
|
||||
throws IOException, ServletException
|
||||
{
|
||||
HttpServletRequest httpRequest = (HttpServletRequest)req;
|
||||
HttpServletResponse httpResponse = (HttpServletResponse)res;
|
||||
|
||||
// The fact that this filter is being called means a request for a protected
|
||||
// resource has taken place, check that the current user is in fact an
|
||||
// administrator.
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("Authorising request for protected resource: " + httpRequest.getRequestURI());
|
||||
|
||||
// there should be a user at this point so retrieve it
|
||||
User user = AuthenticationHelper.getUser(httpRequest, httpResponse);
|
||||
|
||||
// if the user is present check to see whether it is an admin user
|
||||
boolean isAdmin = (user != null && user.isAdmin());
|
||||
|
||||
if (isAdmin)
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("Current user has admin authority, allowing access.");
|
||||
|
||||
// continue filter chaining if current user is admin user
|
||||
chain.doFilter(req, res);
|
||||
}
|
||||
else
|
||||
{
|
||||
// return the 401 Forbidden error as the current user is not an administrator
|
||||
// if the response has already been committed there's nothing we can do but
|
||||
// print out a warning
|
||||
if (httpResponse.isCommitted() == false)
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("Current user does not have admin authority, returning 401 Forbidden error...");
|
||||
|
||||
httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (logger.isWarnEnabled())
|
||||
logger.warn("Access denied to '" + httpRequest.getRequestURI() +
|
||||
"'. The response has already been committed so a 401 Forbidden error could not be sent!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
|
||||
*/
|
||||
public void init(FilterConfig config) throws ServletException
|
||||
{
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
/**
|
||||
* @see javax.servlet.Filter#destroy()
|
||||
*/
|
||||
public void destroy()
|
||||
{
|
||||
// nothing to do
|
||||
}
|
||||
}
|
@@ -100,39 +100,15 @@ public final class AuthenticationHelper
|
||||
{
|
||||
HttpSession session = httpRequest.getSession();
|
||||
|
||||
// examine the appropriate session for our User object
|
||||
User user = null;
|
||||
// retrieve the User object
|
||||
User user = getUser(httpRequest, httpResponse);
|
||||
|
||||
// get the login bean if we're not in the portal
|
||||
LoginBean loginBean = null;
|
||||
if (Application.inPortalServer() == false)
|
||||
{
|
||||
user = (User)session.getAttribute(AUTHENTICATION_USER);
|
||||
loginBean = (LoginBean)session.getAttribute(LOGIN_BEAN);
|
||||
}
|
||||
else
|
||||
{
|
||||
// naff solution as we need to enumerate all session keys until we find the one that
|
||||
// should match our User objects - this is weak but we don't know how the underlying
|
||||
// Portal vendor has decided to encode the objects in the session
|
||||
if (portalUserKeyName.get() == null)
|
||||
{
|
||||
String userKeyPostfix = "?" + AUTHENTICATION_USER;
|
||||
Enumeration enumNames = session.getAttributeNames();
|
||||
while (enumNames.hasMoreElements())
|
||||
{
|
||||
String name = (String)enumNames.nextElement();
|
||||
if (name.endsWith(userKeyPostfix))
|
||||
{
|
||||
// cache the key value once found!
|
||||
portalUserKeyName.set(name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (portalUserKeyName.get() != null)
|
||||
{
|
||||
user = (User)session.getAttribute(portalUserKeyName.get());
|
||||
}
|
||||
}
|
||||
|
||||
// setup the authentication context
|
||||
WebApplicationContext wc = WebApplicationContextUtils.getRequiredWebApplicationContext(context);
|
||||
@@ -388,6 +364,52 @@ public final class AuthenticationHelper
|
||||
return AuthenticationStatus.Failure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to retrieve the User object stored in the current session.
|
||||
*
|
||||
* @param httpRequest The HTTP request
|
||||
* @param httpResponse The HTTP response
|
||||
* @return The User object representing the current user or null if it could not be found
|
||||
*/
|
||||
public static User getUser(HttpServletRequest httpRequest, HttpServletResponse httpResponse)
|
||||
{
|
||||
HttpSession session = httpRequest.getSession();
|
||||
User user = null;
|
||||
|
||||
// examine the appropriate session to try and find the User object
|
||||
if (Application.inPortalServer() == false)
|
||||
{
|
||||
user = (User)session.getAttribute(AUTHENTICATION_USER);
|
||||
}
|
||||
else
|
||||
{
|
||||
// naff solution as we need to enumerate all session keys until we find the one that
|
||||
// should match our User objects - this is weak but we don't know how the underlying
|
||||
// Portal vendor has decided to encode the objects in the session
|
||||
if (portalUserKeyName.get() == null)
|
||||
{
|
||||
String userKeyPostfix = "?" + AUTHENTICATION_USER;
|
||||
Enumeration enumNames = session.getAttributeNames();
|
||||
while (enumNames.hasMoreElements())
|
||||
{
|
||||
String name = (String)enumNames.nextElement();
|
||||
if (name.endsWith(userKeyPostfix))
|
||||
{
|
||||
// cache the key value once found!
|
||||
portalUserKeyName.set(name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (portalUserKeyName.get() != null)
|
||||
{
|
||||
user = (User)session.getAttribute(portalUserKeyName.get());
|
||||
}
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the Alfresco auth cookie value.
|
||||
*
|
||||
|
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Mozilla Public License version 1.1
|
||||
* with a permitted attribution clause. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfresco.org/legal/license.txt
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific
|
||||
* language governing permissions and limitations under the
|
||||
* License.
|
||||
*/
|
||||
package org.alfresco.web.app.servlet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.SocketException;
|
||||
import java.net.URLEncoder;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Date;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.content.filestore.FileContentReader;
|
||||
import org.alfresco.service.ServiceRegistry;
|
||||
import org.alfresco.service.cmr.repository.ContentReader;
|
||||
import org.alfresco.service.cmr.repository.ContentService;
|
||||
import org.alfresco.service.cmr.repository.MimetypeService;
|
||||
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.security.AccessStatus;
|
||||
import org.alfresco.service.cmr.security.PermissionService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.web.app.Application;
|
||||
import org.alfresco.web.ui.common.Utils;
|
||||
import org.apache.commons.logging.Log;
|
||||
|
||||
/**
|
||||
* Base class for the download content servlets. Provides common
|
||||
* processing for the request.
|
||||
*
|
||||
* @see org.alfresco.web.app.servlet.DownloadContentServlet
|
||||
* @see org.alfresco.web.app.servlet.GuestDownloadContentServlet
|
||||
*
|
||||
* @author Kevin Roast
|
||||
* @author gavinc
|
||||
*/
|
||||
public abstract class BaseDownloadContentServlet extends BaseServlet
|
||||
{
|
||||
private static final long serialVersionUID = -4558907921887235966L;
|
||||
|
||||
protected static final String MIMETYPE_OCTET_STREAM = "application/octet-stream";
|
||||
|
||||
protected static final String MSG_ERROR_CONTENT_MISSING = "error_content_missing";
|
||||
|
||||
protected static final String ARG_PROPERTY = "property";
|
||||
protected static final String ARG_ATTACH = "attach";
|
||||
protected static final String ARG_PATH = "path";
|
||||
|
||||
/**
|
||||
* Gets the logger to use for this request.
|
||||
* <p>
|
||||
* This will show all debug entries from this class as though they
|
||||
* came from the subclass.
|
||||
*
|
||||
* @return The logger
|
||||
*/
|
||||
protected abstract Log getLogger();
|
||||
|
||||
/**
|
||||
* Processes the download request using the current context i.e. no
|
||||
* authentication checks are made, it is presumed they have already
|
||||
* been done.
|
||||
*
|
||||
* @param req The HTTP request
|
||||
* @param res The HTTP response
|
||||
* @param redirectToLogin Flag to determine whether to redirect to the login
|
||||
* page if the user does not have the correct permissions
|
||||
*/
|
||||
protected void processDownloadRequest(HttpServletRequest req, HttpServletResponse res,
|
||||
boolean redirectToLogin)
|
||||
throws ServletException, IOException
|
||||
{
|
||||
Log logger = getLogger();
|
||||
String uri = req.getRequestURI();
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
String queryString = req.getQueryString();
|
||||
logger.debug("Processing URL: " + uri +
|
||||
((queryString != null && queryString.length() > 0) ? ("?" + queryString) : ""));
|
||||
}
|
||||
|
||||
// TODO: add compression here?
|
||||
// see http://servlets.com/jservlet2/examples/ch06/ViewResourceCompress.java for example
|
||||
// only really needed if we don't use the built in compression of the servlet container
|
||||
uri = uri.substring(req.getContextPath().length());
|
||||
StringTokenizer t = new StringTokenizer(uri, "/");
|
||||
int tokenCount = t.countTokens();
|
||||
|
||||
t.nextToken(); // skip servlet name
|
||||
|
||||
// attachment mode (either 'attach' or 'direct')
|
||||
String attachToken = t.nextToken();
|
||||
boolean attachment = attachToken.equals(ARG_ATTACH);
|
||||
|
||||
// get or calculate the noderef and filename to download as
|
||||
NodeRef nodeRef;
|
||||
String filename;
|
||||
|
||||
// do we have a path parameter instead of a NodeRef?
|
||||
String path = req.getParameter(ARG_PATH);
|
||||
if (path != null && path.length() != 0)
|
||||
{
|
||||
// process the name based path to resolve the NodeRef and the Filename element
|
||||
PathRefInfo pathInfo = resolveNamePath(getServletContext(), path);
|
||||
|
||||
nodeRef = pathInfo.NodeRef;
|
||||
filename = pathInfo.Filename;
|
||||
}
|
||||
else
|
||||
{
|
||||
// a NodeRef must have been specified if no path has been found
|
||||
if (tokenCount < 6)
|
||||
{
|
||||
throw new IllegalArgumentException("Download URL did not contain all required args: " + uri);
|
||||
}
|
||||
|
||||
// assume 'workspace' or other NodeRef based protocol for remaining URL elements
|
||||
StoreRef storeRef = new StoreRef(t.nextToken(), t.nextToken());
|
||||
String id = t.nextToken();
|
||||
// build noderef from the appropriate URL elements
|
||||
nodeRef = new NodeRef(storeRef, id);
|
||||
|
||||
// filename is last remaining token
|
||||
filename = t.nextToken();
|
||||
}
|
||||
|
||||
// get qualified of the property to get content from - default to ContentModel.PROP_CONTENT
|
||||
QName propertyQName = ContentModel.PROP_CONTENT;
|
||||
String property = req.getParameter(ARG_PROPERTY);
|
||||
if (property != null && property.length() != 0)
|
||||
{
|
||||
propertyQName = QName.createQName(property);
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Found NodeRef: " + nodeRef.toString());
|
||||
logger.debug("Will use filename: " + filename);
|
||||
logger.debug("For property: " + propertyQName);
|
||||
logger.debug("With attachment mode: " + attachment);
|
||||
}
|
||||
|
||||
// get the services we need to retrieve the content
|
||||
ServiceRegistry serviceRegistry = getServiceRegistry(getServletContext());
|
||||
NodeService nodeService = serviceRegistry.getNodeService();
|
||||
ContentService contentService = serviceRegistry.getContentService();
|
||||
PermissionService permissionService = serviceRegistry.getPermissionService();
|
||||
|
||||
try
|
||||
{
|
||||
// check that the user has at least READ_CONTENT access - else redirect to the login page
|
||||
if (permissionService.hasPermission(nodeRef, PermissionService.READ_CONTENT) == AccessStatus.DENIED)
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("User does not have permissions to read content for NodeRef: " + nodeRef.toString());
|
||||
|
||||
if (redirectToLogin)
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("Redirecting to login page...");
|
||||
|
||||
redirectToLoginPage(req, res, getServletContext());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("Returning 403 Forbidden error...");
|
||||
|
||||
res.sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// check If-Modified-Since header and set Last-Modified header as appropriate
|
||||
Date modified = (Date)nodeService.getProperty(nodeRef, ContentModel.PROP_MODIFIED);
|
||||
long modifiedSince = req.getDateHeader("If-Modified-Since");
|
||||
if (modifiedSince > 0L)
|
||||
{
|
||||
// round the date to the ignore millisecond value which is not supplied by header
|
||||
long modDate = (modified.getTime() / 1000L) * 1000L;
|
||||
if (modDate <= modifiedSince)
|
||||
{
|
||||
res.setStatus(304);
|
||||
return;
|
||||
}
|
||||
}
|
||||
res.setDateHeader("Last-Modified", modified.getTime());
|
||||
|
||||
if (attachment == true)
|
||||
{
|
||||
// set header based on filename - will force a Save As from the browse if it doesn't recognise it
|
||||
// this is better than the default response of the browser trying to display the contents
|
||||
res.setHeader("Content-Disposition", "attachment");
|
||||
}
|
||||
|
||||
// get the content reader
|
||||
ContentReader reader = contentService.getReader(nodeRef, propertyQName);
|
||||
// ensure that it is safe to use
|
||||
reader = FileContentReader.getSafeContentReader(
|
||||
reader,
|
||||
Application.getMessage(req.getSession(), MSG_ERROR_CONTENT_MISSING),
|
||||
nodeRef, reader);
|
||||
|
||||
String mimetype = reader.getMimetype();
|
||||
// fall back if unable to resolve mimetype property
|
||||
if (mimetype == null || mimetype.length() == 0)
|
||||
{
|
||||
MimetypeService mimetypeMap = serviceRegistry.getMimetypeService();
|
||||
mimetype = MIMETYPE_OCTET_STREAM;
|
||||
int extIndex = filename.lastIndexOf('.');
|
||||
if (extIndex != -1)
|
||||
{
|
||||
String ext = filename.substring(extIndex + 1);
|
||||
String mt = mimetypeMap.getMimetypesByExtension().get(ext);
|
||||
if (mt != null)
|
||||
{
|
||||
mimetype = mt;
|
||||
}
|
||||
}
|
||||
}
|
||||
// set mimetype for the content and the character encoding for the stream
|
||||
res.setContentType(mimetype);
|
||||
res.setCharacterEncoding(reader.getEncoding());
|
||||
|
||||
// get the content and stream directly to the response output stream
|
||||
// assuming the repo is capable of streaming in chunks, this should allow large files
|
||||
// to be streamed directly to the browser response stream.
|
||||
try
|
||||
{
|
||||
reader.getContent( res.getOutputStream() );
|
||||
}
|
||||
catch (SocketException e)
|
||||
{
|
||||
if (e.getMessage().contains("ClientAbortException"))
|
||||
{
|
||||
// the client cut the connection - our mission was accomplished apart from a little error message
|
||||
logger.error("Client aborted stream read:\n node: " + nodeRef + "\n content: " + reader);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Throwable err)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Error during download content servlet processing: " + err.getMessage(), err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to generate a URL to a content node for downloading content from the server.
|
||||
*
|
||||
* @param pattern The pattern to use for the URL
|
||||
* @param ref NodeRef of the content node to generate URL for (cannot be null)
|
||||
* @param name File name to return in the URL (cannot be null)
|
||||
*
|
||||
* @return URL to download the content from the specified node
|
||||
*/
|
||||
protected final static String generateUrl(String pattern, NodeRef ref, String name)
|
||||
{
|
||||
String url = null;
|
||||
|
||||
try
|
||||
{
|
||||
url = MessageFormat.format(pattern, new Object[] {
|
||||
ref.getStoreRef().getProtocol(),
|
||||
ref.getStoreRef().getIdentifier(),
|
||||
ref.getId(),
|
||||
Utils.replace(URLEncoder.encode(name, "UTF-8"), "+", "%20") } );
|
||||
}
|
||||
catch (UnsupportedEncodingException uee)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Failed to encode content URL for node: " + ref, uee);
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
}
|
@@ -73,7 +73,7 @@ public class CommandServlet extends BaseServlet
|
||||
/**
|
||||
* @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
|
||||
*/
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse res)
|
||||
protected void service(HttpServletRequest req, HttpServletResponse res)
|
||||
throws ServletException, IOException
|
||||
{
|
||||
String uri = req.getRequestURI();
|
||||
|
@@ -17,32 +17,12 @@
|
||||
package org.alfresco.web.app.servlet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.SocketException;
|
||||
import java.net.URLEncoder;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Date;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.content.filestore.FileContentReader;
|
||||
import org.alfresco.service.ServiceRegistry;
|
||||
import org.alfresco.service.cmr.repository.ContentReader;
|
||||
import org.alfresco.service.cmr.repository.ContentService;
|
||||
import org.alfresco.service.cmr.repository.MimetypeService;
|
||||
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.security.AccessStatus;
|
||||
import org.alfresco.service.cmr.security.PermissionService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.web.app.Application;
|
||||
import org.alfresco.web.ui.common.Utils;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
@@ -71,26 +51,30 @@ import org.apache.commons.logging.LogFactory;
|
||||
* Like most Alfresco servlets, the URL may be followed by a valid 'ticket' argument for authentication:
|
||||
* ?ticket=1234567890
|
||||
* <p>
|
||||
* And/or also followed by the "?guest=true" argument to force guest access login for the URL.
|
||||
* And/or also followed by the "?guest=true" argument to force guest access login for the URL. If the
|
||||
* guest=true parameter is used the current session will be logged out and the guest user logged in.
|
||||
* Therefore upon completion of this request the current user will be "guest".
|
||||
* <p>
|
||||
* If the user attempting the request is not authorised to access the requested node the login page
|
||||
* will be redirected to.
|
||||
*
|
||||
* @author Kevin Roast
|
||||
* @author gavinc
|
||||
*/
|
||||
public class DownloadContentServlet extends BaseServlet
|
||||
public class DownloadContentServlet extends BaseDownloadContentServlet
|
||||
{
|
||||
private static final long serialVersionUID = -4558907921887235966L;
|
||||
|
||||
private static final long serialVersionUID = -576405943603122206L;
|
||||
|
||||
private static Log logger = LogFactory.getLog(DownloadContentServlet.class);
|
||||
|
||||
private static final String DOWNLOAD_URL = "/download/attach/{0}/{1}/{2}/{3}";
|
||||
private static final String BROWSER_URL = "/download/direct/{0}/{1}/{2}/{3}";
|
||||
|
||||
private static final String MIMETYPE_OCTET_STREAM = "application/octet-stream";
|
||||
|
||||
private static final String MSG_ERROR_CONTENT_MISSING = "error_content_missing";
|
||||
|
||||
private static final String ARG_PROPERTY = "property";
|
||||
private static final String ARG_ATTACH = "attach";
|
||||
private static final String ARG_PATH = "path";
|
||||
@Override
|
||||
protected Log getLogger()
|
||||
{
|
||||
return logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
|
||||
@@ -98,10 +82,12 @@ public class DownloadContentServlet extends BaseServlet
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse res)
|
||||
throws ServletException, IOException
|
||||
{
|
||||
String uri = req.getRequestURI();
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("Processing URL: " + uri + (req.getQueryString() != null ? ("?" + req.getQueryString()) : ""));
|
||||
{
|
||||
String queryString = req.getQueryString();
|
||||
logger.debug("Authenticating request to URL: " + req.getRequestURI() +
|
||||
((queryString != null && queryString.length() > 0) ? ("?" + queryString) : ""));
|
||||
}
|
||||
|
||||
AuthenticationStatus status = servletAuthenticate(req, res);
|
||||
if (status == AuthenticationStatus.Failure)
|
||||
@@ -109,159 +95,7 @@ public class DownloadContentServlet extends BaseServlet
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: add compression here?
|
||||
// see http://servlets.com/jservlet2/examples/ch06/ViewResourceCompress.java for example
|
||||
// only really needed if we don't use the built in compression of the servlet container
|
||||
uri = uri.substring(req.getContextPath().length());
|
||||
StringTokenizer t = new StringTokenizer(uri, "/");
|
||||
int tokenCount = t.countTokens();
|
||||
|
||||
t.nextToken(); // skip servlet name
|
||||
|
||||
// attachment mode (either 'attach' or 'direct')
|
||||
String attachToken = t.nextToken();
|
||||
boolean attachment = attachToken.equals(ARG_ATTACH);
|
||||
|
||||
// get or calculate the noderef and filename to download as
|
||||
NodeRef nodeRef;
|
||||
String filename;
|
||||
|
||||
// do we have a path parameter instead of a NodeRef?
|
||||
String path = req.getParameter(ARG_PATH);
|
||||
if (path != null && path.length() != 0)
|
||||
{
|
||||
// process the name based path to resolve the NodeRef and the Filename element
|
||||
PathRefInfo pathInfo = resolveNamePath(getServletContext(), path);
|
||||
|
||||
nodeRef = pathInfo.NodeRef;
|
||||
filename = pathInfo.Filename;
|
||||
}
|
||||
else
|
||||
{
|
||||
// a NodeRef must have been specified if no path has been found
|
||||
if (tokenCount < 6)
|
||||
{
|
||||
throw new IllegalArgumentException("Download URL did not contain all required args: " + uri);
|
||||
}
|
||||
|
||||
// assume 'workspace' or other NodeRef based protocol for remaining URL elements
|
||||
StoreRef storeRef = new StoreRef(t.nextToken(), t.nextToken());
|
||||
String id = t.nextToken();
|
||||
// build noderef from the appropriate URL elements
|
||||
nodeRef = new NodeRef(storeRef, id);
|
||||
|
||||
// filename is last remaining token
|
||||
filename = t.nextToken();
|
||||
}
|
||||
|
||||
// get qualified of the property to get content from - default to ContentModel.PROP_CONTENT
|
||||
QName propertyQName = ContentModel.PROP_CONTENT;
|
||||
String property = req.getParameter(ARG_PROPERTY);
|
||||
if (property != null && property.length() != 0)
|
||||
{
|
||||
propertyQName = QName.createQName(property);
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Found NodeRef: " + nodeRef.toString());
|
||||
logger.debug("Will use filename: " + filename);
|
||||
logger.debug("For property: " + propertyQName);
|
||||
logger.debug("With attachment mode: " + attachment);
|
||||
}
|
||||
|
||||
// get the services we need to retrieve the content
|
||||
ServiceRegistry serviceRegistry = getServiceRegistry(getServletContext());
|
||||
NodeService nodeService = serviceRegistry.getNodeService();
|
||||
ContentService contentService = serviceRegistry.getContentService();
|
||||
PermissionService permissionService = serviceRegistry.getPermissionService();
|
||||
|
||||
try
|
||||
{
|
||||
// check that the user has at least READ_CONTENT access - else redirect to the login page
|
||||
if (permissionService.hasPermission(nodeRef, PermissionService.READ_CONTENT) == AccessStatus.DENIED)
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("User does not have permissions to read content for NodeRef: " + nodeRef.toString());
|
||||
redirectToLoginPage(req, res, getServletContext());
|
||||
return;
|
||||
}
|
||||
|
||||
// check If-Modified-Since header and set Last-Modified header as appropriate
|
||||
Date modified = (Date)nodeService.getProperty(nodeRef, ContentModel.PROP_MODIFIED);
|
||||
long modifiedSince = req.getDateHeader("If-Modified-Since");
|
||||
if (modifiedSince > 0L)
|
||||
{
|
||||
// round the date to the ignore millisecond value which is not supplied by header
|
||||
long modDate = (modified.getTime() / 1000L) * 1000L;
|
||||
if (modDate <= modifiedSince)
|
||||
{
|
||||
res.setStatus(304);
|
||||
return;
|
||||
}
|
||||
}
|
||||
res.setDateHeader("Last-Modified", modified.getTime());
|
||||
|
||||
if (attachment == true)
|
||||
{
|
||||
// set header based on filename - will force a Save As from the browse if it doesn't recognise it
|
||||
// this is better than the default response of the browser trying to display the contents
|
||||
res.setHeader("Content-Disposition", "attachment");
|
||||
}
|
||||
|
||||
// get the content reader
|
||||
ContentReader reader = contentService.getReader(nodeRef, propertyQName);
|
||||
// ensure that it is safe to use
|
||||
reader = FileContentReader.getSafeContentReader(
|
||||
reader,
|
||||
Application.getMessage(req.getSession(), MSG_ERROR_CONTENT_MISSING),
|
||||
nodeRef, reader);
|
||||
|
||||
String mimetype = reader.getMimetype();
|
||||
// fall back if unable to resolve mimetype property
|
||||
if (mimetype == null || mimetype.length() == 0)
|
||||
{
|
||||
MimetypeService mimetypeMap = serviceRegistry.getMimetypeService();
|
||||
mimetype = MIMETYPE_OCTET_STREAM;
|
||||
int extIndex = filename.lastIndexOf('.');
|
||||
if (extIndex != -1)
|
||||
{
|
||||
String ext = filename.substring(extIndex + 1);
|
||||
String mt = mimetypeMap.getMimetypesByExtension().get(ext);
|
||||
if (mt != null)
|
||||
{
|
||||
mimetype = mt;
|
||||
}
|
||||
}
|
||||
}
|
||||
// set mimetype for the content and the character encoding for the stream
|
||||
res.setContentType(mimetype);
|
||||
res.setCharacterEncoding(reader.getEncoding());
|
||||
|
||||
// get the content and stream directly to the response output stream
|
||||
// assuming the repo is capable of streaming in chunks, this should allow large files
|
||||
// to be streamed directly to the browser response stream.
|
||||
try
|
||||
{
|
||||
reader.getContent( res.getOutputStream() );
|
||||
}
|
||||
catch (SocketException e)
|
||||
{
|
||||
if (e.getMessage().contains("ClientAbortException"))
|
||||
{
|
||||
// the client cut the connection - our mission was accomplished apart from a little error message
|
||||
logger.error("Client aborted stream read:\n node: " + nodeRef + "\n content: " + reader);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Throwable err)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Error during download content servlet processing: " + err.getMessage(), err);
|
||||
}
|
||||
processDownloadRequest(req, res, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -276,22 +110,7 @@ public class DownloadContentServlet extends BaseServlet
|
||||
*/
|
||||
public final static String generateDownloadURL(NodeRef ref, String name)
|
||||
{
|
||||
String url = null;
|
||||
|
||||
try
|
||||
{
|
||||
url = MessageFormat.format(DOWNLOAD_URL, new Object[] {
|
||||
ref.getStoreRef().getProtocol(),
|
||||
ref.getStoreRef().getIdentifier(),
|
||||
ref.getId(),
|
||||
Utils.replace(URLEncoder.encode(name, "UTF-8"), "+", "%20") } );
|
||||
}
|
||||
catch (UnsupportedEncodingException uee)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Failed to encode content URL for node: " + ref, uee);
|
||||
}
|
||||
|
||||
return url;
|
||||
return generateUrl(DOWNLOAD_URL, ref, name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -306,21 +125,6 @@ public class DownloadContentServlet extends BaseServlet
|
||||
*/
|
||||
public final static String generateBrowserURL(NodeRef ref, String name)
|
||||
{
|
||||
String url = null;
|
||||
|
||||
try
|
||||
{
|
||||
url = MessageFormat.format(BROWSER_URL, new Object[] {
|
||||
ref.getStoreRef().getProtocol(),
|
||||
ref.getStoreRef().getIdentifier(),
|
||||
ref.getId(),
|
||||
Utils.replace(URLEncoder.encode(name, "UTF-8"), "+", "%20") } );
|
||||
}
|
||||
catch (UnsupportedEncodingException uee)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Failed to encode content URL for node: " + ref, uee);
|
||||
}
|
||||
|
||||
return url;
|
||||
return generateUrl(BROWSER_URL, ref, name);
|
||||
}
|
||||
}
|
||||
|
@@ -145,12 +145,31 @@ public final class FacesHelper
|
||||
else
|
||||
{
|
||||
// make sure we do not have illegal characters in the id
|
||||
id = makeLegalId(id);
|
||||
}
|
||||
|
||||
component.setId(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the given id a legal JSF component id by replacing illegal
|
||||
* characters with underscores.
|
||||
*
|
||||
* @param id The id to make legal
|
||||
* @return The legalised id
|
||||
*/
|
||||
public static String makeLegalId(String id)
|
||||
{
|
||||
if (id != null)
|
||||
{
|
||||
// replace illegal ID characters with an underscore
|
||||
id = id.replace(':', '_');
|
||||
id = id.replace(' ', '_');
|
||||
|
||||
// TODO: check all other illegal characters - only allowed dash and underscore
|
||||
}
|
||||
|
||||
component.setId(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Mozilla Public License version 1.1
|
||||
* with a permitted attribution clause. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfresco.org/legal/license.txt
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific
|
||||
* language governing permissions and limitations under the
|
||||
* License.
|
||||
*/
|
||||
package org.alfresco.web.app.servlet;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.security.PermissionService;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Servlet responsible for streaming node content from the repo directly to the response stream.
|
||||
* The appropriate mimetype is calculated based on filename extension.
|
||||
* <p>
|
||||
* The URL to the servlet should be generated thus:
|
||||
* <pre>/alfresco/guestDownload/attach/workspace/SpacesStore/0000-0000-0000-0000/myfile.pdf</pre>
|
||||
* or
|
||||
* <pre>/alfresco/guestDownload/direct/workspace/SpacesStore/0000-0000-0000-0000/myfile.pdf</pre>
|
||||
* or
|
||||
* <pre>/alfresco/guestDownload/[direct|attach]?path=/Company%20Home/MyFolder/myfile.pdf</pre>
|
||||
* The protocol, followed by either the store and Id (NodeRef) or instead specify a name based
|
||||
* encoded Path to the content, note that the filename element is used for mimetype lookup and
|
||||
* as the returning filename for the response stream.
|
||||
* <p>
|
||||
* The 'attach' or 'direct' element is used to indicate whether to display the stream directly
|
||||
* in the browser or download it as a file attachment.
|
||||
* <p>
|
||||
* By default, the download assumes that the content is on the
|
||||
* {@link org.alfresco.model.ContentModel#PROP_CONTENT content property}.<br>
|
||||
* To retrieve the content of a specific model property, use a 'property' arg, providing the workspace,
|
||||
* node ID AND the qualified name of the property.
|
||||
* <p>
|
||||
* This servlet only accesses content available to the guest user. If the guest user does not
|
||||
* have access to the requested a 401 Forbidden response is returned to the caller.
|
||||
* <p>
|
||||
* This servlet does not effect the current session, therefore if guest access is required to a
|
||||
* resource this servlet can be used without logging out the current user.
|
||||
*
|
||||
* @author gavinc
|
||||
*/
|
||||
public class GuestDownloadContentServlet extends BaseDownloadContentServlet
|
||||
{
|
||||
private static final long serialVersionUID = -5258137503339817457L;
|
||||
|
||||
private static Log logger = LogFactory.getLog(GuestDownloadContentServlet.class);
|
||||
|
||||
private static final String DOWNLOAD_URL = "/guestDownload/attach/{0}/{1}/{2}/{3}";
|
||||
private static final String BROWSER_URL = "/guestDownload/direct/{0}/{1}/{2}/{3}";
|
||||
|
||||
@Override
|
||||
protected Log getLogger()
|
||||
{
|
||||
return logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
|
||||
*/
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse res)
|
||||
throws ServletException, IOException
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
String queryString = req.getQueryString();
|
||||
logger.debug("Setting up guest access to URL: " + req.getRequestURI() +
|
||||
((queryString != null && queryString.length() > 0) ? ("?" + queryString) : ""));
|
||||
}
|
||||
|
||||
DownloadContentWork dcw = new DownloadContentWork(req, res);
|
||||
AuthenticationUtil.runAs(dcw, PermissionService.GUEST_AUTHORITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to generate a URL to a content node for downloading content from the server.
|
||||
* The content is supplied as an HTTP1.1 attachment to the response. This generally means
|
||||
* a browser should prompt the user to save the content to specified location.
|
||||
*
|
||||
* @param ref NodeRef of the content node to generate URL for (cannot be null)
|
||||
* @param name File name to return in the URL (cannot be null)
|
||||
*
|
||||
* @return URL to download the content from the specified node
|
||||
*/
|
||||
public final static String generateDownloadURL(NodeRef ref, String name)
|
||||
{
|
||||
return generateUrl(DOWNLOAD_URL, ref, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to generate a URL to a content node for downloading content from the server.
|
||||
* The content is supplied directly in the reponse. This generally means a browser will
|
||||
* attempt to open the content directly if possible, else it will prompt to save the file.
|
||||
*
|
||||
* @param ref NodeRef of the content node to generate URL for (cannot be null)
|
||||
* @param name File name to return in the URL (cannot be null)
|
||||
*
|
||||
* @return URL to download the content from the specified node
|
||||
*/
|
||||
public final static String generateBrowserURL(NodeRef ref, String name)
|
||||
{
|
||||
return generateUrl(BROWSER_URL, ref, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to wrap the call to processDownloadRequest.
|
||||
*
|
||||
* @author gavinc
|
||||
*/
|
||||
public class DownloadContentWork implements RunAsWork<Object>
|
||||
{
|
||||
private HttpServletRequest req = null;
|
||||
private HttpServletResponse res = null;
|
||||
|
||||
public DownloadContentWork(HttpServletRequest req, HttpServletResponse res)
|
||||
{
|
||||
this.req = req;
|
||||
this.res = res;
|
||||
}
|
||||
|
||||
public Object doWork() throws Exception
|
||||
{
|
||||
processDownloadRequest(this.req, this.res, false);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -86,8 +86,6 @@ public class TemplateContentServlet extends BaseServlet
|
||||
private static final String DEFAULT_URL = "/template/{0}/{1}/{2}";
|
||||
private static final String TEMPLATE_URL = "/template/{0}/{1}/{2}/{3}/{4}/{5}";
|
||||
|
||||
private static final String MSG_ERROR_CONTENT_MISSING = "error_content_missing";
|
||||
|
||||
private static final String ARG_MIMETYPE = "mimetype";
|
||||
private static final String ARG_TEMPLATE_PATH = "templatePath";
|
||||
private static final String ARG_CONTEXT_PATH = "contextPath";
|
||||
@@ -95,13 +93,17 @@ public class TemplateContentServlet extends BaseServlet
|
||||
/**
|
||||
* @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
|
||||
*/
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse res)
|
||||
protected void service(HttpServletRequest req, HttpServletResponse res)
|
||||
throws ServletException, IOException
|
||||
{
|
||||
String uri = req.getRequestURI();
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("Processing URL: " + uri + (req.getQueryString() != null ? ("?" + req.getQueryString()) : ""));
|
||||
{
|
||||
String queryString = req.getQueryString();
|
||||
logger.debug("Processing URL: " + uri +
|
||||
((queryString != null && queryString.length() > 0) ? ("?" + queryString) : ""));
|
||||
}
|
||||
|
||||
AuthenticationStatus status = servletAuthenticate(req, res);
|
||||
if (status == AuthenticationStatus.Failure)
|
||||
@@ -258,6 +260,7 @@ public class TemplateContentServlet extends BaseServlet
|
||||
*
|
||||
* @return an object model ready for executing template against
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private Object getModel(ServiceRegistry services, HttpServletRequest req, NodeRef templateRef, NodeRef nodeRef)
|
||||
{
|
||||
// build FreeMarker default model and merge
|
||||
|
@@ -22,7 +22,7 @@ public interface AjaxCommand
|
||||
* expression. Parameters required to call the method can be retrieved
|
||||
* from the request.
|
||||
*
|
||||
* Currently the content type of the response will always be text/html, in the
|
||||
* Currently the content type of the response will always be text/xml, in the
|
||||
* future sublcasses may provide a mechanism to allow the content type to be set
|
||||
* dynamically.
|
||||
*
|
||||
|
@@ -42,7 +42,7 @@ public class InvokeCommand extends BaseAjaxCommand
|
||||
|
||||
// NOTE: it doesn't seem to matter what the content type of the response is (at least with Dojo),
|
||||
// it determines it's behaviour from the mimetype specified in the AJAX call on the client,
|
||||
// therefore, for now we will always return a content type of text/html.
|
||||
// therefore, for now we will always return a content type of text/xml.
|
||||
// In the future we may use annotations on the method to be called to specify what content
|
||||
// type should be used for the response.
|
||||
|
||||
@@ -53,7 +53,7 @@ public class InvokeCommand extends BaseAjaxCommand
|
||||
RenderKit renderKit = renderFactory.getRenderKit(facesContext,
|
||||
viewRoot.getRenderKitId());
|
||||
ResponseWriter writer = renderKit.createResponseWriter(
|
||||
new OutputStreamWriter(os), MimetypeMap.MIMETYPE_HTML, "UTF-8");
|
||||
new OutputStreamWriter(os), MimetypeMap.MIMETYPE_XML, "UTF-8");
|
||||
facesContext.setResponseWriter(writer);
|
||||
response.setContentType(writer.getContentType());
|
||||
|
||||
|
Reference in New Issue
Block a user