Java tutorial
/* * #%L * Alfresco Repository WAR Community * %% * Copyright (C) 2005 - 2016 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of * the paid license agreement will prevail. Otherwise, the software is * provided under the following open source license terms: * * Alfresco is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Alfresco is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * #L% */ package org.alfresco.web.app.servlet; import java.io.IOException; import java.io.PrintWriter; import java.net.URLEncoder; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.StringTokenizer; import javax.faces.context.FacesContext; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; 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.repo.tenant.TenantService; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.model.FileNotFoundException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.security.AccessStatus; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.web.app.Application; import org.alfresco.web.bean.LoginOutcomeBean; import org.alfresco.web.bean.repository.Repository; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.extensions.surf.util.URLDecoder; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; import org.springframework.web.jsf.FacesContextUtils; /** * Base servlet class containing useful constant values and common methods for Alfresco servlets. * * @author Kevin Roast */ public abstract class BaseServlet extends HttpServlet { private static final long serialVersionUID = -826295358696861789L; public static final String FACES_SERVLET = "/faces"; public static final String KEY_STORE = "store"; public static final String KEY_ROOT_PATH = "rootPath"; /** an existing Ticket can be passed to most servlet for non-session based authentication */ private static final String ARG_TICKET = "ticket"; /** forcing guess access is available on most servlets */ private static final String ARG_GUEST = "guest"; private static final String MSG_ERROR_PERMISSIONS = "error_permissions"; /** list of valid JSPs for redirect after a clean login */ // TODO: make this list configurable private static Set<String> validRedirectJSPs = new HashSet<String>(); static { validRedirectJSPs.add("/jsp/browse/browse.jsp"); validRedirectJSPs.add("/jsp/admin/admin-console.jsp"); validRedirectJSPs.add("/jsp/admin/avm-console.jsp"); validRedirectJSPs.add("/jsp/admin/node-browser.jsp"); validRedirectJSPs.add("/jsp/admin/store-browser.jsp"); validRedirectJSPs.add("/jsp/users/user-console.jsp"); validRedirectJSPs.add("/jsp/categories/categories.jsp"); validRedirectJSPs.add("/jsp/dialog/about.jsp"); validRedirectJSPs.add("/jsp/search/advanced-search.jsp"); validRedirectJSPs.add("/jsp/admin/system-info.jsp"); validRedirectJSPs.add("/jsp/forums/forums.jsp"); validRedirectJSPs.add("/jsp/users/users.jsp"); validRedirectJSPs.add("/jsp/trashcan/trash-list.jsp"); } private static Log logger = LogFactory.getLog(BaseServlet.class); /** * Return the ServiceRegistry helper instance * * @param sc ServletContext * * @return ServiceRegistry */ public static ServiceRegistry getServiceRegistry(ServletContext sc) { WebApplicationContext wc = WebApplicationContextUtils.getRequiredWebApplicationContext(sc); return (ServiceRegistry) wc.getBean(ServiceRegistry.SERVICE_REGISTRY); } /** * Perform an authentication for the servlet request URI. Processing any "ticket" or * "guest" URL arguments. * * @return AuthenticationStatus * * @throws IOException */ public AuthenticationStatus servletAuthenticate(HttpServletRequest req, HttpServletResponse res) throws IOException { return servletAuthenticate(req, res, true); } /** * Perform an authentication for the servlet request URI. Processing any "ticket" or * "guest" URL arguments. * * @return AuthenticationStatus * * @throws IOException */ public AuthenticationStatus servletAuthenticate(HttpServletRequest req, HttpServletResponse res, boolean redirectToLoginPage) throws IOException { AuthenticationStatus status; // see if a ticket or a force Guest parameter has been supplied String ticket = req.getParameter(ARG_TICKET); if (ticket != null && ticket.length() != 0) { status = AuthenticationHelper.authenticate(getServletContext(), req, res, ticket); } else { boolean forceGuest = false; String guest = req.getParameter(ARG_GUEST); if (guest != null) { forceGuest = Boolean.parseBoolean(guest); } status = AuthenticationHelper.authenticate(getServletContext(), req, res, forceGuest); } if (status == AuthenticationStatus.Failure && redirectToLoginPage) { // authentication failed - now need to display the login page to the user, if asked to redirectToLoginPage(req, res, getServletContext()); } return status; } /** * Check the user has the given permission on the given node. If they do not either force a log on if this is a guest * user or forward to an error page. * * @param req * the request * @param res * the response * @param nodeRef * the node in question * @param allowLogIn * Indicates whether guest users without access to the node should be redirected to the log in page. If * <code>false</code>, a status 403 forbidden page is displayed instead. * @return <code>true</code>, if the user has access * @throws IOException * Signals that an I/O exception has occurred. * @throws ServletException * On other errors */ public boolean checkAccess(HttpServletRequest req, HttpServletResponse res, NodeRef nodeRef, String permission, boolean allowLogIn) throws IOException, ServletException { ServletContext sc = getServletContext(); ServiceRegistry serviceRegistry = getServiceRegistry(sc); PermissionService permissionService = serviceRegistry.getPermissionService(); // check that the user has the permission if (permissionService.hasPermission(nodeRef, permission) == AccessStatus.DENIED) { if (logger.isDebugEnabled()) logger.debug("User does not have " + permission + " permission for NodeRef: " + nodeRef.toString()); if (allowLogIn && serviceRegistry.getAuthorityService().hasGuestAuthority()) { if (logger.isDebugEnabled()) logger.debug("Redirecting to login page..."); redirectToLoginPage(req, res, sc); } else { if (logger.isDebugEnabled()) logger.debug("Forwarding to error page..."); Application.handleSystemError(sc, req, res, MSG_ERROR_PERMISSIONS, HttpServletResponse.SC_FORBIDDEN, logger); } return false; } return true; } /** * Redirect to the Login page - saving the current URL which can be redirected back later * once the user has successfully completed the authentication process. */ public static void redirectToLoginPage(HttpServletRequest req, HttpServletResponse res, ServletContext sc) throws IOException { redirectToLoginPage(req, res, sc, AuthenticationHelper.getRemoteUserMapper(sc) == null); } /** * Redirect to the Login page - saving the current URL which can be redirected back later * once the user has successfully completed the authentication process. * @param sendRedirect allow a redirect status code to be set? If <code>false</code> redirect * will be via markup rather than status code (to allow the status code to be used for handshake * responses etc. */ public static void redirectToLoginPage(HttpServletRequest req, HttpServletResponse res, ServletContext sc, boolean sendRedirect) throws IOException { // Pass the full requested URL as a parameter so the login page knows where to redirect to later final String uri = req.getRequestURI(); String redirectURL = uri; // authentication failed - so end servlet execution and redirect to login page if (WebApplicationContextUtils.getRequiredWebApplicationContext(sc) .containsBean(Application.BEAN_CONFIG_SERVICE)) { StringBuilder redirect = new StringBuilder(128).append(req.getContextPath()).append(FACES_SERVLET) .append(Application.getLoginPage(sc)); // if we find a JSF servlet reference in the URI then we need to check if the rest of the // JSP specified is valid for a redirect operation after Login has occured. int jspIndex; if (uri.indexOf(req.getContextPath() + FACES_SERVLET) == -1 || uri .length() > (jspIndex = uri.indexOf(BaseServlet.FACES_SERVLET) + BaseServlet.FACES_SERVLET.length()) && BaseServlet.validRedirectJSP(uri.substring(jspIndex))) { if (redirect.indexOf("?") == -1) { redirect.append('?'); } else { redirect.append('&'); } redirect.append(LoginOutcomeBean.PARAM_REDIRECT_URL); redirect.append('='); String url = uri; // Append the query string if necessary String queryString = req.getQueryString(); if (queryString != null) { // Strip out leading ticket arguments queryString = queryString.replaceAll("(?<=^|&)" + ARG_TICKET + "(=[^&=]*)?&", ""); // Strip out trailing ticket arguments queryString = queryString.replaceAll("(^|&)" + ARG_TICKET + "(=[^&=]*)?(?=&|$)", ""); if (queryString.length() != 0) { url += "?" + queryString; } } redirect.append(URLEncoder.encode(url, "UTF-8")); } redirectURL = redirect.toString(); } // If external authentication isn't in use (e.g. proxied share authentication), it's safe to return a redirect to the client if (sendRedirect) { res.sendRedirect(redirectURL); } // Otherwise, we must signal to the client with an unauthorized status code and rely on a browser refresh to do // the redirect for failover login (as we do with NTLM, Kerberos) else { res.setContentType("text/html; charset=UTF-8"); res.setStatus(HttpServletResponse.SC_UNAUTHORIZED); final PrintWriter out = res.getWriter(); out.println("<html><head>"); out.println("<meta http-equiv=\"Refresh\" content=\"0; url=" + redirectURL + "\">"); out.println("</head><body><p>Please <a href=\"" + redirectURL + "\">log in</a>.</p>"); out.println("</body></html>"); out.close(); } } /** * Apply the headers required to disallow caching of the response in the browser */ public static void setNoCacheHeaders(HttpServletResponse res) { res.setHeader("Cache-Control", "no-cache"); res.setHeader("Pragma", "no-cache"); } /** * Returns true if the specified JSP file is valid for a redirect after login. * Only a specific sub-set of the available JSPs are valid to jump directly too after a * clean login attempt - e.g. those that do not require JSF bean context setup. This is * a limitation of the JSP architecture. The ExternalAccessServlet provides a mechanism to * setup the JSF bean context directly for some specific cases. * * @param jsp Filename of JSP to check, for example "/jsp/browse/browse.jsp" * * @return true if the JSP is in the list of valid direct URLs, false otherwise */ public static boolean validRedirectJSP(String jsp) { return validRedirectJSPs.contains(jsp); } /** * Resolves the given path elements to a NodeRef in the current repository * * @param context Faces context * @param args The elements of the path to lookup */ public static NodeRef resolveWebDAVPath(FacesContext context, String[] args) { WebApplicationContext wc = FacesContextUtils.getRequiredWebApplicationContext(context); return resolveWebDAVPath(wc, args, true); } /** * Resolves the given path elements to a NodeRef in the current repository * * @param context Faces context * @param args The elements of the path to lookup * @param decode True to decode the arg from UTF-8 format, false for no decoding */ public static NodeRef resolveWebDAVPath(FacesContext context, String[] args, boolean decode) { WebApplicationContext wc = FacesContextUtils.getRequiredWebApplicationContext(context); return resolveWebDAVPath(wc, args, decode); } /** * Resolves the given path elements to a NodeRef in the current repository * * @param context ServletContext context * @param args The elements of the path to lookup */ public static NodeRef resolveWebDAVPath(ServletContext context, String[] args) { WebApplicationContext wc = WebApplicationContextUtils.getRequiredWebApplicationContext(context); return resolveWebDAVPath(wc, args, true); } /** * Resolves the given path elements to a NodeRef in the current repository * * @param context ServletContext context * @param args The elements of the path to lookup * @param decode True to decode the arg from UTF-8 format, false for no decoding */ public static NodeRef resolveWebDAVPath(ServletContext context, String[] args, boolean decode) { WebApplicationContext wc = WebApplicationContextUtils.getRequiredWebApplicationContext(context); return resolveWebDAVPath(wc, args, decode); } /** * Resolves the given path elements to a NodeRef in the current repository * * @param wc WebApplicationContext Context * @param args The elements of the path to lookup * @param decode True to decode the arg from UTF-8 format, false for no decoding */ private static NodeRef resolveWebDAVPath(final WebApplicationContext wc, final String[] args, final boolean decode) { return AuthenticationUtil.runAs(new RunAsWork<NodeRef>() { public NodeRef doWork() throws Exception { NodeRef nodeRef = null; List<String> paths = new ArrayList<String>(args.length - 1); FileInfo file = null; try { // create a list of path elements (decode the URL as we go) for (int x = 1; x < args.length; x++) { paths.add(decode ? URLDecoder.decode(args[x]) : args[x]); } if (logger.isDebugEnabled()) logger.debug("Attempting to resolve webdav path: " + paths); // get the company home node to start the search from nodeRef = new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId()); TenantService tenantService = (TenantService) wc.getBean("tenantService"); if (tenantService != null && tenantService.isEnabled()) { if (logger.isDebugEnabled()) logger.debug("MT is enabled."); NodeService nodeService = (NodeService) wc.getBean("NodeService"); SearchService searchService = (SearchService) wc.getBean("SearchService"); NamespaceService namespaceService = (NamespaceService) wc.getBean("NamespaceService"); // TODO: since these constants are used more widely than just the WebDAVServlet, // they should be defined somewhere other than in that servlet String rootPath = wc.getServletContext().getInitParameter(BaseServlet.KEY_ROOT_PATH); // note: rootNodeRef is required (for storeRef part) nodeRef = tenantService.getRootNode(nodeService, searchService, namespaceService, rootPath, nodeRef); } if (paths.size() != 0) { FileFolderService ffs = (FileFolderService) wc.getBean("FileFolderService"); file = ffs.resolveNamePath(nodeRef, paths); nodeRef = file.getNodeRef(); } if (logger.isDebugEnabled()) logger.debug("Resolved webdav path to NodeRef: " + nodeRef); } catch (FileNotFoundException fne) { if (logger.isWarnEnabled()) logger.warn("Failed to resolve webdav path", fne); nodeRef = null; } return nodeRef; } }, AuthenticationUtil.getSystemUserName()); } /** * Resolve a name based into a NodeRef and Filename string * * @param sc ServletContext * @param path 'cm:name' based path using the '/' character as a separator * * @return PathRefInfo structure containing the resolved NodeRef and filename * * @throws IllegalArgumentException */ public final static PathRefInfo resolveNamePath(ServletContext sc, String path) { StringTokenizer t = new StringTokenizer(path, "/"); int tokenCount = t.countTokens(); String[] elements = new String[tokenCount]; for (int i = 0; i < tokenCount; i++) { elements[i] = t.nextToken(); } // process name based path tokens using the webdav path resolving helper NodeRef nodeRef = resolveWebDAVPath(sc, elements, false); if (nodeRef == null) { // unable to resolve path - output helpful error to the user throw new IllegalArgumentException("Unable to resolve item Path: " + path); } return new PathRefInfo(nodeRef, elements[tokenCount - 1]); } /** * Simple structure class for returning both a NodeRef and Filename String * @author Kevin Roast */ public static class PathRefInfo { PathRefInfo(NodeRef ref, String filename) { this.NodeRef = ref; this.Filename = filename; } public NodeRef NodeRef; public String Filename; } }