Liferay Portal Security Audit
package it.dontesta.labs.liferay.portal.security.audit.message.processor.dummy;
import com.liferay.portal.configuration.metatype.bnd.util.ConfigurableUtil;
import com.liferay.portal.kernel.audit.AuditMessage;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.security.audit.AuditMessageProcessor;
import it.dontesta.labs.liferay.portal.security.audit.message.processor.dummy.configuration.DummyAuditMessageProcessorConfiguration;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Modified;
import java.util.Map;
/**
* @author Antonio Musarra
*/
@Component(
configurationPid = "it.dontesta.labs.liferay.portal.security.audit.message.processor.dummy.configuration.DummyAuditMessageProcessorConfiguration",
immediate = true,
property = "eventTypes=*",
service = AuditMessageProcessor.class
)
public class DummyAuditMessageProcessor implements AuditMessageProcessor {
@Override
public void process(AuditMessage auditMessage) {
try {
doProcess(auditMessage);
}
catch (Exception e) {
_log.fatal("Unable to process audit message " + auditMessage, e);
}
}
@Activate
@Modified
protected void activate(Map<String, Object> properties) {
_dummyAuditMessageProcessorConfiguration =
ConfigurableUtil.createConfigurable(
DummyAuditMessageProcessorConfiguration.class, properties);
}
protected void doProcess(AuditMessage auditMessage) throws Exception {
if (_dummyAuditMessageProcessorConfiguration.enabled()) {
if(_log.isInfoEnabled()) {
_log.info("Dummy processor processing this Audit Message => "
+ auditMessage.toJSONObject());
}
}
}
private static final Log _log = LogFactoryUtil.getLog(
DummyAuditMessageProcessor.class);
private volatile DummyAuditMessageProcessorConfiguration
_dummyAuditMessageProcessorConfiguration;
}
package it.dontesta.labs.liferay.portal.security.audit.event.security.authentication;
import com.liferay.portal.kernel.audit.AuditException;
import com.liferay.portal.kernel.audit.AuditMessage;
import com.liferay.portal.kernel.audit.AuditRouter;
import com.liferay.portal.kernel.json.JSONFactory;
import com.liferay.portal.kernel.json.JSONObject;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.model.User;
import com.liferay.portal.kernel.security.auth.AuthFailure;
import com.liferay.portal.kernel.service.UserLocalService;
import java.util.Map;
import it.dontesta.labs.liferay.portal.security.audit.router.constants.EventTypes;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* @author Antonio Musarra
*/
@Component(
immediate = true,
property = {"key=auth.failure"},
service = AuthFailure.class
)
public class LoginFailure implements AuthFailure {
@Override
public void onFailureByEmailAddress(
long companyId, String emailAddress, Map<String, String[]> headerMap,
Map<String, String[]> parameterMap) {
try {
User user = _userLocalService.getUserByEmailAddress(
companyId, emailAddress);
AuditMessage auditMessage = buildAuditMessage(
user, headerMap, "Failed to authenticate by email address");
_auditRouter.route(auditMessage);
}
catch (AuditException ae) {
if (_log.isWarnEnabled()) {
_log.warn("Unable to route audit message", ae);
}
}
catch (Exception e) {
if (_log.isWarnEnabled()) {
_log.warn("Unable to route audit message", e);
}
}
}
@Override
public void onFailureByScreenName(
long companyId, String screenName, Map<String, String[]> headerMap,
Map<String, String[]> parameterMap) {
try {
User user = _userLocalService.getUserByScreenName(
companyId, screenName);
AuditMessage auditMessage = buildAuditMessage(
user, headerMap, "Failed to authenticate by screen name");
_auditRouter.route(auditMessage);
}
catch (AuditException ae) {
if (_log.isWarnEnabled()) {
_log.warn("Unable to route audit message", ae);
}
}
catch (Exception e) {
if (_log.isWarnEnabled()) {
_log.warn("Unable to route audit message", e);
}
}
}
@Override
public void onFailureByUserId(
long companyId, long userId, Map<String, String[]> headerMap,
Map<String, String[]> parameterMap) {
try {
User user = _userLocalService.getUserById(companyId, userId);
AuditMessage auditMessage = buildAuditMessage(
user, headerMap, "Failed to authenticate by user ID");
_auditRouter.route(auditMessage);
}
catch (AuditException ae) {
if (_log.isWarnEnabled()) {
_log.warn("Unable to route audit message", ae);
}
}
catch (Exception e) {
if (_log.isWarnEnabled()) {
_log.warn("Unable to route audit message", e);
}
}
}
protected AuditMessage buildAuditMessage(
User user, Map<String, String[]> headerMap, String reason) {
JSONObject additionalInfoJSONObject = _jsonFactory.createJSONObject();
additionalInfoJSONObject.put(
"headers", _jsonFactory.serialize(headerMap));
additionalInfoJSONObject.put("reason", reason);
AuditMessage auditMessage = new AuditMessage(
EventTypes.LOGIN_FAILURE, user.getCompanyId(), user.getUserId(),
user.getFullName(), User.class.getName(),
String.valueOf(user.getPrimaryKey()), null,
additionalInfoJSONObject);
return auditMessage;
}
private static final Log _log = LogFactoryUtil.getLog(LoginFailure.class);
@Reference
private AuditRouter _auditRouter;
@Reference
private JSONFactory _jsonFactory;
@Reference
private UserLocalService _userLocalService;
}
package it.dontesta.labs.liferay.portal.security.audit.message.processor;
import com.liferay.mail.kernel.model.MailMessage;
import com.liferay.mail.kernel.service.MailServiceUtil;
import com.liferay.portal.configuration.metatype.bnd.util.ConfigurableUtil;
import com.liferay.portal.kernel.audit.AuditMessage;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.security.audit.AuditMessageProcessor;
import it.dontesta.labs.liferay.portal.security.audit.message.processor.configuration.DummyAuditMessageProcessorConfiguration;
import it.dontesta.labs.liferay.portal.security.audit.message.processor.configuration.LoginFailureAuditMessageProcessorConfiguration;
import it.dontesta.labs.liferay.portal.security.audit.router.constants.EventTypes;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Modified;
import javax.mail.internet.InternetAddress;
import java.util.Map;
/**
* @author Antonio Musarra
*/
@Component(
configurationPid = "it.dontesta.labs.liferay.portal.security.audit.message.processor.configuration.LoginFailureAuditMessageProcessorConfiguration",
immediate = true,
property = "eventTypes=" + EventTypes.LOGIN_FAILURE,
service = AuditMessageProcessor.class
)
public class LoginFailureAuditMessageProcessor implements AuditMessageProcessor {
@Override
public void process(AuditMessage auditMessage) {
try {
doProcess(auditMessage);
}
catch (Exception e) {
_log.fatal("Unable to process audit message " + auditMessage, e);
}
}
@Activate
@Modified
protected void activate(Map<String, Object> properties) {
_loginFailureAuditMessageProcessorConfiguration =
ConfigurableUtil.createConfigurable(
LoginFailureAuditMessageProcessorConfiguration.class, properties);
}
protected void doProcess(AuditMessage auditMessage) throws Exception {
if (_loginFailureAuditMessageProcessorConfiguration.enabled()) {
if(_log.isDebugEnabled()) {
_log.debug("Login Failure processor processing this Audit Message => "
+ auditMessage.toJSONObject());
}
InternetAddress fromAddress = null;
InternetAddress toAddress = null;
try {
fromAddress = new InternetAddress(
_loginFailureAuditMessageProcessorConfiguration.from());
toAddress = new InternetAddress(
_loginFailureAuditMessageProcessorConfiguration.reportTo());
MailMessage mailMessage = new MailMessage();
mailMessage.setTo(toAddress);
mailMessage.setFrom(fromAddress);
mailMessage.setSubject(
_loginFailureAuditMessageProcessorConfiguration.emailSubject());
mailMessage.setBody(auditMessage.toJSONObject().toString());
MailServiceUtil.sendEmail(mailMessage);
if(_log.isInfoEnabled()) {
_log.info("Send audit email to "
+ _loginFailureAuditMessageProcessorConfiguration.reportTo());
}
} catch (Exception e) {
if(_log.isWarnEnabled()) {
_log.warn("Send email failed.", e);
}
}
}
}
private static final Log _log = LogFactoryUtil.getLog(
LoginFailureAuditMessageProcessor.class);
private volatile LoginFailureAuditMessageProcessorConfiguration
_loginFailureAuditMessageProcessorConfiguration;
}
package it.dontesta.labs.liferay.portal.security.audit.router;
import com.liferay.portal.configuration.metatype.bnd.util.ConfigurableUtil;
import com.liferay.portal.kernel.audit.AuditException;
import com.liferay.portal.kernel.audit.AuditMessage;
import com.liferay.portal.kernel.audit.AuditRouter;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.messaging.DestinationNames;
import com.liferay.portal.kernel.messaging.MessageBus;
import com.liferay.portal.kernel.messaging.proxy.ProxyMessageListener;
import com.liferay.portal.kernel.util.HashMapDictionary;
import com.liferay.portal.kernel.util.StringPool;
import com.liferay.portal.kernel.util.StringUtil;
import com.liferay.portal.kernel.util.Validator;
import com.liferay.portal.security.audit.AuditMessageProcessor;
import com.liferay.portal.security.audit.configuration.AuditConfiguration;
import it.dontesta.labs.liferay.portal.security.audit.router.constants.AuditConstants;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ConfigurationPolicy;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.osgi.service.component.annotations.ReferencePolicyOption;
import java.util.Dictionary;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* @author Antonio Musarra
*/
@Component(
configurationPid = "com.liferay.portal.security.audit.configuration.AuditConfiguration",
configurationPolicy = ConfigurationPolicy.OPTIONAL,
immediate = true,
service = StandardAuditRouter.class
)
public class StandardAuditRouter implements AuditRouter {
@Override
public boolean isDeployed() {
int auditMessageProcessorsCount = _auditMessageProcessors.size();
return (auditMessageProcessorsCount > 0) ||
!_globalAuditMessageProcessors.isEmpty();
}
@Override
public void route(AuditMessage auditMessage) throws AuditException {
if (!getAuditEnabled()) {
if (_log.isWarnEnabled()) {
_log.warn("Liferay Portal Security Audit disabled, "
+ "not processing this message: => "
+ auditMessage.toJSONObject()
);
}
return;
}
if (!isDeployed()) {
if (_log.isWarnEnabled()) {
_log.warn("No Audit Message Processor installed.");
}
return;
}
for (AuditMessageProcessor globalAuditMessageProcessor :
_globalAuditMessageProcessors) {
globalAuditMessageProcessor.process(auditMessage);
}
String eventType = auditMessage.getEventType();
Set<AuditMessageProcessor> auditMessageProcessors =
_auditMessageProcessors.get(eventType);
if (auditMessageProcessors != null) {
for (AuditMessageProcessor auditMessageProcessor :
auditMessageProcessors) {
auditMessageProcessor.process(auditMessage);
}
}
}
@Activate
@Modified
protected void activate(BundleContext bundleContext,
Map<String, Object> properties) {
_auditConfiguration =
ConfigurableUtil.createConfigurable(AuditConfiguration.class,
properties);
if (_log.isInfoEnabled()) {
_log.info("Liferay Portal Security Audit enabled : "
+ getAuditEnabled());
}
ProxyMessageListener proxyMessageListener = new ProxyMessageListener();
proxyMessageListener.setManager(this);
proxyMessageListener.setMessageBus(_messageBus);
Dictionary<String, Object> proxyMessageListenerProperties
= new HashMapDictionary<>();
proxyMessageListenerProperties.put("destination.name", DestinationNames.AUDIT);
_serviceRegistration = bundleContext.registerService(
ProxyMessageListener.class,
proxyMessageListener,
proxyMessageListenerProperties);
}
@Deactivate
protected void deactivate() {
if (_serviceRegistration != null) {
_serviceRegistration.unregister();
}
}
/**
* Get eventType from OSGi properties
*
* @param properties OSGi properties
* @return String array of the eventType
*/
private String[] getEventTypes(
Map<String, Object> properties) {
String eventTypes = (String)properties.get(AuditConstants.EVENT_TYPES);
if (Validator.isNull(eventTypes)) {
throw new IllegalArgumentException(
"The property \"" + AuditConstants.EVENT_TYPES + "\" is null");
}
return StringUtil.split(eventTypes);
}
@Reference (
cardinality = ReferenceCardinality.MULTIPLE,
policy = ReferencePolicy.DYNAMIC,
policyOption = ReferencePolicyOption.GREEDY,
unbind = "unsetAuditMessageProcessor"
)
protected void setAuditMessageProcessor(
AuditMessageProcessor auditMessageProcessor,
Map<String, Object> properties) {
String[] eventTypes = getEventTypes(properties);
if ((eventTypes.length == 1) && eventTypes[0].equals(StringPool.STAR)) {
_globalAuditMessageProcessors.add(auditMessageProcessor);
return;
}
for (String eventType : eventTypes) {
Set<AuditMessageProcessor> auditMessageProcessorsSet =
_auditMessageProcessors.get(eventType);
if (auditMessageProcessorsSet == null) {
auditMessageProcessorsSet = new HashSet<>();
_auditMessageProcessors.put(
eventType, auditMessageProcessorsSet);
}
auditMessageProcessorsSet.add(auditMessageProcessor);
}
}
protected void unsetAuditMessageProcessor(
AuditMessageProcessor auditMessageProcessor,
Map<String, Object> properties) {
String[] eventTypes = getEventTypes(properties);
if ((eventTypes.length == 1) && eventTypes[0].equals(StringPool.STAR)) {
_globalAuditMessageProcessors.remove(auditMessageProcessor);
return;
}
for (String eventType : eventTypes) {
Set<AuditMessageProcessor> auditMessageProcessorsSet =
_auditMessageProcessors.get(eventType);
if (auditMessageProcessorsSet == null) {
continue;
}
auditMessageProcessorsSet.remove(auditMessageProcessor);
}
}
private boolean getAuditEnabled() {
return _auditConfiguration.enabled();
}
private static final Log _log = LogFactoryUtil.getLog(
StandardAuditRouter.class);
private final Map<String, Set<AuditMessageProcessor>>
_auditMessageProcessors = new ConcurrentHashMap<>();
private final List<AuditMessageProcessor> _globalAuditMessageProcessors =
new CopyOnWriteArrayList<>();
private volatile AuditConfiguration _auditConfiguration;
private ServiceRegistration<ProxyMessageListener> _serviceRegistration;
@Reference
private MessageBus _messageBus;
}