package hirs.persist;
import hirs.FilteredRecordsList;
import static org.apache.logging.log4j.LogManager.getLogger;
import hirs.data.persist.Alert;
import hirs.data.persist.baseline.Baseline;
import hirs.data.persist.Device;
import hirs.data.persist.DeviceGroup;
import hirs.data.persist.Policy;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import hirs.data.persist.Report;
import hirs.data.persist.enums.AlertSource;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Property;
import org.hibernate.criterion.Restrictions;
import org.hibernate.criterion.Subqueries;
/**
* This class defines a AlertManager
that stores policies in a
* database.
*/
public class DBAlertManager extends DBManager implements AlertManager {
private static final Logger LOGGER = getLogger(DBAlertManager.class);
/**
* Creates a new DBAlertManager
. The optional SessionFactory
* parameter is used to initialize a session factory to manage all hibernate
* sessions.
*
* @param factory session factory to manage connections to hibernate db
*/
public DBAlertManager(final SessionFactory factory) {
super(Alert.class, factory);
}
/**
* Saves the Alert
in the database and returns it.
*
* @param alert
* alert to save
* @return Alert
that was saved
* @throws AlertManagerException
* if alert has previously been saved or an error occurs while
* trying to save it to the database
*/
@Override
public final Alert saveAlert(final Alert alert)
throws AlertManagerException {
LOGGER.debug("saving alert: {}", alert);
try {
return super.save(alert);
} catch (DBManagerException e) {
throw new AlertManagerException(e);
}
}
/**
* Updates all of the {@link Alert}s provided in the list.
*
* @param alerts list of alerts to be updated
* @return list of updated Alerts
* @throws AlertManagerException
* if unable to update the list of Alerts
*/
@Override
public final List updateAlerts(final List alerts) throws AlertManagerException {
LOGGER.debug("updating object");
if (alerts == null) {
LOGGER.debug("null object argument");
throw new NullPointerException("object");
}
Transaction tx = null;
List updatedAlerts = new ArrayList<>();
Session session = getFactory().getCurrentSession();
try {
LOGGER.debug("updating object in db");
tx = session.beginTransaction();
for (Alert alert : alerts) {
updatedAlerts.add((Alert) session.merge(alert));
}
tx.commit();
} catch (Exception e) {
final String msg = "unable to update alert";
LOGGER.error(msg, e);
if (tx != null) {
LOGGER.debug("rolling back transaction");
tx.rollback();
}
throw new AlertManagerException(msg, e);
}
return updatedAlerts;
}
/**
* Returns a list of all Alert
s.
* This searches through the database for this information.
*
* @return list of Alert
s
* @throws AlertManagerException
* if unable to search the database
*/
@Override
public final List getAlertList() throws AlertManagerException {
LOGGER.debug("getting alert list");
try {
return super.getList(Alert.class);
} catch (DBManagerException e) {
throw new AlertManagerException(e);
}
}
/**
* Returns a list of all Alert
s that relate to the provided Report
* ID. If the given reportId is null or an empty, alerts for all reports are provided. The
* Alerts are ordered by a column and direction (ASC, DESC) that is provided by the user.
* This method helps support the server-side processing in the JQuery DataTables. Alerts with a
* non-null archivedTime are not included in the returned list.
*
* @param reportId ID of the Report to return Alerts from, empty or null for all Alerts
* @param columnToOrder Column to be ordered
* @param ascending direction of sort
* @param firstResult starting point of first result in set
* @param maxResults total number we want returned for display in table
* @param search string of criteria to be matched to visible columns
* @param listType enumeration indicating if the returned list conatins resolved or
* unresolved alerts
* @param searchableColumns Map of String and boolean values with column
* headers and whether they are to. Boolean is true if field provides
* a typical String that can be searched by Hibernate without
* transformation.
* @param beginDate the earliest date of any alert returned from this method. Can be null.
* @param endDate the latest date of any alert returned from this method. Can be null.
* @return FilteredRecordsList object with fields for DataTables
* @throws AlertManagerException
* if unable to create the list
*/
@Override
@SuppressWarnings("checkstyle:parameternumber")
public final FilteredRecordsList getOrderedAlertList(
final String reportId, final String columnToOrder, final boolean ascending,
final int firstResult, final int maxResults, final String search,
final AlertListType listType,
final Map searchableColumns, final Date beginDate, final Date endDate)
throws AlertManagerException {
if (columnToOrder == null) {
LOGGER.debug("null object argument");
throw new NullPointerException("object");
}
// verify that the report ID is a legit UUID
if (StringUtils.isNotEmpty(reportId)) {
try {
UUID.fromString(reportId);
} catch (IllegalArgumentException iae) {
throw new AlertManagerException(reportId + " is not a valid UUID", iae);
}
}
final FilteredRecordsList alerts;
// check that the alert is not archived and that it is in the specified report
CriteriaModifier modifier = new CriteriaModifier() {
@Override
public void modify(final Criteria criteria) {
if (listType == AlertListType.RESOLVED_ALERTS) {
criteria.add(Restrictions.isNotNull("archivedTime"));
} else {
criteria.add(Restrictions.isNull("archivedTime"));
}
// filter by date, if specified
if (null != beginDate) {
criteria.add(Restrictions.ge("createTime", beginDate));
}
if (null != endDate) {
criteria.add(Restrictions.le("createTime", endDate));
}
if (StringUtils.isNotEmpty(reportId)) {
// creating a separate criteria associated with the report field is necessary
// for this to work in HSQL and MySQL to avoid column ambiguity.
Criteria reportCriteria = criteria.createCriteria("report");
reportCriteria.add(Restrictions.eq("id", UUID.fromString(reportId)));
}
}
};
try {
LOGGER.debug("querying db for alerts");
alerts = super.getOrderedList(Alert.class, columnToOrder, ascending, firstResult,
maxResults, search, searchableColumns, modifier);
} catch (DBManagerException e) {
throw new AlertManagerException(e);
}
return alerts;
}
/**
* Retrieves the Alert
from the database. This searches the
* database for an entry whose name matches name
. It then
* reconstructs a Alert
object from the database entry
*
* @param id
* id of the Alert
* @return alert if found, otherwise null.
* @throws AlertManagerException
* if unable to search the database or recreate the
* Alert
*/
@Override
public final Alert getAlert(final UUID id) throws AlertManagerException {
LOGGER.debug("getting alert: {}", id);
try {
return super.get(id);
} catch (DBManagerException e) {
throw new AlertManagerException(e);
}
}
/**
* Retrieves all {@link Alert}s associated with the provided {@link Policy}.
*
* @param policy policy that is being evaluated
* @return list of all alerts associated with {@link Policy}
* @throws AlertManagerException
* If there is a query error
*/
@Override
public final List getAlertsForPolicy(final Policy policy)
throws AlertManagerException {
LOGGER.debug("");
if (policy == null) {
throw new NullPointerException("policy was null");
}
List alerts = new ArrayList<>();
Transaction tx = null;
Session session = getFactory().getCurrentSession();
try {
tx = session.beginTransaction();
// query hibernate to count alerts with the given deviceName and null archivedTime
Criteria criteria = session.createCriteria(Alert.class);
criteria.add(Restrictions.eq("policyId", policy.getId()));
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
List list = criteria.list();
for (Object o : list) {
if (o instanceof Alert) {
alerts.add((Alert) o);
}
}
tx.commit();
} catch (HibernateException e) {
final String msg = "unable to query alerts table";
LOGGER.error(msg, e);
if (tx != null) {
LOGGER.debug("rolling back transaction");
tx.rollback();
}
throw e;
}
return alerts;
}
/**
* Retrieves all {@link Alert}s associated with the provided {@link Baseline}.
*
* @param baseline baseline that is being evaluated
* @return list of all alerts associated with {@link Baseline}
* @throws AlertManagerException
* If there is a query error
*/
@Override
public final List getAlertsForBaseline(final Baseline baseline)
throws AlertManagerException {
LOGGER.debug("");
if (baseline == null) {
throw new NullPointerException("baseline was null");
}
List alerts = new ArrayList<>();
Transaction tx = null;
Session session = getFactory().getCurrentSession();
try {
tx = session.beginTransaction();
// query hibernate to retrieve alerts with the given baseline id
Criteria criteria = session.createCriteria(Alert.class);
criteria.add(Restrictions.eq("baselineId", baseline.getId()));
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
List list = criteria.list();
for (Object o : list) {
if (o instanceof Alert) {
alerts.add((Alert) o);
}
}
tx.commit();
} catch (HibernateException e) {
final String msg = "unable to query alerts table";
LOGGER.error(msg, e);
if (tx != null) {
LOGGER.debug("rolling back transaction");
tx.rollback();
}
throw e;
}
return alerts;
}
/**
* Retrieves the total number of Unresolved {@link Alert}s associated with the provided
* {@link Baseline}.
*
* @param baseline baseline that is being evaluated
* @return number of unresolved alerts associated with Baseline
* @throws AlertManagerException
* If there is a query error
*/
@Override
public final long getTotalAlertsForBaseline(final Baseline baseline)
throws AlertManagerException {
LOGGER.debug("");
if (baseline == null) {
throw new NullPointerException("baseline was null");
}
Transaction tx = null;
Session session = getFactory().getCurrentSession();
try {
tx = session.beginTransaction();
// query hibernate to count alerts with the given deviceName and null archivedTime
Criteria criteria = session.createCriteria(Alert.class);
criteria.add(Restrictions.isNull("archivedTime"));
criteria.add(Restrictions.eq("baselineId", baseline.getId()));
criteria.setProjection(Projections.rowCount()).uniqueResult();
Long result = (Long) criteria.uniqueResult();
tx.commit();
if (result == null) {
throw new AlertManagerException("failed to query unresolved alert count");
} else {
return result;
}
} catch (HibernateException e) {
final String msg = "unable to query alerts table";
LOGGER.error(msg, e);
if (tx != null) {
LOGGER.debug("rolling back transaction");
tx.rollback();
}
throw e;
}
}
private Criteria getCriteriaForTrustAlertsForDevice(final Device device,
final Report integrityReport,
final Criterion optionalCriterion,
final Session session) {
if (null == device) {
throw new NullPointerException("device was null");
}
if (null == integrityReport) {
throw new NullPointerException("integrityReport was null");
}
if (null == session) {
throw new NullPointerException("session was null");
}
// query hibernate to count alerts with the given deviceName and null archivedTime
Criteria criteria = session.createCriteria(Alert.class);
criteria.add(Restrictions.isNull("archivedTime"));
criteria.add(Restrictions.eq("deviceName", device.getName()));
// alerts for this report, or alerts not associated with any report (case 1 and 2)
Criterion reportEqualsCriterion = Restrictions.eq("report", integrityReport);
Criterion reportNullCriterion = Restrictions.isNull("report");
// only apply the optional criterion to the disjunction if there is one.
if (null != optionalCriterion) {
criteria.add(Restrictions.disjunction(
reportEqualsCriterion, reportNullCriterion, optionalCriterion));
} else {
criteria.add(Restrictions.disjunction(
reportEqualsCriterion, reportNullCriterion));
}
return criteria;
}
/**
* Gets the set of alerts for a device in order to determine the status of
* the device (trusted or untrusted).
*
* The alerts meet one or more of these specifications:
*
* - Have no report associated (missed periodic report alerts) for this device
* - Are associated with the provided integrity report
* - Match the specified criteria. e.g. leftover alerts from
* delta reports in the current series of delta reports).
*
* @param device the device to query for alerts on
* @param integrityReport the integrity report to find associated alerts with
* @param optionalCriterion the optional additional criteria for which to query on
* @return the set of device alerts associated with trust
*/
@Override
public List getTrustAlerts(final Device device, final Report integrityReport,
final Criterion optionalCriterion) {
LOGGER.debug("getting trust alerts for {}", device);
Transaction tx = null;
Session session = getFactory().getCurrentSession();
try {
tx = session.beginTransaction();
Criteria criteria = getCriteriaForTrustAlertsForDevice(device, integrityReport,
optionalCriterion, session);
List alerts = new ArrayList<>();
List list = criteria.list();
for (Object o : list) {
if (o instanceof Alert) {
alerts.add((Alert) o);
}
}
tx.commit();
return alerts;
} catch (HibernateException | NullPointerException e) {
final String msg = "unable to query alerts table";
LOGGER.error(msg, e);
if (tx != null) {
LOGGER.debug("rolling back transaction");
tx.rollback();
}
throw e;
}
}
/**
* Gets the count of trust alerts for a device. See {@link #getTrustAlerts} for more
* information about which alerts are counted.
*
* @param device the device to query for alerts on
* @param integrityReport the integrity report to find associated alerts with
* @param optionalCriterion the optional additional criteria for which to query on
* @return the count of alerts associated with trust
*/
@Override
public final int getTrustAlertCount(final Device device, final Report integrityReport,
final Criterion optionalCriterion) {
LOGGER.debug("getting trust alert count for {}", device);
Transaction tx = null;
Session session = getFactory().getCurrentSession();
try {
tx = session.beginTransaction();
Criteria criteria = getCriteriaForTrustAlertsForDevice(device, integrityReport,
optionalCriterion, session);
criteria.setProjection(Projections.rowCount()).uniqueResult();
Long result = (Long) criteria.uniqueResult();
tx.commit();
return result.intValue();
} catch (HibernateException | NullPointerException e) {
final String msg = "unable to query alerts table";
LOGGER.error(msg, e);
if (tx != null) {
LOGGER.debug("rolling back transaction");
tx.rollback();
}
throw e;
}
}
/**
* Overloads the resolveAlerts method and provides a null description for the
* alert resolution.
*
* @param alerts - list of Alert objects to be marked as resolved.
* @throws AlertManagerException
* if unable to save the list
*/
@Override
public final void resolveAlerts(final List alerts)
throws AlertManagerException {
resolveAlerts(alerts, null);
}
/**
* Marks all Alerts that are provided as arguments as resolved. This is used as
* a "soft delete" method and will ensure they no longer appear in the Alert
* table on the Portal.
*
* @param alerts - Alert objects to be marked as resolved
* @param description - description of action taken. The description can be null
* @throws AlertManagerException
* if unable to save the list
*/
@Override
public final void resolveAlerts(final List alerts, final String description)
throws AlertManagerException {
if (alerts == null) {
String message = "list of alert objects was null";
LOGGER.debug(message);
throw new NullPointerException(message);
}
LOGGER.debug("Marking " + alerts.size() + " alerts to resolved and saving.");
Transaction tx = null;
Session session = getFactory().getCurrentSession();
try {
LOGGER.debug("saving object in db");
tx = session.beginTransaction();
for (Alert alert : alerts) {
alert.archive(description);
session.merge(alert);
LOGGER.info("Alert {} is marked as resolved.", alert.getId());
}
tx.commit();
} catch (Exception e) {
final String msg = "unable to save alert";
LOGGER.error(msg, e);
if (tx != null) {
LOGGER.debug("rolling back transaction");
tx.rollback();
}
throw new DBManagerException(msg, e);
}
}
/**
* Return the count of unresolved alerts associated with the given device.
*
* @param device associated with unresolved alerts being counted
* @return count of unresolved alerts
*/
public final int countUnresolvedAlerts(final Device device) {
if (device == null) {
LOGGER.warn("null device found, returning 0");
return 0;
}
Transaction tx = null;
Session session = getFactory().getCurrentSession();
try {
LOGGER.debug("querying alerts table for unresolved alerts");
tx = session.beginTransaction();
// query hibernate to count alerts with the given deviceName and null archivedTime
Criteria criteria = session.createCriteria(Alert.class);
criteria.add(Restrictions.isNull("archivedTime"));
criteria.add(Restrictions.eq("deviceName", device.getName()));
criteria.setProjection(Projections.rowCount()).uniqueResult();
Long result = (Long) criteria.uniqueResult();
tx.commit();
if (result == null) {
throw new AlertManagerException("failed to query unresolved alert count");
} else {
return result.intValue();
}
} catch (HibernateException e) {
final String msg = "unable to query alerts table";
LOGGER.error(msg, e);
if (tx != null) {
LOGGER.debug("rolling back transaction");
tx.rollback();
}
throw e;
}
}
/**
* Return the count of unresolved alerts associated with the given device that originate from
* the given AlertSource.
*
* @param device associated with unresolved alerts being counted
* @param source counted alerts must originate from
* @return count of unresolved alerts
*/
public final int countUnresolvedAlerts(final Device device, final AlertSource source) {
if (device == null) {
String msg = "invalid argument - null value for device";
LOGGER.error(msg);
throw new IllegalArgumentException(msg);
}
if (source == null) {
String msg = "invalid argument - null value for source";
LOGGER.error(msg);
throw new IllegalArgumentException(msg);
}
Transaction tx = null;
Session session = getFactory().getCurrentSession();
try {
LOGGER.debug("querying alerts table for unresolved alerts");
tx = session.beginTransaction();
Criteria criteria = session.createCriteria(Alert.class);
criteria.add(Restrictions.isNull("archivedTime"));
criteria.add(Restrictions.eq("deviceName", device.getName()));
criteria.add(Restrictions.eq("source", source));
criteria.setProjection(Projections.rowCount()).uniqueResult();
Long result = (Long) criteria.uniqueResult();
tx.commit();
if (result == null) {
throw new AlertManagerException("failed to query unresolved alert count");
} else {
return result.intValue();
}
} catch (HibernateException e) {
final String msg = "unable to query alerts table";
LOGGER.error(msg, e);
if (tx != null) {
LOGGER.debug("rolling back transaction");
tx.rollback();
}
throw e;
}
}
/**
* Count the total number of devices with at least one unresolved alert within the given group.
*
* @param deviceGroup to count devices from
* @return count of devices with unresolved alerts
*/
public final int countUnresolvedDevices(final DeviceGroup deviceGroup) {
if (deviceGroup == null) {
String msg = "invalid argument - null value for device group";
LOGGER.error(msg);
throw new IllegalArgumentException(msg);
}
Transaction tx = null;
Session session = getFactory().getCurrentSession();
try {
LOGGER.debug("querying alerts table for unresolved devices");
tx = session.beginTransaction();
// first use a subquery to list the devices in the given group
DetachedCriteria deviceQuery = DetachedCriteria.forClass(Device.class);
deviceQuery.createAlias("deviceGroup", "g");
deviceQuery.add(Restrictions.eq("g.name", deviceGroup.getName()));
deviceQuery.setProjection(Property.forName("name"));
// now query within that group for unique device names among unresolved alerts
Criteria criteria = session.createCriteria(Alert.class);
criteria.add(Restrictions.isNull("archivedTime"));
criteria.add(Subqueries.propertyIn("deviceName", deviceQuery));
criteria.setProjection(Projections.countDistinct("deviceName"));
Long result = (Long) criteria.uniqueResult();
tx.commit();
if (result == null) {
throw new AlertManagerException("failed to query unresolved alert count");
} else {
return result.intValue();
}
} catch (HibernateException e) {
final String msg = "unable to query alerts table";
LOGGER.error(msg, e);
if (tx != null) {
LOGGER.debug("rolling back transaction");
tx.rollback();
}
throw e;
}
}
}