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.runtime.authentication.AccessDeniedException; 045import org.ametys.runtime.authentication.AuthorizationRequiredException; 046import org.ametys.web.repository.page.Page; 047import org.ametys.web.repository.page.ZoneItem; 048 049/** 050 * Check if we are authorized to access to the path 051 */ 052public class CheckPathAccessAction extends ServiceableAction 053{ 054 private org.apache.excalibur.source.SourceResolver _srcResolver; 055 private AmetysObjectResolver _resolver; 056 private CurrentUserProvider _currentUserProvider; 057 private RightManager _rightManager; 058 059 @Override 060 public void service(ServiceManager smanager) throws ServiceException 061 { 062 super.service(smanager); 063 _srcResolver = (org.apache.excalibur.source.SourceResolver) smanager.lookup(org.apache.excalibur.source.SourceResolver.ROLE); 064 _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); 065 _currentUserProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE); 066 _rightManager = (RightManager) smanager.lookup(RightManager.ROLE); 067 } 068 069 public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception 070 { 071 Map<String, Object> result = new HashMap<>(); 072 073 Request request = ObjectModelHelper.getRequest(objectModel); 074 String path = request.getParameter("path"); 075 String name = request.getParameter("name"); 076 077 if (StringUtils.isBlank(path)) 078 { 079 throw new IllegalArgumentException("Missing server directory's path"); 080 } 081 if (StringUtils.isBlank(name)) 082 { 083 throw new IllegalArgumentException("Missing server file's name"); 084 } 085 086 String decodedPath = ServerDirectoryHelper.normalize(_decodePath(path)); 087 088 String zoneItemEncoded = parameters.getParameter("zoneItem"); 089 String zoneItemId = new String(Base64.getUrlDecoder().decode(zoneItemEncoded.getBytes("UTF-8"))); 090 ZoneItem zoneItem = (ZoneItem) _resolver.resolveById(zoneItemId); 091 092 // Check page to page 093 _checkPageAccess(zoneItem); 094 095 boolean enableDynamicPaths = zoneItem.getServiceParameters().getBoolean("enableDynamicPaths", false); 096 097 String folder = ServerDirectoryHelper.normalize(zoneItem.getServiceParameters().getString("folder", null)); 098 if (enableDynamicPaths) 099 { 100 String site = (String) request.getAttribute("site"); 101 String language = (String) request.getAttribute("sitemapLanguage"); 102 103 folder = ServerDirectoryHelper.evaluateDynamicPath(folder, site, language, _currentUserProvider.getUser()); 104 105 if (!folder.startsWith("file:/")) 106 { 107 folder = "file:/" + folder; 108 } 109 } 110 111 Set<Source> rootSources = ServerDirectoryHelper.getRootServerSources(_srcResolver); 112 if (!ServerDirectoryHelper.isValidPath(decodedPath, rootSources)) 113 { 114 throw new AccessDeniedException("You are not allowed to access to server directory file " + decodedPath); 115 } 116 117 if (!decodedPath.startsWith(folder)) 118 { 119 throw new IllegalStateException("The server directory file '" + decodedPath + "' is not part of the current service : " + folder); 120 } 121 122 result.put("path", path); 123 124 String decodedName = ServerDirectoryHelper.normalize(_decodePath(name)); 125 Response response = ObjectModelHelper.getResponse(objectModel); 126 _setHeader(decodedName, response); 127 128 return result; 129 } 130 131 private void _checkPageAccess(ZoneItem zoneItem) throws AuthorizationRequiredException, AmetysRepositoryException, AccessDeniedException 132 { 133 Page page = zoneItem.getZone().getPage(); 134 135 if (!_rightManager.hasAnonymousReadAccess(page)) 136 { 137 UserIdentity user = _currentUserProvider.getUser(); 138 if (user == null) 139 { 140 throw new AuthorizationRequiredException(null); 141 } 142 else if (!_rightManager.hasReadAccess(user, page)) 143 { 144 throw new AccessDeniedException("Access to page " + page.getPathInSitemap() + " is not allowed for user " + user); 145 } 146 } 147 } 148 149 /** 150 * Decode the resource path 151 * @param path the resource path 152 * @return the decoded resource path 153 * @throws UnsupportedEncodingException if UTF-8 encoding is not supported 154 * @throws URISyntaxException if an error occurred 155 */ 156 protected String _decodePath (String path) throws UnsupportedEncodingException, URISyntaxException 157 { 158 StringBuffer sb = new StringBuffer(); 159 160 String[] parts = path.split("/"); 161 boolean first = true; 162 for (String part : parts) 163 { 164 if (first) 165 { 166 if (path.startsWith("/")) 167 { 168 sb.append("/"); 169 } 170 first = false; 171 } 172 else 173 { 174 sb.append("/"); 175 } 176 177 sb.append(URLDecoder.decode(part, "utf-8")); 178 } 179 180 return sb.toString(); 181 } 182 183 /** 184 * Set Content-Disposition header at attachement, with the file name 185 * @param name file name to encode 186 * @param response request response where the header will be set 187 */ 188 // TODO EXPLORER-484 189 protected void _setHeader (String name, Response response) 190 { 191 String encodedName = null; 192 try 193 { 194 URI uri = new URI(null, null, name, null); 195 encodedName = uri.toASCIIString(); 196 // EXPLORER-358 : Fix for Chrome which does not support comma in filename 197 encodedName = encodedName.replaceAll(",", "%2C"); 198 encodedName = encodedName.replaceAll(";", "%3B"); 199 } 200 catch (URISyntaxException e) 201 { 202 // do nothing and send no encoded name 203 } 204 205 String escapedName = name.replaceAll("\\\\", "\\\\\\\\"); 206 escapedName = escapedName.replaceAll("\\\"", "\\\\\\\""); 207 response.setHeader("Content-Disposition", "attachment; filename=\"" + (encodedName != null ? encodedName : escapedName) + "\"" + (encodedName != null ? ";filename*=UTF-8''" + encodedName : "")); 208 } 209}