View Javadoc
1   /*
2    * Copyright (c) 2002-2025, City of Paris
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    *
9    *  1. Redistributions of source code must retain the above copyright notice
10   *     and the following disclaimer.
11   *
12   *  2. Redistributions in binary form must reproduce the above copyright notice
13   *     and the following disclaimer in the documentation and/or other materials
14   *     provided with the distribution.
15   *
16   *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
17   *     contributors may be used to endorse or promote products derived from
18   *     this software without specific prior written permission.
19   *
20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
24   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30   * POSSIBILITY OF SUCH DAMAGE.
31   *
32   * License 1.0
33   */
34  package fr.paris.lutece.portal.service.file.implementation;
35  
36  import static fr.paris.lutece.portal.service.file.FileService.PARAMETER_VALIDITY_TIME;
37  
38  import java.security.GeneralSecurityException;
39  import java.sql.Timestamp;
40  import java.time.LocalDateTime;
41  import java.util.HashMap;
42  import java.util.Map;
43  
44  import javax.servlet.http.HttpServletRequest;
45  
46  import org.apache.commons.lang3.StringUtils;
47  import org.apache.commons.text.StringEscapeUtils;
48  
49  import fr.paris.lutece.portal.service.file.ExpiredLinkException;
50  import fr.paris.lutece.portal.service.file.FileService;
51  import fr.paris.lutece.portal.service.file.IFileDownloadUrlService;
52  import fr.paris.lutece.portal.service.security.RsaService;
53  import fr.paris.lutece.portal.service.util.AppLogService;
54  import fr.paris.lutece.portal.service.util.AppPathService;
55  import fr.paris.lutece.util.url.UrlItem;
56  
57  /**
58   * 
59   * DatabaseBlobStoreService.
60   * 
61   */
62  public class DefaultFileDownloadService implements IFileDownloadUrlService
63  {
64      private static final long serialVersionUID = 1L;
65  
66      // constants
67      protected static final String URL_FO = "jsp/site/file/download";
68      protected static final String URL_BO = "jsp/admin/file/download";
69      private static final String SERVICE_NAME = "DefaultFileDownloadService";
70      private static final String DEFAULT_SEPARATOR = "/";
71  
72      private String _separator = DEFAULT_SEPARATOR;
73  
74      // Keys
75      public static final String KEY_LINK_VALIDITY_TIME = "link_validity_time";
76  
77      public String getSeparator( )
78      {
79          return _separator;
80      }
81  
82      public void setSeparator( String separator )
83      {
84          _separator = separator;
85      }
86  
87      /**
88       * Build the additionnel data map to provide encryption data
89       * 
90       * @param strFileId
91       * @param strResourceId
92       * @param strResourceType
93       * @return the map
94       */
95      public static Map<String, String> buildAdditionnalDatas( String strFileId, String strResourceId, String strResourceType )
96      {
97          Map<String, String> map = new HashMap<>( );
98  
99          map.put( FileService.PARAMETER_FILE_ID, strFileId );
100         map.put( FileService.PARAMETER_RESOURCE_ID, strResourceId );
101         map.put( FileService.PARAMETER_RESOURCE_TYPE, strResourceType );
102 
103         return map;
104     }
105 
106     /**
107      * {@inheritDoc}
108      */
109     @Override
110     public String getFileDownloadUrlFO( String strFileKey, String strFileStorageServiceProviderName )
111     {
112 
113         return getFileDownloadUrlFO( strFileKey, null, strFileStorageServiceProviderName );
114     }
115 
116     /**
117      * {@inheritDoc}
118      */
119     @Override
120     public String getFileDownloadUrlFO( String strFileKey, Map<String, String> additionnalData, String strFileStorageServiceProviderName )
121     {
122         StringBuilder sbUrl = new StringBuilder( );
123 
124         sbUrl.append( AppPathService.getBaseUrl( null ) );
125         sbUrl.append( URL_FO );
126 
127         if ( additionnalData == null )
128         {
129             additionnalData = new HashMap<>( );
130         }
131         additionnalData.put( FileService.PARAMETER_FILE_ID, strFileKey );
132 
133         return getEncryptedUrl( sbUrl.toString( ), getDataToEncrypt( additionnalData ), strFileStorageServiceProviderName );
134     }
135 
136     /**
137      * {@inheritDoc}
138      */
139     @Override
140     public String getFileDownloadUrlBO( String strFileKey, String strFileStorageServiceProviderName )
141     {
142         return getFileDownloadUrlBO( strFileKey, null, strFileStorageServiceProviderName );
143     }
144 
145     /**
146      * {@inheritDoc}
147      */
148     @Override
149     public String getFileDownloadUrlBO( String strFileKey, Map<String, String> additionnalData, String strFileStorageServiceProviderName )
150     {
151 
152         StringBuilder sbUrl = new StringBuilder( );
153 
154         sbUrl.append( AppPathService.getBaseUrl( null ) );
155         sbUrl.append( URL_BO );
156 
157         if ( additionnalData == null )
158         {
159             additionnalData = new HashMap<>( );
160         }
161         additionnalData.put( FileService.PARAMETER_FILE_ID, strFileKey );
162 
163         return getEncryptedUrl( sbUrl.toString( ), getDataToEncrypt( additionnalData ), strFileStorageServiceProviderName );
164     }
165 
166     /**
167      * get encrypted url
168      * 
169      * @param strUrl
170      * @param additionnalData
171      * 
172      * @return the url, null otherwise
173      */
174     protected String getEncryptedUrl( String strUrl, String dataToEncrypt, String strFileStorageServiceProviderName )
175     {
176         UrlItemil/url/UrlItem.html#UrlItem">UrlItem item = new UrlItem( strUrl );
177 
178         try
179         {
180             String idEncrytped = RsaService.encryptRsa( dataToEncrypt );
181 
182             item.addParameter( FileService.PARAMETER_PROVIDER, strFileStorageServiceProviderName );
183             item.addParameter( FileService.PARAMETER_DATA, idEncrytped );
184 
185             return item.getUrlWithEntity( );
186         }
187         catch( GeneralSecurityException e )
188         {
189             AppLogService.error( e.getMessage( ), e );
190             return null;
191         }
192     }
193 
194     /**
195      * {@inheritDoc}
196      */
197     @Override
198     public String getName( )
199     {
200         return SERVICE_NAME;
201     }
202 
203     /**
204      * get data to encrypt
205      * 
206      * @param fileDownloadData
207      * @return the map of datas to encrypt in the url
208      */
209     private String getDataToEncrypt( Map<String, String> additionnalData )
210     {
211         StringBuilder sb = new StringBuilder( );
212         sb.append( StringUtils.defaultIfEmpty( additionnalData.get( FileService.PARAMETER_FILE_ID ), "" ) ).append( _separator );
213         sb.append( StringUtils.defaultIfEmpty( additionnalData.get( FileService.PARAMETER_RESOURCE_ID ), "" ) ).append( _separator );
214         sb.append( StringUtils.defaultIfEmpty( additionnalData.get( FileService.PARAMETER_RESOURCE_TYPE ), "" ) ).append( _separator );
215         sb.append( calculateEndValidity( ) );
216 
217         return sb.toString( );
218     }
219 
220     /**
221      * get end validity time
222      * 
223      * @return the end time of url validity
224      */
225     protected long calculateEndValidity( )
226     {
227         LocalDateTime endValidity = LocalDateTime.MAX;
228         if ( getValidityTime( ) > 0 )
229         {
230             endValidity = LocalDateTime.now( ).plusMinutes( LINK_VALIDITY_TIME );
231         }
232         return Timestamp.valueOf( endValidity ).getTime( );
233     }
234 
235     /**
236      * {@inheritDoc}
237      */
238     @Override
239     public Map<String, String> getRequestDataBO( HttpServletRequest request )
240     {
241         String strEncryptedDataParam = request.getParameter( FileService.PARAMETER_DATA );
242 
243         try
244         {
245             // the base64 encoded data "=" characters could be escaped 
246             String unescapedUrlParam = StringEscapeUtils.unescapeHtml4( strEncryptedDataParam );
247             String strDecryptedData = RsaService.decryptRsa( unescapedUrlParam );
248             return getDecryptedData( strDecryptedData );
249         }
250         catch( GeneralSecurityException e )
251         {
252             AppLogService.error( e.getMessage( ), e );
253             return null;
254         }
255     }
256 
257     /**
258      * {@inheritDoc}
259      */
260     @Override
261     public Map<String, String> getRequestDataFO( HttpServletRequest request )
262     {
263         String strEncryptedDataParam = request.getParameter( FileService.PARAMETER_DATA );
264 
265         try
266         {
267             // the base64 encoded data "=" characters could be escaped 
268             String unescapedUrlParam = StringEscapeUtils.unescapeHtml4( strEncryptedDataParam );
269             String strDecryptedData = RsaService.decryptRsa( unescapedUrlParam );
270             return getDecryptedData( strDecryptedData );
271         }
272         catch( GeneralSecurityException e )
273         {
274             AppLogService.error( e.getMessage( ), e );
275             return null;
276         }
277     }
278 
279     /**
280      * get map of datas from encrypted url data parameter
281      * 
282      * @param data
283      * @return the map of
284      */
285     protected Map<String, String> getDecryptedData( String strData )
286     {
287         String [ ] data = strData.split( _separator );
288         Map<String, String> fileData = buildAdditionnalDatas( data [0], data [1], data [2] );
289         fileData.put( PARAMETER_VALIDITY_TIME, data [3] );
290 
291         return fileData;
292     }
293 
294     /**
295      * {@inheritDoc}
296      */
297     @Override
298     public void checkLinkValidity( Map<String, String> fileData ) throws ExpiredLinkException
299     {
300         LocalDateTime validityTime = new Timestamp( Long.parseLong( fileData.get( FileService.PARAMETER_VALIDITY_TIME ) ) ).toLocalDateTime( );
301 
302         if ( LocalDateTime.now( ).isAfter( validityTime ) )
303         {
304             throw new ExpiredLinkException( "Link expired on : " + validityTime.toString( ) );
305         }
306     }
307 }