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}