amusarra
1/25/2018 - 10:50 AM

Liferay Portal Security Audit

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;

}