/**
* Copyright 2017 Linagora, Université Joseph Fourier, Floralis
*
* The present code is developed in the scope of the joint LINAGORA -
* Université Joseph Fourier - Floralis research program and is designated
* as a "Result" pursuant to the terms and conditions of the LINAGORA
* - Université Joseph Fourier - Floralis research program. Each copyright
* holder of Results enumerated here above fully & independently holds complete
* ownership of the complete Intellectual Property rights applicable to the whole
* of said Results, and may freely exploit it in any manner which does not infringe
* the moral rights of the other copyright holders.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.roboconf.dm.rest.services.internal.audit;
import java.util.logging.Level;
import java.util.logging.LogRecord;
/**
* A log record dedicated to audit security access to our REST API.
* <p>
* If the target resource is not defined, or if the access is not authorized,
* the log level is {@value Level#WARNING}. If the user is unknown, it is
* {@value Level#SEVERE}. Otherwise, it is {@value Level#INFO}.
* </p>
* <p>
* At the beginning, it was thought we could just add custom fields and
* format the output in the log configuration files. Unfortunately, it does
* not work. Pax-Logging converts java.util.logs into Log4j logs and it does
* not propagate everything. So, it is more simple for this class to format
* the log message. To make things simple, the formatting is not configurable.
* </p>
* <p>
* This class overrides the logger name and sets it to {@value #LOGGER_NAME}.
* It has a specific configuration in the log4j properties and does not inherit
* from the usual Roboconf loggers (net.roboconf).
* </p>
*
* @author Vincent Zurczak - Linagora
*/
public class AuditLogRecord extends LogRecord {
public static final String LOGGER_NAME = "audit.roboconf.rest.services";
private static final long serialVersionUID = -463999943841687911L;
static final String SEPARATOR = " | ";
static final String ANONYMOUS = "anonymous";
static final String ALLOWED = "OK";
static final String BLOCKED = "BLOCKED";
private static final int PADDING_USER = 30; // Rough Estimation
private static final int PADDING_TARGET = 60; // Rough estimation
private static final int PADDING_VERB = 6; // "DELETE".length()
private static final int PADDING_AUTHORIZED = BLOCKED.length();
private static final int PADDING_IP = 16; // IPV4 => 15, IPV6 => 45
/**
* Constructor.
* @param user the user name (null for anonymous)
* @param targetResource the REST path (as defined in Jersey annotations)
* @param targetPath the real URI path
* @param ipAddress the IP address of the client
* @param restVerb the REST verb (get, post, put, delete)
* @param userAgent the user agent
* @param authorized true if the access is authorized, false otherwise
*/
public AuditLogRecord(
String user,
String targetResource,
String targetPath,
String restVerb,
String ipAddress,
String userAgent,
boolean authorized ) {
super(
findLevel( user, targetResource, authorized ),
buildMessage( user, targetResource, targetPath, restVerb, ipAddress, userAgent, authorized ));
setLoggerName( LOGGER_NAME );
setSourceClassName( null );
setSourceMethodName( null );
}
/**
* @param user
* @param targetResource
* @param restVerb
* @param ipAddress
* @param targetPath
* @param userAgent the user agent
* @param authorized
* @return a non-null and formatted message
*/
private static String buildMessage(
String user,
String targetResource,
String targetPath,
String restVerb,
String ipAddress,
String userAgent,
boolean authorized ) {
StringBuilder sb = new StringBuilder();
sb.append( String.format( "%" + PADDING_IP + "s", ipAddress ));
sb.append( SEPARATOR );
sb.append( String.format( "%" + PADDING_AUTHORIZED + "s", authorized ? ALLOWED : BLOCKED ));
sb.append( SEPARATOR );
sb.append( String.format( "%" + PADDING_USER + "s", user == null ? ANONYMOUS : user ));
sb.append( SEPARATOR );
sb.append( String.format( "%" + PADDING_VERB + "s", restVerb ));
sb.append( SEPARATOR );
sb.append( String.format( "%" + PADDING_TARGET + "s", targetResource == null ? "-" : targetResource ));
sb.append( SEPARATOR );
// Left-aligned, no limit of size
sb.append( targetPath );
sb.append( SEPARATOR );
sb.append( userAgent == null ? "-" : userAgent );
return sb.toString();
}
/**
* @param user
* @param targetResource
* @param authorized
* @return a non-null log level
*/
private static Level findLevel( String user, String targetResource, boolean authorized ) {
Level result = Level.INFO;
if( user == null )
result = Level.SEVERE;
else if( ! authorized )
result = Level.SEVERE;
else if( targetResource == null )
result = Level.WARNING;
return result;
}
}