View Javadoc
1   /*
2    * Copyright (c) 2002-2022, 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.security.impl;
35  
36  import java.io.UnsupportedEncodingException;
37  import java.security.MessageDigest;
38  import java.security.NoSuchAlgorithmException;
39  
40  import com.fasterxml.jackson.core.JsonProcessingException;
41  import com.fasterxml.jackson.databind.ObjectMapper;
42  import fr.paris.lutece.api.user.User;
43  import fr.paris.lutece.portal.service.security.AccessLoggerConstants;
44  import fr.paris.lutece.portal.service.util.AppPropertiesService;
45  import fr.paris.lutece.portal.service.security.IAccessLogger;
46  
47  import java.text.MessageFormat;
48  import org.apache.logging.log4j.Level;
49  import org.apache.logging.log4j.LogManager;
50  import org.apache.logging.log4j.Logger;
51  
52  /**
53   * This class provides a default implementation for AccessLogger Service
54   * 
55   * A specific log format can be set in the properties file
56   * 
57   * - portal.defaultAccessLogger.messageFormat - portal.defaultAccessLogger.messageFormatSeparator
58   * 
59   * Log lines could be certified with a hash, that could be verified, whith this property set to true :
60   * 
61   * - portal.defaultAccessLogger.addHashToLogs
62   * 
63   */
64  public final class DefaultAccessLogger implements IAccessLogger
65  {
66  
67      private static final String CONSTANT_HASH_ENCODING = "UTF-8";
68      private static final String CONSTANT_HASH_DIGEST = "MD5";
69      private static final String PROPERTY_ADD_HASH_TO_LOGS = "accessLogger.defaultAccessLogger.addHashToLogs";
70      private static final String PROPERTY_ACCESSLOG_MESSAGE_FORMAT = "accessLogger.defaultAccessLogger.messageFormat";
71      private static final String PROPERTY_ACCESSLOG_MESSAGE_FORMAT_SEPARATOR = "accessLogger.defaultAccessLogger.messageFormatSeparator";
72  
73      private static final String DEFAULT_ACCESSLOG_MESSAGE_FORMAT = "|{0}|{1}|{2}|{3}|{4}|{5}|";
74      private static final String DEFAULT_ACCESSLOG_MESSAGE_FORMAT_SEPARATOR = "|";
75  
76      private static final String ERROR_MSG = "ERROR : unable to create json from data";
77  
78      private final boolean _bAddHashToLogs = AppPropertiesService.getPropertyBoolean( PROPERTY_ADD_HASH_TO_LOGS, false );
79      private final String _messageFormat = AppPropertiesService.getProperty( PROPERTY_ACCESSLOG_MESSAGE_FORMAT, DEFAULT_ACCESSLOG_MESSAGE_FORMAT );
80      private final String _messageFormatSeparator = AppPropertiesService.getProperty( PROPERTY_ACCESSLOG_MESSAGE_FORMAT_SEPARATOR,
81              DEFAULT_ACCESSLOG_MESSAGE_FORMAT_SEPARATOR );
82  
83      public static final String DEFAULT_LOGGER_ACCESS_LOG = "lutece.accessLogger";
84      private static Logger _defaultLogger = LogManager.getLogger( DEFAULT_LOGGER_ACCESS_LOG );
85  
86      /**
87       * {@inheritDoc}
88       */
89      @Override
90      public void info( String strEventType, String strAppEventCode, User connectedUser, Object data, String specificOrigin )
91      {
92          Logger logger = getLogger( specificOrigin );
93  
94          if ( logger.isInfoEnabled( ) )
95          {
96              String strAppId = AppPropertiesService.getProperty( AccessLoggerConstants.PROPERTY_SITE_CODE, "?" );
97              String logMessage = getLogMessage( strAppId, strEventType, strAppEventCode, ( connectedUser != null ? connectedUser.getAccessCode( ) : "null" ),
98                      data );
99  
100             logger.info( logMessage );
101         }
102     }
103 
104     /**
105      * {@inheritDoc}
106      */
107     @Override
108     public void debug( String strEventType, String strAppEventCode, User connectedUser, Object data, String specificOrigin )
109     {
110         Logger logger = getLogger( specificOrigin );
111 
112         if ( logger.isDebugEnabled( ) )
113         {
114             String strAppId = AppPropertiesService.getProperty( AccessLoggerConstants.PROPERTY_SITE_CODE, "?" );
115             String logMessage = getLogMessage( strAppId, strEventType, strAppEventCode, ( connectedUser != null ? connectedUser.getAccessCode( ) : "null" ),
116                     data );
117 
118             logger.debug( logMessage );
119         }
120     }
121 
122     /**
123      * {@inheritDoc}
124      */
125     @Override
126     public void trace( String strEventType, String strAppEventCode, User connectedUser, Object data, String specificOrigin )
127     {
128         Logger logger = getLogger( specificOrigin );
129 
130         if ( logger.isTraceEnabled( ) )
131         {
132             String strAppId = AppPropertiesService.getProperty( AccessLoggerConstants.PROPERTY_SITE_CODE, "?" );
133             String logMessage = getLogMessage( strAppId, strEventType, strAppEventCode, ( connectedUser != null ? connectedUser.getAccessCode( ) : "null" ),
134                     data );
135 
136             logger.trace( logMessage );
137         }
138     }
139 
140     /**
141      * {@inheritDoc}
142      */
143     @Override
144     public void warn( String strEventType, String strAppEventCode, User connectedUser, Object data, String specificOrigin )
145     {
146         Logger logger = getLogger( specificOrigin );
147 
148         if ( logger.isEnabled( Level.WARN ) )
149         {
150             String strAppId = AppPropertiesService.getProperty( AccessLoggerConstants.PROPERTY_SITE_CODE, "?" );
151             String logMessage = getLogMessage( strAppId, strEventType, strAppEventCode, ( connectedUser != null ? connectedUser.getAccessCode( ) : "null" ),
152                     data );
153 
154             logger.warn( logMessage );
155         }
156     }
157 
158     /**
159      * build log message
160      *
161      * @param eventType
162      * @param description
163      * @param connectedUserLogin
164      * @param data
165      * @return the log message
166      */
167     private String getLogMessage( String strAppId, String strEventType, String strAppEventCode, String strConnectedUserLogin, Object data )
168     {
169 
170         String jsonData = "";
171 
172         if ( data != null )
173         {
174             ObjectMapper obj = new ObjectMapper( );
175 
176             try
177             {
178                 jsonData = obj.writeValueAsString( data );
179             }
180             catch( JsonProcessingException e )
181             {
182                 jsonData = ERROR_MSG;
183             }
184         }
185 
186         return getLogMessage( strAppId, strEventType, strAppEventCode, strConnectedUserLogin, jsonData, isAddHashToLogs( ) );
187     }
188 
189     /**
190      * build log message
191      *
192      * @param eventType
193      * @param description
194      * @param connectedUserLogin
195      * @param data
196      * @return the log message
197      */
198     private String getLogMessage( String strAppId, String strEventType, String strAppEventCode, String strConnectedUserLogin, String strData,
199             boolean isAddHashToLogs )
200     {
201 
202         String strHash = "";
203 
204         if ( isAddHashToLogs )
205         {
206             strHash = getHash( MessageFormat.format( _messageFormat, "", strAppId, strEventType, strAppEventCode, strConnectedUserLogin, strData ) );
207         }
208 
209         return MessageFormat.format( _messageFormat, strHash, strAppId, strEventType, strAppEventCode, strConnectedUserLogin, strData );
210     }
211 
212     /**
213      * get hash
214      *
215      * @param message
216      * @param last
217      *            hash
218      *
219      * @return the hash in String
220      */
221     private static String getHash( String message )
222     {
223 
224         byte [ ] byteChaine;
225         try
226         {
227             byteChaine = message.getBytes( CONSTANT_HASH_ENCODING );
228             MessageDigest md = MessageDigest.getInstance( CONSTANT_HASH_DIGEST );
229             byte [ ] hash = md.digest( byteChaine );
230 
231             // convert byte array to Hexadecimal String
232             StringBuilder sb = new StringBuilder( 2 * hash.length );
233             for ( byte b : hash )
234             {
235                 sb.append( String.format( "%02x", b & 0xff ) );
236             }
237 
238             return sb.toString( );
239 
240         }
241         catch( UnsupportedEncodingException | NoSuchAlgorithmException e )
242         {
243             return "Hash ERROR : " + e.getLocalizedMessage( );
244         }
245 
246     }
247 
248     /**
249      * verify hash
250      *
251      * @param message
252      *
253      * @return true if the hash contained in the message is valid
254      */
255     public boolean verifyMessageHash( String message )
256     {
257         try
258         {
259 
260             int idx = message.indexOf( _messageFormatSeparator, message.indexOf( DEFAULT_LOGGER_ACCESS_LOG ) );
261             String hash = message.substring( idx + 1, idx + 33 );
262             String data = "||" + message.substring( idx + 34 );
263 
264             return ( hash != null && ( hash.equals( "" ) || hash.equals( getHash( data ) ) ) );
265 
266         }
267         catch( StringIndexOutOfBoundsException e )
268         {
269 
270             return false;
271         }
272     }
273 
274     /**
275      * is addHashToLogs enabled
276      * 
277      * @return true if addHashToLogs enabled
278      */
279     public boolean isAddHashToLogs( )
280     {
281         return _bAddHashToLogs;
282     }
283 
284     /**
285      * get logger
286      * 
287      * @param strSpecificLogger
288      * @return the logger
289      */
290     private Logger getLogger( String specificOrigin )
291     {
292         if ( specificOrigin != null && !"".equals( specificOrigin ) )
293         {
294             return LogManager.getLogger( DEFAULT_LOGGER_ACCESS_LOG + "." + specificOrigin );
295         }
296 
297         return _defaultLogger;
298 
299     }
300 }