DefaultAccessLogger.java

  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. import java.io.UnsupportedEncodingException;
  36. import java.security.MessageDigest;
  37. import java.security.NoSuchAlgorithmException;

  38. import com.fasterxml.jackson.core.JsonProcessingException;
  39. import com.fasterxml.jackson.databind.ObjectMapper;
  40. import fr.paris.lutece.api.user.User;
  41. import fr.paris.lutece.portal.service.security.AccessLoggerConstants;
  42. import fr.paris.lutece.portal.service.util.AppPropertiesService;
  43. import fr.paris.lutece.portal.service.security.IAccessLogger;

  44. import java.text.MessageFormat;
  45. import org.apache.logging.log4j.Level;
  46. import org.apache.logging.log4j.LogManager;
  47. import org.apache.logging.log4j.Logger;

  48. /**
  49.  * This class provides a default implementation for AccessLogger Service
  50.  *
  51.  * A specific log format can be set in the properties file
  52.  *
  53.  * - portal.defaultAccessLogger.messageFormat - portal.defaultAccessLogger.messageFormatSeparator
  54.  *
  55.  * Log lines could be certified with a hash, that could be verified, whith this property set to true :
  56.  *
  57.  * - portal.defaultAccessLogger.addHashToLogs
  58.  *
  59.  */
  60. public final class DefaultAccessLogger implements IAccessLogger
  61. {

  62.     private static final String CONSTANT_HASH_ENCODING = "UTF-8";
  63.     private static final String CONSTANT_HASH_DIGEST = "MD5";
  64.     private static final String PROPERTY_ADD_HASH_TO_LOGS = "accessLogger.defaultAccessLogger.addHashToLogs";
  65.     private static final String PROPERTY_ACCESSLOG_MESSAGE_FORMAT = "accessLogger.defaultAccessLogger.messageFormat";
  66.     private static final String PROPERTY_ACCESSLOG_MESSAGE_FORMAT_SEPARATOR = "accessLogger.defaultAccessLogger.messageFormatSeparator";

  67.     private static final String DEFAULT_ACCESSLOG_MESSAGE_FORMAT = "|{0}|{1}|{2}|{3}|{4}|{5}|";
  68.     private static final String DEFAULT_ACCESSLOG_MESSAGE_FORMAT_SEPARATOR = "|";

  69.     private static final String ERROR_MSG = "ERROR : unable to create json from data";

  70.     private final boolean _bAddHashToLogs = AppPropertiesService.getPropertyBoolean( PROPERTY_ADD_HASH_TO_LOGS, false );
  71.     private final String _messageFormat = AppPropertiesService.getProperty( PROPERTY_ACCESSLOG_MESSAGE_FORMAT, DEFAULT_ACCESSLOG_MESSAGE_FORMAT );
  72.     private final String _messageFormatSeparator = AppPropertiesService.getProperty( PROPERTY_ACCESSLOG_MESSAGE_FORMAT_SEPARATOR,
  73.             DEFAULT_ACCESSLOG_MESSAGE_FORMAT_SEPARATOR );

  74.     public static final String DEFAULT_LOGGER_ACCESS_LOG = "lutece.accessLogger";
  75.     private static Logger _defaultLogger = LogManager.getLogger( DEFAULT_LOGGER_ACCESS_LOG );

  76.     /**
  77.      * {@inheritDoc}
  78.      */
  79.     @Override
  80.     public void info( String strEventType, String strAppEventCode, User connectedUser, Object data, String specificOrigin )
  81.     {
  82.         Logger logger = getLogger( specificOrigin );

  83.         if ( logger.isInfoEnabled( ) )
  84.         {
  85.             String strAppId = AppPropertiesService.getProperty( AccessLoggerConstants.PROPERTY_SITE_CODE, "?" );
  86.             String logMessage = getLogMessage( strAppId, strEventType, strAppEventCode, ( connectedUser != null ? connectedUser.getAccessCode( ) : "null" ),
  87.                     data );

  88.             logger.info( logMessage );
  89.         }
  90.     }

  91.     /**
  92.      * {@inheritDoc}
  93.      */
  94.     @Override
  95.     public void debug( String strEventType, String strAppEventCode, User connectedUser, Object data, String specificOrigin )
  96.     {
  97.         Logger logger = getLogger( specificOrigin );

  98.         if ( logger.isDebugEnabled( ) )
  99.         {
  100.             String strAppId = AppPropertiesService.getProperty( AccessLoggerConstants.PROPERTY_SITE_CODE, "?" );
  101.             String logMessage = getLogMessage( strAppId, strEventType, strAppEventCode, ( connectedUser != null ? connectedUser.getAccessCode( ) : "null" ),
  102.                     data );

  103.             logger.debug( logMessage );
  104.         }
  105.     }

  106.     /**
  107.      * {@inheritDoc}
  108.      */
  109.     @Override
  110.     public void trace( String strEventType, String strAppEventCode, User connectedUser, Object data, String specificOrigin )
  111.     {
  112.         Logger logger = getLogger( specificOrigin );

  113.         if ( logger.isTraceEnabled( ) )
  114.         {
  115.             String strAppId = AppPropertiesService.getProperty( AccessLoggerConstants.PROPERTY_SITE_CODE, "?" );
  116.             String logMessage = getLogMessage( strAppId, strEventType, strAppEventCode, ( connectedUser != null ? connectedUser.getAccessCode( ) : "null" ),
  117.                     data );

  118.             logger.trace( logMessage );
  119.         }
  120.     }

  121.     /**
  122.      * {@inheritDoc}
  123.      */
  124.     @Override
  125.     public void warn( String strEventType, String strAppEventCode, User connectedUser, Object data, String specificOrigin )
  126.     {
  127.         Logger logger = getLogger( specificOrigin );

  128.         if ( logger.isEnabled( Level.WARN ) )
  129.         {
  130.             String strAppId = AppPropertiesService.getProperty( AccessLoggerConstants.PROPERTY_SITE_CODE, "?" );
  131.             String logMessage = getLogMessage( strAppId, strEventType, strAppEventCode, ( connectedUser != null ? connectedUser.getAccessCode( ) : "null" ),
  132.                     data );

  133.             logger.warn( logMessage );
  134.         }
  135.     }

  136.     /**
  137.      * build log message
  138.      *
  139.      * @param eventType
  140.      * @param description
  141.      * @param connectedUserLogin
  142.      * @param data
  143.      * @return the log message
  144.      */
  145.     private String getLogMessage( String strAppId, String strEventType, String strAppEventCode, String strConnectedUserLogin, Object data )
  146.     {

  147.         String jsonData = "";

  148.         if ( data != null )
  149.         {
  150.             ObjectMapper obj = new ObjectMapper( );

  151.             try
  152.             {
  153.                 jsonData = obj.writeValueAsString( data );
  154.             }
  155.             catch( JsonProcessingException e )
  156.             {
  157.                 jsonData = ERROR_MSG;
  158.             }
  159.         }

  160.         return getLogMessage( strAppId, strEventType, strAppEventCode, strConnectedUserLogin, jsonData, isAddHashToLogs( ) );
  161.     }

  162.     /**
  163.      * build log message
  164.      *
  165.      * @param eventType
  166.      * @param description
  167.      * @param connectedUserLogin
  168.      * @param data
  169.      * @return the log message
  170.      */
  171.     private String getLogMessage( String strAppId, String strEventType, String strAppEventCode, String strConnectedUserLogin, String strData,
  172.             boolean isAddHashToLogs )
  173.     {

  174.         String strHash = "";

  175.         if ( isAddHashToLogs )
  176.         {
  177.             strHash = getHash( MessageFormat.format( _messageFormat, "", strAppId, strEventType, strAppEventCode, strConnectedUserLogin, strData ) );
  178.         }

  179.         return MessageFormat.format( _messageFormat, strHash, strAppId, strEventType, strAppEventCode, strConnectedUserLogin, strData );
  180.     }

  181.     /**
  182.      * get hash
  183.      *
  184.      * @param message
  185.      * @param last
  186.      *            hash
  187.      *
  188.      * @return the hash in String
  189.      */
  190.     private static String getHash( String message )
  191.     {

  192.         byte [ ] byteChaine;
  193.         try
  194.         {
  195.             byteChaine = message.getBytes( CONSTANT_HASH_ENCODING );
  196.             MessageDigest md = MessageDigest.getInstance( CONSTANT_HASH_DIGEST );
  197.             byte [ ] hash = md.digest( byteChaine );

  198.             // convert byte array to Hexadecimal String
  199.             StringBuilder sb = new StringBuilder( 2 * hash.length );
  200.             for ( byte b : hash )
  201.             {
  202.                 sb.append( String.format( "%02x", b & 0xff ) );
  203.             }

  204.             return sb.toString( );

  205.         }
  206.         catch( UnsupportedEncodingException | NoSuchAlgorithmException e )
  207.         {
  208.             return "Hash ERROR : " + e.getLocalizedMessage( );
  209.         }

  210.     }

  211.     /**
  212.      * verify hash
  213.      *
  214.      * @param message
  215.      *
  216.      * @return true if the hash contained in the message is valid
  217.      */
  218.     public boolean verifyMessageHash( String message )
  219.     {
  220.         try
  221.         {

  222.             int idx = message.indexOf( _messageFormatSeparator, message.indexOf( DEFAULT_LOGGER_ACCESS_LOG ) );
  223.             String hash = message.substring( idx + 1, idx + 33 );
  224.             String data = "||" + message.substring( idx + 34 );

  225.             return ( hash != null && ( hash.equals( "" ) || hash.equals( getHash( data ) ) ) );

  226.         }
  227.         catch( StringIndexOutOfBoundsException e )
  228.         {

  229.             return false;
  230.         }
  231.     }

  232.     /**
  233.      * is addHashToLogs enabled
  234.      *
  235.      * @return true if addHashToLogs enabled
  236.      */
  237.     public boolean isAddHashToLogs( )
  238.     {
  239.         return _bAddHashToLogs;
  240.     }

  241.     /**
  242.      * get logger
  243.      *
  244.      * @param strSpecificLogger
  245.      * @return the logger
  246.      */
  247.     private Logger getLogger( String specificOrigin )
  248.     {
  249.         if ( specificOrigin != null && !"".equals( specificOrigin ) )
  250.         {
  251.             return LogManager.getLogger( DEFAULT_LOGGER_ACCESS_LOG + "." + specificOrigin );
  252.         }

  253.         return _defaultLogger;

  254.     }
  255. }