001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.main; 029 030import org.opencms.configuration.CmsParameterConfiguration; 031import org.opencms.configuration.I_CmsConfigurationParameterHandler; 032import org.opencms.configuration.I_CmsNeedsAdminCmsObject; 033import org.opencms.file.CmsObject; 034import org.opencms.file.CmsResource; 035import org.opencms.file.CmsResourceFilter; 036import org.opencms.file.CmsVfsResourceNotFoundException; 037import org.opencms.file.types.I_CmsResourceType; 038import org.opencms.relations.CmsLink; 039import org.opencms.relations.I_CmsCustomLinkRenderer; 040import org.opencms.security.CmsPermissionViolationException; 041import org.opencms.util.CmsFileUtil; 042import org.opencms.util.CmsStringUtil; 043import org.opencms.xml.xml2json.I_CmsApiAuthorizationHandler; 044 045import java.io.IOException; 046import java.net.URI; 047import java.net.URISyntaxException; 048import java.util.regex.Pattern; 049 050import javax.servlet.http.HttpServletRequest; 051import javax.servlet.http.HttpServletResponse; 052 053import org.apache.commons.logging.Log; 054 055/** 056 * Resource init handler that provides an alternative way of serving static files like images or binary files, using the API authorization mechanism 057 * instead of the normal authorization handler. 058 * 059 * <p>Resources are accessed by appending their VFS root path to the /staticresource handler path. When resources are requested this way, they are still 060 * loaded with the normal OpenCms loader mechanism. This works for the intended use case (binary files, images) but may not work for other types. 061 * 062 * <p>The resources accessible through this handler can be restricted by setting regex configuration parameters for path and type which the requested resources 063 * have to match. 064 * 065 * <p>This can be used in combination with the CmsJsonResourceHandler class. When configured correctly (using the linkrewrite.id parameter on this handler, 066 * and a matching linkrewrite.refid on the CmsJsonResourceHandler), links to resources this handler is responsible for will be rewritten to point to the URL 067 * for the resource using this handler. 068 */ 069public class CmsProtectedStaticFileHandler 070implements I_CmsResourceInit, I_CmsConfigurationParameterHandler, I_CmsNeedsAdminCmsObject, I_CmsCustomLinkRenderer { 071 072 /** Parameter for defining the id under which the link renderer should be registered. */ 073 public static final String PARAM_LINKREWRITE_ID = "linkrewrite.id"; 074 075 /** Configuration parameter that determines which authorization method to use. */ 076 public static final String PARAM_AUTHORIZATION = "authorization"; 077 078 /** Configuration parameter for the path filter regex. */ 079 public static final String PARAM_PATHFILTER = "pathfilter"; 080 081 /** Configuration parameter for the type filter regex. */ 082 public static final String PARAM_TYPEFILTER = "typefilter"; 083 084 /** URL prefix. */ 085 public static final String PREFIX = "/staticresource"; 086 087 /** Logger instance for this class. */ 088 private static final Log LOG = CmsLog.getLog(CmsProtectedStaticFileHandler.class); 089 090 public static final String PARAM_LINKREWRITE_PREFIX = "linkrewrite.prefix"; 091 092 /** The Admin CMS context. */ 093 private CmsObject m_adminCms; 094 095 /** Configuration from config file. */ 096 private CmsParameterConfiguration m_config = new CmsParameterConfiguration(); 097 098 /** Regex for matching paths. */ 099 private Pattern m_pathFilter; 100 101 /** Regex for matching types. */ 102 private Pattern m_typeFilter; 103 104 /** The link rewrite prefix. */ 105 private String m_linkRewritePrefix; 106 107 /** 108 * Merges a link prefix with additional link components. 109 * 110 * @param prefix the prefix 111 * @param path the path 112 * @param query the query 113 * 114 * @return the combined link 115 */ 116 public static String mergeLinkPrefix(String prefix, String path, String query) { 117 118 try { 119 URI baseUri = new URI(prefix); 120 URI correctedUri = new URI( 121 baseUri.getScheme(), 122 baseUri.getAuthority(), 123 CmsStringUtil.joinPaths(baseUri.getPath(), PREFIX, path), 124 query, 125 null); 126 return correctedUri.toASCIIString(); 127 } catch (URISyntaxException e) { 128 LOG.error(e.getLocalizedMessage(), e); 129 return null; 130 } 131 132 } 133 134 /** 135 * Helper method for authorizing requests based on a comma-separated list of API authorization handler names. 136 * 137 * <p>This will evaluate each authorization handler from authChain and return the first non-null CmsObject returned. 138 * A special case is authChain contains the word 'default', this is not u 139 * 140 * <p>Returns null if the authorization failed. 141 * 142 * @param adminCms the Admin CmsObject 143 * @param defaultCms the current CmsObject with the default user data from the request 144 * @param request the current request 145 * @param authChain a comma-separated list of API authorization handler names 146 * 147 * @return the initialized CmsObject 148 */ 149 private static CmsObject authorize( 150 CmsObject adminCms, 151 CmsObject defaultCms, 152 HttpServletRequest request, 153 String authChain) { 154 155 if (authChain == null) { 156 return defaultCms; 157 } 158 for (String token : authChain.split(",")) { 159 token = token.trim(); 160 if ("default".equals(token)) { 161 LOG.info("Using default CmsObject"); 162 return defaultCms; 163 } else if ("guest".equals(token)) { 164 try { 165 return OpenCms.initCmsObject(OpenCms.getDefaultUsers().getUserGuest()); 166 } catch (CmsException e) { 167 LOG.error(e.getLocalizedMessage(), e); 168 return null; 169 } 170 } else { 171 I_CmsApiAuthorizationHandler handler = OpenCms.getApiAuthorization(token); 172 if (handler == null) { 173 LOG.error("Could not find API authorization handler " + token); 174 return null; 175 } else { 176 try { 177 CmsObject cms = handler.initCmsObject(adminCms, request); 178 if (cms != null) { 179 LOG.info("Succeeded with authorization handler: " + token); 180 return cms; 181 } 182 } catch (CmsException e) { 183 LOG.error("Error evaluating authorization handler " + token); 184 return null; 185 } 186 } 187 } 188 } 189 LOG.info("Authentication unsusccessful"); 190 return null; 191 } 192 193 /** 194 * @see org.opencms.configuration.I_CmsConfigurationParameterHandler#addConfigurationParameter(java.lang.String, java.lang.String) 195 */ 196 public void addConfigurationParameter(String paramName, String paramValue) { 197 198 m_config.add(paramName, paramValue); 199 } 200 201 /** 202 * @see org.opencms.configuration.I_CmsConfigurationParameterHandler#getConfiguration() 203 */ 204 public CmsParameterConfiguration getConfiguration() { 205 206 return m_config; 207 } 208 209 /** 210 * @see org.opencms.relations.I_CmsCustomLinkRenderer#getLink(org.opencms.file.CmsObject, org.opencms.relations.CmsLink) 211 */ 212 public String getLink(CmsObject cms, CmsLink link) { 213 214 try { 215 CmsObject adminCms = OpenCms.initCmsObject(m_adminCms); 216 adminCms.getRequestContext().setCurrentProject(cms.getRequestContext().getCurrentProject()); 217 link.checkConsistency(adminCms); 218 219 if (checkResourceAccessible(link.getResource())) { 220 return mergeLinkPrefix(m_linkRewritePrefix, link.getResource().getRootPath(), link.getQuery()); 221 } 222 return null; 223 } catch (CmsException e) { 224 LOG.warn(e.getLocalizedMessage(), e); 225 return null; 226 } 227 } 228 229 /** 230 * @see org.opencms.relations.I_CmsCustomLinkRenderer#getLink(org.opencms.file.CmsObject, org.opencms.file.CmsResource) 231 */ 232 public String getLink(CmsObject cms, CmsResource resource) { 233 234 if (checkResourceAccessible(resource)) { 235 return mergeLinkPrefix(m_linkRewritePrefix, resource.getRootPath(), null); 236 } 237 return null; 238 } 239 240 /** 241 * @see org.opencms.configuration.I_CmsConfigurationParameterHandler#initConfiguration() 242 */ 243 public void initConfiguration() { 244 245 m_config = CmsParameterConfiguration.unmodifiableVersion(m_config); 246 m_pathFilter = Pattern.compile(m_config.getString(PARAM_PATHFILTER, ".*")); 247 m_typeFilter = Pattern.compile(m_config.getString(PARAM_TYPEFILTER, "image|text|binary")); 248 String linkRewriteId = m_config.getString(PARAM_LINKREWRITE_ID, null); 249 if (linkRewriteId != null) { 250 OpenCms.setRuntimeProperty(linkRewriteId, this); 251 } 252 m_linkRewritePrefix = m_config.getString(PARAM_LINKREWRITE_PREFIX, null); 253 } 254 255 /** 256 * @see org.opencms.main.I_CmsResourceInit#initResource(org.opencms.file.CmsResource, org.opencms.file.CmsObject, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) 257 */ 258 public CmsResource initResource(CmsResource origRes, CmsObject cms, HttpServletRequest req, HttpServletResponse res) 259 throws CmsResourceInitException { 260 261 String uri = cms.getRequestContext().getUri(); 262 263 if (origRes != null) { 264 return origRes; 265 } 266 if (res == null) { 267 // called from locale handler 268 return origRes; 269 } 270 if (!CmsStringUtil.isPrefixPath(PREFIX, uri)) { 271 return null; 272 } 273 String path = uri.substring(PREFIX.length()); 274 if (path.isEmpty()) { 275 path = "/"; 276 } else if (path.length() > 1) { 277 path = CmsFileUtil.removeTrailingSeparator(path); 278 } 279 280 String authorizationParam = m_config.get(PARAM_AUTHORIZATION); 281 CmsObject origCms = cms; 282 cms = authorize(m_adminCms, origCms, req, authorizationParam); 283 if ((cms != null) && (cms != origCms)) { 284 origCms.getRequestContext().setAttribute(I_CmsResourceInit.ATTR_ALTERNATIVE_CMS_OBJECT, cms); 285 cms.getRequestContext().setSiteRoot(origCms.getRequestContext().getSiteRoot()); 286 cms.getRequestContext().setUri(origCms.getRequestContext().getUri()); 287 } 288 int status = 200; 289 try { 290 CmsObject rootCms = OpenCms.initCmsObject(cms); 291 rootCms.getRequestContext().setSiteRoot(""); 292 if (m_pathFilter.matcher(path).matches()) { 293 CmsResource resource = rootCms.readResource(path, CmsResourceFilter.IGNORE_EXPIRATION); 294 if (!checkResourceAccessible(resource)) { 295 status = HttpServletResponse.SC_FORBIDDEN; 296 } else { 297 return resource; 298 } 299 } 300 status = HttpServletResponse.SC_NOT_FOUND; 301 } catch (CmsPermissionViolationException e) { 302 if (OpenCms.getDefaultUsers().isUserGuest(cms.getRequestContext().getCurrentUser().getName())) { 303 status = HttpServletResponse.SC_UNAUTHORIZED; 304 } else { 305 status = HttpServletResponse.SC_FORBIDDEN; 306 } 307 } catch (CmsVfsResourceNotFoundException e) { 308 status = HttpServletResponse.SC_NOT_FOUND; 309 } catch (CmsException e) { 310 LOG.error(e.getLocalizedMessage(), e); 311 status = HttpServletResponse.SC_INTERNAL_SERVER_ERROR; 312 } 313 try { 314 res.sendError(status); 315 } catch (IOException e) { 316 LOG.error(e.getLocalizedMessage(), e); 317 } 318 CmsResourceInitException ex = new CmsResourceInitException(CmsProtectedStaticFileHandler.class); 319 ex.setClearErrors(true); 320 throw ex; 321 } 322 323 /** 324 * @see org.opencms.configuration.I_CmsNeedsAdminCmsObject#setAdminCmsObject(org.opencms.file.CmsObject) 325 */ 326 public void setAdminCmsObject(CmsObject adminCms) { 327 328 m_adminCms = adminCms; 329 330 } 331 332 /** 333 * Checks if the resource is not hidden according to the filters configured in the resource handler parameters. 334 * 335 * @param res the resource to check 336 * @return true if the resource is accessible 337 */ 338 private boolean checkResourceAccessible(CmsResource res) { 339 340 return (res != null) && m_pathFilter.matcher(res.getRootPath()).matches() && checkType(res.getTypeId()); 341 } 342 343 /** 344 * Checks that the type matches the configured type filter 345 * 346 * @param typeId a type id 347 * @return true if the type matches the configured type filter 348 */ 349 private boolean checkType(int typeId) { 350 351 try { 352 I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(typeId); 353 return m_typeFilter.matcher(type.getTypeName()).matches(); 354 } catch (Exception e) { 355 LOG.error("Missing type with id: " + typeId); 356 return false; 357 } 358 359 } 360 361}