DefaultAccessLogger.java
/*
* Copyright (c) 2002-2022, City of Paris
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright notice
* and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice
* and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* 3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* License 1.0
*/
package fr.paris.lutece.portal.service.security.impl;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import fr.paris.lutece.api.user.User;
import fr.paris.lutece.portal.service.security.AccessLoggerConstants;
import fr.paris.lutece.portal.service.util.AppPropertiesService;
import fr.paris.lutece.portal.service.security.IAccessLogger;
import java.text.MessageFormat;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* This class provides a default implementation for AccessLogger Service
*
* A specific log format can be set in the properties file
*
* - portal.defaultAccessLogger.messageFormat - portal.defaultAccessLogger.messageFormatSeparator
*
* Log lines could be certified with a hash, that could be verified, whith this property set to true :
*
* - portal.defaultAccessLogger.addHashToLogs
*
*/
public final class DefaultAccessLogger implements IAccessLogger
{
private static final String CONSTANT_HASH_ENCODING = "UTF-8";
private static final String CONSTANT_HASH_DIGEST = "MD5";
private static final String PROPERTY_ADD_HASH_TO_LOGS = "accessLogger.defaultAccessLogger.addHashToLogs";
private static final String PROPERTY_ACCESSLOG_MESSAGE_FORMAT = "accessLogger.defaultAccessLogger.messageFormat";
private static final String PROPERTY_ACCESSLOG_MESSAGE_FORMAT_SEPARATOR = "accessLogger.defaultAccessLogger.messageFormatSeparator";
private static final String DEFAULT_ACCESSLOG_MESSAGE_FORMAT = "|{0}|{1}|{2}|{3}|{4}|{5}|";
private static final String DEFAULT_ACCESSLOG_MESSAGE_FORMAT_SEPARATOR = "|";
private static final String ERROR_MSG = "ERROR : unable to create json from data";
private final boolean _bAddHashToLogs = AppPropertiesService.getPropertyBoolean( PROPERTY_ADD_HASH_TO_LOGS, false );
private final String _messageFormat = AppPropertiesService.getProperty( PROPERTY_ACCESSLOG_MESSAGE_FORMAT, DEFAULT_ACCESSLOG_MESSAGE_FORMAT );
private final String _messageFormatSeparator = AppPropertiesService.getProperty( PROPERTY_ACCESSLOG_MESSAGE_FORMAT_SEPARATOR,
DEFAULT_ACCESSLOG_MESSAGE_FORMAT_SEPARATOR );
public static final String DEFAULT_LOGGER_ACCESS_LOG = "lutece.accessLogger";
private static Logger _defaultLogger = LogManager.getLogger( DEFAULT_LOGGER_ACCESS_LOG );
/**
* {@inheritDoc}
*/
@Override
public void info( String strEventType, String strAppEventCode, User connectedUser, Object data, String specificOrigin )
{
Logger logger = getLogger( specificOrigin );
if ( logger.isInfoEnabled( ) )
{
String strAppId = AppPropertiesService.getProperty( AccessLoggerConstants.PROPERTY_SITE_CODE, "?" );
String logMessage = getLogMessage( strAppId, strEventType, strAppEventCode, ( connectedUser != null ? connectedUser.getAccessCode( ) : "null" ),
data );
logger.info( logMessage );
}
}
/**
* {@inheritDoc}
*/
@Override
public void debug( String strEventType, String strAppEventCode, User connectedUser, Object data, String specificOrigin )
{
Logger logger = getLogger( specificOrigin );
if ( logger.isDebugEnabled( ) )
{
String strAppId = AppPropertiesService.getProperty( AccessLoggerConstants.PROPERTY_SITE_CODE, "?" );
String logMessage = getLogMessage( strAppId, strEventType, strAppEventCode, ( connectedUser != null ? connectedUser.getAccessCode( ) : "null" ),
data );
logger.debug( logMessage );
}
}
/**
* {@inheritDoc}
*/
@Override
public void trace( String strEventType, String strAppEventCode, User connectedUser, Object data, String specificOrigin )
{
Logger logger = getLogger( specificOrigin );
if ( logger.isTraceEnabled( ) )
{
String strAppId = AppPropertiesService.getProperty( AccessLoggerConstants.PROPERTY_SITE_CODE, "?" );
String logMessage = getLogMessage( strAppId, strEventType, strAppEventCode, ( connectedUser != null ? connectedUser.getAccessCode( ) : "null" ),
data );
logger.trace( logMessage );
}
}
/**
* {@inheritDoc}
*/
@Override
public void warn( String strEventType, String strAppEventCode, User connectedUser, Object data, String specificOrigin )
{
Logger logger = getLogger( specificOrigin );
if ( logger.isEnabled( Level.WARN ) )
{
String strAppId = AppPropertiesService.getProperty( AccessLoggerConstants.PROPERTY_SITE_CODE, "?" );
String logMessage = getLogMessage( strAppId, strEventType, strAppEventCode, ( connectedUser != null ? connectedUser.getAccessCode( ) : "null" ),
data );
logger.warn( logMessage );
}
}
/**
* build log message
*
* @param eventType
* @param description
* @param connectedUserLogin
* @param data
* @return the log message
*/
private String getLogMessage( String strAppId, String strEventType, String strAppEventCode, String strConnectedUserLogin, Object data )
{
String jsonData = "";
if ( data != null )
{
ObjectMapper obj = new ObjectMapper( );
try
{
jsonData = obj.writeValueAsString( data );
}
catch( JsonProcessingException e )
{
jsonData = ERROR_MSG;
}
}
return getLogMessage( strAppId, strEventType, strAppEventCode, strConnectedUserLogin, jsonData, isAddHashToLogs( ) );
}
/**
* build log message
*
* @param eventType
* @param description
* @param connectedUserLogin
* @param data
* @return the log message
*/
private String getLogMessage( String strAppId, String strEventType, String strAppEventCode, String strConnectedUserLogin, String strData,
boolean isAddHashToLogs )
{
String strHash = "";
if ( isAddHashToLogs )
{
strHash = getHash( MessageFormat.format( _messageFormat, "", strAppId, strEventType, strAppEventCode, strConnectedUserLogin, strData ) );
}
return MessageFormat.format( _messageFormat, strHash, strAppId, strEventType, strAppEventCode, strConnectedUserLogin, strData );
}
/**
* get hash
*
* @param message
* @param last
* hash
*
* @return the hash in String
*/
private static String getHash( String message )
{
byte [ ] byteChaine;
try
{
byteChaine = message.getBytes( CONSTANT_HASH_ENCODING );
MessageDigest md = MessageDigest.getInstance( CONSTANT_HASH_DIGEST );
byte [ ] hash = md.digest( byteChaine );
// convert byte array to Hexadecimal String
StringBuilder sb = new StringBuilder( 2 * hash.length );
for ( byte b : hash )
{
sb.append( String.format( "%02x", b & 0xff ) );
}
return sb.toString( );
}
catch( UnsupportedEncodingException | NoSuchAlgorithmException e )
{
return "Hash ERROR : " + e.getLocalizedMessage( );
}
}
/**
* verify hash
*
* @param message
*
* @return true if the hash contained in the message is valid
*/
public boolean verifyMessageHash( String message )
{
try
{
int idx = message.indexOf( _messageFormatSeparator, message.indexOf( DEFAULT_LOGGER_ACCESS_LOG ) );
String hash = message.substring( idx + 1, idx + 33 );
String data = "||" + message.substring( idx + 34 );
return ( hash != null && ( hash.equals( "" ) || hash.equals( getHash( data ) ) ) );
}
catch( StringIndexOutOfBoundsException e )
{
return false;
}
}
/**
* is addHashToLogs enabled
*
* @return true if addHashToLogs enabled
*/
public boolean isAddHashToLogs( )
{
return _bAddHashToLogs;
}
/**
* get logger
*
* @param strSpecificLogger
* @return the logger
*/
private Logger getLogger( String specificOrigin )
{
if ( specificOrigin != null && !"".equals( specificOrigin ) )
{
return LogManager.getLogger( DEFAULT_LOGGER_ACCESS_LOG + "." + specificOrigin );
}
return _defaultLogger;
}
}