001/* 002 * Copyright 2015 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.plugins.serverdirectory; 017 018import java.io.UnsupportedEncodingException; 019import java.net.URI; 020import java.net.URISyntaxException; 021import java.net.URLDecoder; 022import java.util.Base64; 023import java.util.HashMap; 024import java.util.Map; 025import java.util.Set; 026 027import org.apache.avalon.framework.parameters.Parameters; 028import org.apache.avalon.framework.service.ServiceException; 029import org.apache.avalon.framework.service.ServiceManager; 030import org.apache.cocoon.acting.ServiceableAction; 031import org.apache.cocoon.environment.ObjectModelHelper; 032import org.apache.cocoon.environment.Redirector; 033import org.apache.cocoon.environment.Request; 034import org.apache.cocoon.environment.Response; 035import org.apache.cocoon.environment.SourceResolver; 036import org.apache.commons.lang.StringUtils; 037import org.apache.excalibur.source.Source; 038 039import org.ametys.core.right.RightManager; 040import org.ametys.core.user.CurrentUserProvider; 041import org.ametys.core.user.UserIdentity; 042import org.ametys.plugins.repository.AmetysObjectResolver; 043import org.ametys.plugins.repository.AmetysRepositoryException; 044import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder; 045import org.ametys.runtime.authentication.AccessDeniedException; 046import org.ametys.runtime.authentication.AuthorizationRequiredException; 047import org.ametys.web.repository.page.Page; 048import org.ametys.web.repository.page.ZoneItem; 049 050/** 051 * Check if we are authorized to access to the path 052 */ 053public class CheckPathAccessAction extends ServiceableAction 054{ 055 private org.apache.excalibur.source.SourceResolver _srcResolver; 056 private AmetysObjectResolver _resolver; 057 private CurrentUserProvider _currentUserProvider; 058 private RightManager _rightManager; 059 060 @Override 061 public void service(ServiceManager smanager) throws ServiceException 062 { 063 super.service(smanager); 064 _srcResolver = (org.apache.excalibur.source.SourceResolver) smanager.lookup(org.apache.excalibur.source.SourceResolver.ROLE); 065 _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); 066 _currentUserProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE); 067 _rightManager = (RightManager) smanager.lookup(RightManager.ROLE); 068 } 069 070 public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception 071 { 072 Map<String, Object> result = new HashMap<>(); 073 074 Request request = ObjectModelHelper.getRequest(objectModel); 075 String path = request.getParameter("path"); 076 String name = request.getParameter("name"); 077 078 if (StringUtils.isBlank(path)) 079 { 080 throw new IllegalArgumentException("Missing server directory's path"); 081 } 082 if (StringUtils.isBlank(name)) 083 { 084 throw new IllegalArgumentException("Missing server file's name"); 085 } 086 087 String decodedPath = ServerDirectoryHelper.normalize(_decodePath(path)); 088 089 String zoneItemEncoded = parameters.getParameter("zoneItem"); 090 String zoneItemId = new String(Base64.getUrlDecoder().decode(zoneItemEncoded.getBytes("UTF-8"))); 091 ZoneItem zoneItem = (ZoneItem) _resolver.resolveById(zoneItemId); 092 093 // Check page to page 094 _checkPageAccess(zoneItem); 095 096 ModelAwareDataHolder serviceParameters = zoneItem.getServiceParameters(); 097 boolean enableDynamicPaths = serviceParameters.getValue("enableDynamicPaths", false, false); 098 099 String folder = ServerDirectoryHelper.normalize(serviceParameters.getValue("folder")); 100 if (enableDynamicPaths) 101 { 102 String site = (String) request.getAttribute("site"); 103 String language = (String) request.getAttribute("sitemapLanguage"); 104 105 folder = ServerDirectoryHelper.evaluateDynamicPath(folder, site, language, _currentUserProvider.getUser()); 106 107 if (!folder.startsWith("file:/")) 108 { 109 folder = "file:/" + folder; 110 } 111 } 112 113 Set<Source> rootSources = ServerDirectoryHelper.getRootServerSources(_srcResolver); 114 if (!ServerDirectoryHelper.isValidPath(decodedPath, rootSources)) 115 { 116 throw new AccessDeniedException("You are not allowed to access to server directory file " + decodedPath); 117 } 118 119 if (!decodedPath.startsWith(folder)) 120 { 121 throw new IllegalStateException("The server directory file '" + decodedPath + "' is not part of the current service : " + folder); 122 } 123 124 result.put("path", path); 125 126 String decodedName = ServerDirectoryHelper.normalize(_decodePath(name)); 127 Response response = ObjectModelHelper.getResponse(objectModel); 128 _setHeader(decodedName, response); 129 130 return result; 131 } 132 133 private void _checkPageAccess(ZoneItem zoneItem) throws AuthorizationRequiredException, AmetysRepositoryException, AccessDeniedException 134 { 135 Page page = zoneItem.getZone().getPage(); 136 137 if (!_rightManager.hasAnonymousReadAccess(page)) 138 { 139 UserIdentity user = _currentUserProvider.getUser(); 140 if (user == null) 141 { 142 throw new AuthorizationRequiredException(null); 143 } 144 else if (!_rightManager.hasReadAccess(user, page)) 145 { 146 throw new AccessDeniedException("Access to page " + page.getPathInSitemap() + " is not allowed for user " + user); 147 } 148 } 149 } 150 151 /** 152 * Decode the resource path 153 * @param path the resource path 154 * @return the decoded resource path 155 * @throws UnsupportedEncodingException if UTF-8 encoding is not supported 156 * @throws URISyntaxException if an error occurred 157 */ 158 protected String _decodePath (String path) throws UnsupportedEncodingException, URISyntaxException 159 { 160 StringBuffer sb = new StringBuffer(); 161 162 String[] parts = path.split("/"); 163 boolean first = true; 164 for (String part : parts) 165 { 166 if (first) 167 { 168 if (path.startsWith("/")) 169 { 170 sb.append("/"); 171 } 172 first = false; 173 } 174 else 175 { 176 sb.append("/"); 177 } 178 179 sb.append(URLDecoder.decode(part, "utf-8")); 180 } 181 182 return sb.toString(); 183 } 184 185 /** 186 * Set Content-Disposition header at attachement, with the file name 187 * @param name file name to encode 188 * @param response request response where the header will be set 189 */ 190 // TODO EXPLORER-484 191 protected void _setHeader (String name, Response response) 192 { 193 String encodedName = null; 194 try 195 { 196 URI uri = new URI(null, null, name, null); 197 encodedName = uri.toASCIIString(); 198 // EXPLORER-358 : Fix for Chrome which does not support comma in filename 199 encodedName = encodedName.replaceAll(",", "%2C"); 200 encodedName = encodedName.replaceAll(";", "%3B"); 201 } 202 catch (URISyntaxException e) 203 { 204 // do nothing and send no encoded name 205 } 206 207 String escapedName = name.replaceAll("\\\\", "\\\\\\\\"); 208 escapedName = escapedName.replaceAll("\\\"", "\\\\\\\""); 209 response.setHeader("Content-Disposition", "attachment; filename=\"" + (encodedName != null ? encodedName : escapedName) + "\"" + (encodedName != null ? ";filename*=UTF-8''" + encodedName : "")); 210 } 211}