Exemplo n.º 1
0
def get_hsps_by_inventory_id(inventory_id, limit, offset):
    """
    return a list of historical system profiles for a given inventory id
    """
    validate_uuids([inventory_id])
    account_number = view_helpers.get_account_number(request)
    query_results = db_interface.get_hsps_by_inventory_id(
        inventory_id, account_number, limit, offset)
    valid_profiles = _filter_old_hsps(query_results)

    if not valid_profiles:
        raise HTTPError(
            HTTPStatus.NOT_FOUND,
            message="no historical profiles found for inventory_id %s" %
            inventory_id,
        )

    # TODO: request just these three fields from the DB, instead of fetching
    # the full records, then slicing and sorting

    profile_metadata = []
    for profile in valid_profiles:
        profile_metadata.append({
            "captured_date": profile.captured_date,
            "id": profile.id,
            "system_id": profile.inventory_id,
        })
    sorted_profile_metadata = sorted(profile_metadata,
                                     key=lambda p: p["captured_date"],
                                     reverse=True)

    result = {"profiles": sorted_profile_metadata}
    return {"data": [result]}
Exemplo n.º 2
0
def get_baselines_by_system_id(system_id=None):
    account_number = view_helpers.get_account_number(request)

    if system_id:
        validate_uuids([system_id])
        query = SystemBaselineMappedSystem.query.filter(
            SystemBaselineMappedSystem.account == account_number,
            SystemBaselineMappedSystem.system_id == system_id,
        )
    else:
        query = SystemBaselineMappedSystem.query.filter(
            SystemBaselineMappedSystem.account == account_number
        )

    try:
        query_results = query.all()
    except Exception:
        message = "Unknown error when reading baselines by system id"
        current_app.logger.audit(message, request=request, success=False)
        raise

    message = "read baselines with system"
    current_app.logger.audit(message, request=request, success=True)

    return [result.system_baseline_id for result in query_results]
Exemplo n.º 3
0
def copy_baseline_by_id(baseline_id, display_name):
    """
    create a new baseline given an existing ID
    """
    ensure_rbac_write()
    validate_uuids([baseline_id])

    # ensure display_name is not null
    if not display_name:
        raise HTTPError(HTTPStatus.BAD_REQUEST,
                        message="no value given for display_name")

    account_number = view_helpers.get_account_number(request)
    _check_for_existing_display_name(display_name, account_number)
    _check_for_whitespace_in_display_name(display_name)

    query = SystemBaseline.query.filter(
        SystemBaseline.account == account_number,
        SystemBaseline.id == baseline_id)

    copy_baseline = query.first_or_404()
    db.session.expunge(copy_baseline)
    make_transient(copy_baseline)
    copy_baseline.id = None
    copy_baseline.created_on = None
    copy_baseline.modified_on = None
    copy_baseline.display_name = display_name
    db.session.add(copy_baseline)
    db.session.commit()
    return copy_baseline.to_json()
Exemplo n.º 4
0
def create_deletion_request_for_systems(baseline_id, body):
    ensure_rbac_write()
    validate_uuids([baseline_id])
    system_ids = body["system_ids"]
    validate_uuids(system_ids)

    delete_systems_with_baseline(baseline_id, system_ids)
    return "OK"
Exemplo n.º 5
0
def create_deletion_request(body):
    """
    delete a list of baselines given their IDs as a list
    """
    ensure_rbac_write()
    baseline_ids = body["baseline_ids"]
    validate_uuids(baseline_ids)
    _delete_baselines(baseline_ids)
    return "OK"
Exemplo n.º 6
0
def delete_baselines_by_ids(baseline_ids):
    """
    delete a list of baselines given their ID
    """
    ensure_rbac_write()
    validate_uuids(baseline_ids)
    if len(set(baseline_ids)) < len(baseline_ids):
        raise HTTPError(HTTPStatus.BAD_REQUEST,
                        message="duplicate IDs in request")
    _delete_baselines(baseline_ids)
    return "OK"
Exemplo n.º 7
0
def update_baseline(baseline_id, system_baseline_patch):
    """
    update a baseline
    """
    ensure_rbac_write()
    validate_uuids([baseline_id])

    account_number = view_helpers.get_account_number(request)
    _check_for_whitespace_in_display_name(
        system_baseline_patch["display_name"])

    # this query is a bit different than what's in _check_for_existing_display_name,
    # since it's OK if the display name is used by the baseline we are updating
    existing_display_name_query = SystemBaseline.query.filter(
        SystemBaseline.account == account_number,
        SystemBaseline.id != baseline_id,
        SystemBaseline.display_name == system_baseline_patch["display_name"],
    )

    if existing_display_name_query.count() > 0:
        raise HTTPError(
            HTTPStatus.BAD_REQUEST,
            message="display_name '%s' already used for this account" %
            system_baseline_patch["display_name"],
        )

    query = SystemBaseline.query.filter(
        SystemBaseline.account == account_number,
        SystemBaseline.id == baseline_id)
    baseline = query.first_or_404()

    try:
        updated_facts = jsonpatch.apply_patch(
            baseline.baseline_facts, system_baseline_patch["facts_patch"])
        _validate_facts(updated_facts)
        baseline.baseline_facts = updated_facts
    except FactValidationError as e:
        raise HTTPError(HTTPStatus.BAD_REQUEST, message=e.message)
    except (jsonpatch.JsonPatchException, jsonpointer.JsonPointerException):
        raise HTTPError(HTTPStatus.BAD_REQUEST,
                        message="unable to apply patch to baseline")

    baseline.display_name = system_baseline_patch["display_name"]

    baseline.baseline_facts = _sort_baseline_facts(baseline.baseline_facts)
    db.session.add(baseline)
    db.session.commit()

    # pull baseline again so we have the correct updated timestamp and fact count
    query = SystemBaseline.query.filter(
        SystemBaseline.account == account_number,
        SystemBaseline.id == baseline_id)
    return [query.first().to_json()]
Exemplo n.º 8
0
def get_baselines_by_ids(baseline_ids, limit, offset, order_by, order_how):
    """
    return a list of baselines given their ID
    """
    validate_uuids(baseline_ids)
    if len(set(baseline_ids)) < len(baseline_ids):
        message = "duplicate IDs in request"
        current_app.logger.audit(message, request=request, success=False)
        raise HTTPError(HTTPStatus.BAD_REQUEST, message=message)

    account_number = view_helpers.get_account_number(request)
    query = SystemBaseline.query.filter(
        SystemBaseline.account == account_number,
        SystemBaseline.id.in_(baseline_ids))

    full_results = query.all()

    message = "read baselines"
    current_app.logger.audit(message, request=request)

    if len(full_results) < len(baseline_ids):
        fetched_ids = {str(result.id) for result in full_results}
        missing_ids = set(baseline_ids) - fetched_ids

        message = "ids [%s] not available to display" % ", ".join(missing_ids)
        current_app.logger.audit(message, request=request, success=False)
        raise HTTPError(
            HTTPStatus.NOT_FOUND,
            message=message,
        )

    count = query.count()

    message = "counted baselines"
    current_app.logger.audit(message, request=request)

    total_available = _get_total_available_baselines()

    query = _create_ordering(order_by, order_how, query)
    query = query.limit(limit).offset(offset)

    query_results = query.all()

    message = "read baselines"
    current_app.logger.audit(message, request=request)

    json_list = [
        baseline.to_json(withhold_facts=False) for baseline in query_results
    ]

    return build_paginated_baseline_list_response(limit, offset, order_by,
                                                  order_how, json_list,
                                                  total_available, count)
Exemplo n.º 9
0
def create_deletion_request(body):
    """
    delete a list of baselines given their IDs as a list
    """
    ensure_rbac_write()
    baseline_ids = body["baseline_ids"]
    validate_uuids(baseline_ids)

    _delete_baselines(baseline_ids)

    message = "deleted baselines"
    current_app.logger.audit(message, request=request, success=True)
    return "OK"
Exemplo n.º 10
0
def delete_baselines_by_ids(baseline_ids):
    """
    delete a list of baselines given their ID
    """
    ensure_rbac_write()
    validate_uuids(baseline_ids)
    if len(set(baseline_ids)) < len(baseline_ids):
        message = "duplicate IDs in request"
        current_app.logger.audit(message, request=request, success=False)
        raise HTTPError(HTTPStatus.BAD_REQUEST, message=message)

    _delete_baselines(baseline_ids)

    message = "deleted baselines"
    current_app.logger.audit(message, request=request, success=True)
    return "OK"
Exemplo n.º 11
0
def delete_systems_by_ids(system_ids):
    """
    delete a list of systems given their system IDs as a list
    """
    validate_uuids(system_ids)
    account_number = view_helpers.get_account_number(request)
    try:
        SystemBaselineMappedSystem.delete_by_system_ids(system_ids, account_number)
    except Exception:
        message = "Unknown error when deleting systems by ids"
        current_app.logger.audit(message, request=request, success=False)
        raise

    message = "delete systems by ids"
    current_app.logger.audit(message, request=request, success=True)

    return "OK"
Exemplo n.º 12
0
def get_hsps_by_ids(profile_ids):
    """
    return a list of historical system profiles for the given profile IDs
    """
    validate_uuids(profile_ids)
    _check_for_duplicates(profile_ids)

    account_number = view_helpers.get_account_number(request)
    result = db_interface.get_hsps_by_profile_ids(profile_ids, account_number)

    # TODO: rely on captured_date and filter in SQL above
    filtered_result = _filter_old_hsps(result)

    _check_for_missing_ids(profile_ids, filtered_result)

    result_with_updated_names = _get_current_names_for_profiles(
        filtered_result)

    return {"data": [r.to_json() for r in result_with_updated_names]}
Exemplo n.º 13
0
def create_systems_with_baseline(baseline_id, body):
    ensure_rbac_write()
    validate_uuids([baseline_id])
    system_ids = body["system_ids"]
    validate_uuids(system_ids)
    if len(set(system_ids)) < len(system_ids):
        message = "duplicate IDs in request"
        current_app.logger.audit(message, request=request, success=False)
        raise HTTPError(HTTPStatus.BAD_REQUEST, message=message)
    account_number = view_helpers.get_account_number(request)

    query = SystemBaseline.query.filter(
        SystemBaseline.account == account_number,
        SystemBaseline.id == baseline_id)
    baseline = query.first_or_404()

    message = "read baseline"
    current_app.logger.audit(message, request=request, success=True)

    try:
        for system_id in system_ids:
            baseline.add_mapped_system(system_id)

        db.session.commit()
    except ValueError as error:
        message = str(error)
        current_app.logger.audit(message, request=request, success=False)
        raise HTTPError(HTTPStatus.BAD_REQUEST, message=message)
    except Exception:
        message = "Unknown error when creating systems with baseline"
        current_app.logger.audit(message, request=request, success=False)
        raise

    message = "created systems with baseline"
    current_app.logger.audit(message, request=request, success=True)

    system_ids = baseline.mapped_system_ids()
    return {"system_ids": system_ids}
Exemplo n.º 14
0
def list_systems_with_baseline(baseline_id):
    validate_uuids([baseline_id])
    account_number = view_helpers.get_account_number(request)

    query = SystemBaseline.query.filter(
        SystemBaseline.account == account_number,
        SystemBaseline.id == baseline_id)
    baseline = query.first_or_404()

    message = "read baseline"
    current_app.logger.audit(message, request=request, success=True)

    try:
        system_ids = baseline.mapped_system_ids()
    except ValueError as error:
        message = str(error)
        current_app.logger.audit(message, request=request, success=False)
        raise HTTPError(HTTPStatus.BAD_REQUEST, message=message)
    except Exception:
        message = "Unknown error when reading mapped system ids"
        current_app.logger.audit(message, request=request, success=False)
        raise

    return {"system_ids": system_ids}
Exemplo n.º 15
0
def comparison_report(
    system_ids,
    baseline_ids,
    historical_sys_profile_ids,
    reference_id,
    auth_key,
    data_format,
):
    """
    return a comparison report
    """
    if len(system_ids + baseline_ids + historical_sys_profile_ids) == 0:
        raise HTTPError(
            HTTPStatus.BAD_REQUEST,
            message="must specify at least one of system, baseline, or HSP",
        )
    if len(system_ids) > len(set(system_ids)):
        raise HTTPError(
            HTTPStatus.BAD_REQUEST,
            message="duplicate UUID specified in system_ids list",
        )

    if len(baseline_ids) > len(set(baseline_ids)):
        raise HTTPError(
            HTTPStatus.BAD_REQUEST,
            message="duplicate UUID specified in baseline_ids list",
        )

    if system_ids:
        validate_uuids(system_ids)
    if baseline_ids:
        validate_uuids(baseline_ids)
    if historical_sys_profile_ids:
        validate_uuids(historical_sys_profile_ids)
    if reference_id:
        validate_uuids([reference_id])
        if reference_id not in (system_ids + baseline_ids +
                                historical_sys_profile_ids):
            raise HTTPError(
                HTTPStatus.BAD_REQUEST,
                message="reference id %s does not match any ids from query" %
                reference_id,
            )

    try:
        systems_with_profiles = []
        baseline_results = []
        hsp_results = []

        if system_ids:
            systems_with_profiles = fetch_systems_with_profiles(
                system_ids, auth_key, current_app.logger, get_event_counters())

        if baseline_ids:
            baseline_results = fetch_baselines(baseline_ids, auth_key,
                                               current_app.logger)
            ensure_correct_system_count(baseline_ids, baseline_results)

        if historical_sys_profile_ids:
            hsp_results = fetch_historical_sys_profiles(
                historical_sys_profile_ids,
                auth_key,
                current_app.logger,
                get_event_counters(),
            )

        comparisons = info_parser.build_comparisons(systems_with_profiles,
                                                    baseline_results,
                                                    hsp_results, reference_id)
        metrics.systems_compared.observe(len(system_ids))
        if data_format == "csv":
            output = make_response(_csvify(comparisons))
            output.headers[
                "Content-Disposition"] = "attachment; filename=export.csv"
            output.headers["Content-type"] = "text/csv"
            return output
        else:
            return jsonify(comparisons)

    except ItemNotReturned as error:
        raise HTTPError(HTTPStatus.NOT_FOUND, message=error.message)
Exemplo n.º 16
0
def comparison_report(
    system_ids,
    baseline_ids,
    historical_sys_profile_ids,
    reference_id,
    auth_key,
    data_format,
    short_circuit,
):
    """
    return a comparison report

    If short_circuit is true, this is a call to see if a single system has
    drifted from a single baseline in order to trigger a Notification if
    necessary, so the only facts that will be compared will be those present
    on the baseline.  If the system has drifted from the baseline, the report
    will contain the key 'drift_event_notify' set to True, otherwise False.
    """
    if len(system_ids + baseline_ids + historical_sys_profile_ids) == 0:
        message = "must specify at least one of system, baseline, or HSP"
        current_app.logger.audit(str(HTTPStatus.BAD_REQUEST) + " " + message,
                                 request=request,
                                 success=False)
        raise HTTPError(
            HTTPStatus.BAD_REQUEST,
            message=message,
        )
    if len(system_ids) > len(set(system_ids)):
        message = "duplicate UUID specified in system_ids list"
        current_app.logger.audit(str(HTTPStatus.BAD_REQUEST) + " " + message,
                                 request=request,
                                 success=False)
        raise HTTPError(
            HTTPStatus.BAD_REQUEST,
            message=message,
        )

    if len(baseline_ids) > len(set(baseline_ids)):
        message = "duplicate UUID specified in baseline_ids list"
        current_app.logger.audit(str(HTTPStatus.BAD_REQUEST) + " " + message,
                                 request=request,
                                 success=False)
        raise HTTPError(
            HTTPStatus.BAD_REQUEST,
            message=message,
        )

    with PT_CR_VALIDATE_UUID.time():
        if system_ids:
            validate_uuids(system_ids)
        if baseline_ids:
            validate_uuids(baseline_ids)
        if historical_sys_profile_ids:
            validate_uuids(historical_sys_profile_ids)
        if reference_id:
            validate_uuids([reference_id])
            if reference_id not in (system_ids + baseline_ids +
                                    historical_sys_profile_ids):
                message = "reference id %s does not match any ids from query" % reference_id
                current_app.logger.audit(
                    str(HTTPStatus.BAD_REQUEST) + " " + message,
                    request=request,
                    success=False,
                )
                raise HTTPError(
                    HTTPStatus.BAD_REQUEST,
                    message=message,
                )

    def get_systems_with_profiles(app, timer, system_ids, auth_key, logger,
                                  counters):
        with timer.time():
            with app.app_context():
                # can raise RBACDenied exception
                message = "reading systems with profiles"
                current_app.logger.audit(message, request=request)
                systems_with_profiles = fetch_systems_with_profiles(
                    system_ids, auth_key, current_app.logger, counters)
        return systems_with_profiles

    def get_baselines(app, timer, baseline_ids, auth_key, logger):
        with timer.time():
            with app.app_context():
                # can raise RBACDenied exception
                message = "reading baselines"
                current_app.logger.audit(message, request=request)
                baseline_results = fetch_baselines(baseline_ids, auth_key,
                                                   logger)
                ensure_correct_system_count(baseline_ids, baseline_results)
        return baseline_results

    def get_historical_sys_profiles(app, timer, historical_sys_profile_ids,
                                    auth_key, logger, counters):
        with timer.time():
            with app.app_context():
                # can raise RBACDenied exception
                message = "reading historical system profiles"
                current_app.logger.audit(message, request=request)
                hsp_results = fetch_historical_sys_profiles(
                    historical_sys_profile_ids,
                    auth_key,
                    logger,
                    counters,
                )
        return hsp_results

    try:
        api_results = {}
        http_errors = []
        other_errors = []

        with PT_CR_API_REQUESTS.time():
            futures = {}
            with concurrent.futures.ThreadPoolExecutor() as executor:
                app = current_app._get_current_object()
                if system_ids:
                    # can raise RBACDenied exception
                    future = executor.submit(
                        get_systems_with_profiles,
                        app,
                        PT_CR_API_SYSTEM_PROFILE_REQUESTS,
                        system_ids,
                        auth_key,
                        current_app.logger,
                        get_event_counters(),
                    )
                    futures[future] = "systems_with_profiles"
                if baseline_ids:
                    # can raise RBACDenied exception
                    future = executor.submit(
                        get_baselines,
                        app,
                        PT_CR_API_BASELINE_REQUESTS,
                        baseline_ids,
                        auth_key,
                        current_app.logger,
                    )
                    futures[future] = "baseline_results"
                if historical_sys_profile_ids:
                    # can raise RBACDenied exception
                    future = executor.submit(
                        get_historical_sys_profiles,
                        app,
                        PT_CR_API_HSP_REQUESTS,
                        historical_sys_profile_ids,
                        auth_key,
                        current_app.logger,
                        get_event_counters(),
                    )
                    futures[future] = "hsp_results"

                for future in concurrent.futures.as_completed(futures):
                    try:
                        api_results[futures[future]] = future.result()
                    except RBACDenied as error:
                        message = error.message
                        current_app.logger.audit(str(HTTPStatus.FORBIDDEN) +
                                                 " " + message,
                                                 request=request)
                        raise HTTPError(HTTPStatus.FORBIDDEN, message=message)
                    except HTTPError as error:
                        http_errors.append(error)
                    except (ItemNotReturned, ServiceError) as error:
                        other_errors.append(error)

            handle_errors(http_errors=http_errors,
                          other_errors=other_errors)  # raises merged error

        systems_with_profiles = api_results.get("systems_with_profiles", [])
        baseline_results = api_results.get("baseline_results", [])
        hsp_results = api_results.get("hsp_results", [])

        with PT_CR_BUILD_COMPARISON.time():
            comparisons = info_parser.build_comparisons(
                systems_with_profiles,
                baseline_results,
                hsp_results,
                reference_id,
                short_circuit,
            )

        metrics.systems_compared.observe(len(system_ids))
        if data_format == "csv":
            output = make_response(_csvify(comparisons))
            output.headers[
                "Content-Disposition"] = "attachment; filename=export.csv"
            output.headers["Content-type"] = "text/csv"
            return output
        else:
            return jsonify(comparisons)

    except ItemNotReturned as error:
        message = error.message
        current_app.logger.audit(str(HTTPStatus.NOT_FOUND) + " " + message,
                                 request=request,
                                 success=False)
        raise HTTPError(HTTPStatus.NOT_FOUND, message=message)
Exemplo n.º 17
0
def create_baseline(system_baseline_in):
    """
    create a baseline
    """
    ensure_rbac_write()
    account_number = view_helpers.get_account_number(request)

    if "values" in system_baseline_in and "value" in system_baseline_in:
        message = "'values' and 'value' cannot both be defined for system baseline"
        current_app.logger.audit(message, request=request, success=False)
        raise HTTPError(
            HTTPStatus.BAD_REQUEST,
            message=message,
        )

    _check_for_existing_display_name(system_baseline_in["display_name"],
                                     account_number)
    _check_for_whitespace_in_display_name(system_baseline_in["display_name"])

    message = "counted baselines"
    current_app.logger.audit(message, request=request)

    baseline_facts = []
    if "baseline_facts" in system_baseline_in:
        if "inventory_uuid" in system_baseline_in:
            message = (
                "Both baseline facts and inventory id provided, can clone only one."
            )
            current_app.logger.audit(message, request=request, success=False)
            raise HTTPError(
                HTTPStatus.BAD_REQUEST,
                message=message,
            )
        if "hsp_uuid" in system_baseline_in:
            message = "Both baseline facts and hsp id provided, can clone only one."
            current_app.logger.audit(message, request=request, success=False)
            raise HTTPError(
                HTTPStatus.BAD_REQUEST,
                message=message,
            )
        baseline_facts = system_baseline_in["baseline_facts"]
    elif "hsp_uuid" in system_baseline_in:
        if "inventory_uuid" in system_baseline_in:
            message = "Both hsp id and system id provided, can clone only one."
            current_app.logger.audit(message, request=request, success=False)
            raise HTTPError(
                HTTPStatus.BAD_REQUEST,
                message=message,
            )
        validate_uuids([system_baseline_in["hsp_uuid"]])
        auth_key = get_key_from_headers(request.headers)
        try:
            hsp = fetch_historical_sys_profiles(
                [system_baseline_in["hsp_uuid"]],
                auth_key,
                current_app.logger,
                get_event_counters(),
            )[0]
            message = "read historical system profiles"
            current_app.logger.audit(message, request=request)
        except ItemNotReturned:
            message = "hsp UUID %s not available" % system_baseline_in[
                "hsp_uuid"]
            current_app.logger.audit(message, request=request, success=False)
            raise HTTPError(
                HTTPStatus.NOT_FOUND,
                message=message,
            )
        except RBACDenied as error:
            message = error.message
            current_app.logger.audit(message, request=request, success=False)
            raise HTTPError(HTTPStatus.FORBIDDEN, message=message)

        system_name = "clone_from_hsp_unused"
        baseline_facts = _parse_from_sysprofile(hsp["system_profile"],
                                                system_name,
                                                current_app.logger)
    elif "inventory_uuid" in system_baseline_in:
        validate_uuids([system_baseline_in["inventory_uuid"]])
        auth_key = get_key_from_headers(request.headers)
        try:
            system_with_profile = fetch_systems_with_profiles(
                [system_baseline_in["inventory_uuid"]],
                auth_key,
                current_app.logger,
                get_event_counters(),
            )[0]
            message = "read system with profiles"
            current_app.logger.audit(message, request=request)
        except ItemNotReturned:
            message = ("inventory UUID %s not available" %
                       system_baseline_in["inventory_uuid"])
            current_app.logger.audit(message, request=request, success=False)
            raise HTTPError(
                HTTPStatus.NOT_FOUND,
                message=message,
            )
        except RBACDenied as error:
            message = error.message
            current_app.logger.audit(message, request=request, success=False)
            raise HTTPError(HTTPStatus.FORBIDDEN, message=message)

        system_name = profile_parser.get_name(system_with_profile)
        baseline_facts = _parse_from_sysprofile(
            system_with_profile["system_profile"], system_name,
            current_app.logger)

    try:
        _validate_facts(baseline_facts)
    except FactValidationError as error:
        message = error.message
        current_app.logger.audit(message, request=request, success=False)
        raise HTTPError(HTTPStatus.BAD_REQUEST, message=message)

    baseline = SystemBaseline(
        account=account_number,
        display_name=system_baseline_in["display_name"],
        baseline_facts=baseline_facts,
    )
    baseline.baseline_facts = _sort_baseline_facts(baseline.baseline_facts)
    db.session.add(baseline)

    db.session.commit(
    )  # commit now so we get a created/updated time before json conversion

    message = "creat baselines"
    current_app.logger.audit(message, request=request)

    return baseline.to_json()
Exemplo n.º 18
0
def comparison_report(
    system_ids,
    baseline_ids,
    historical_sys_profile_ids,
    reference_id,
    auth_key,
    data_format,
    short_circuit,
):
    """
    return a comparison report

    If short_circuit is true, this is a call to see if a single system has
    drifted from a single baseline in order to trigger a Notification if
    necessary, so the only facts that will be compared will be those present
    on the baseline.  If the system has drifted from the baseline, the report
    will contain the key 'drift_event_notify' set to True, otherwise False.
    """
    if len(system_ids + baseline_ids + historical_sys_profile_ids) == 0:
        message = "must specify at least one of system, baseline, or HSP"
        current_app.logger.audit(str(HTTPStatus.BAD_REQUEST) + " " + message,
                                 request=request,
                                 success=False)
        raise HTTPError(
            HTTPStatus.BAD_REQUEST,
            message=message,
        )
    if len(system_ids) > len(set(system_ids)):
        message = "duplicate UUID specified in system_ids list"
        current_app.logger.audit(str(HTTPStatus.BAD_REQUEST) + " " + message,
                                 request=request,
                                 success=False)
        raise HTTPError(
            HTTPStatus.BAD_REQUEST,
            message=message,
        )

    if len(baseline_ids) > len(set(baseline_ids)):
        message = "duplicate UUID specified in baseline_ids list"
        current_app.logger.audit(str(HTTPStatus.BAD_REQUEST) + " " + message,
                                 request=request,
                                 success=False)
        raise HTTPError(
            HTTPStatus.BAD_REQUEST,
            message=message,
        )

    if system_ids:
        validate_uuids(system_ids)
    if baseline_ids:
        validate_uuids(baseline_ids)
    if historical_sys_profile_ids:
        validate_uuids(historical_sys_profile_ids)
    if reference_id:
        validate_uuids([reference_id])
        if reference_id not in (system_ids + baseline_ids +
                                historical_sys_profile_ids):
            message = "reference id %s does not match any ids from query" % reference_id
            current_app.logger.audit(
                str(HTTPStatus.BAD_REQUEST) + " " + message,
                request=request,
                success=False,
            )
            raise HTTPError(
                HTTPStatus.BAD_REQUEST,
                message=message,
            )

    try:
        systems_with_profiles = []
        baseline_results = []
        hsp_results = []

        with PT_CR_API_REQUESTS.time():
            try:
                if system_ids:
                    # can raise RBACDenied exception
                    message = "reading systems with profiles"
                    current_app.logger.audit(message, request=request)
                    systems_with_profiles = fetch_systems_with_profiles(
                        system_ids, auth_key, current_app.logger,
                        get_event_counters())

                if baseline_ids:
                    # can raise RBACDenied exception
                    message = "reading baselines"
                    current_app.logger.audit(message, request=request)
                    baseline_results = fetch_baselines(baseline_ids, auth_key,
                                                       current_app.logger)
                    ensure_correct_system_count(baseline_ids, baseline_results)

                if historical_sys_profile_ids:
                    # can raise RBACDenied exception
                    message = "reading historical system profiles"
                    current_app.logger.audit(message, request=request)
                    hsp_results = fetch_historical_sys_profiles(
                        historical_sys_profile_ids,
                        auth_key,
                        current_app.logger,
                        get_event_counters(),
                    )
            except RBACDenied as error:
                message = error.message
                current_app.logger.audit(str(HTTPStatus.FORBIDDEN) + " " +
                                         message,
                                         request=request)
                raise HTTPError(HTTPStatus.FORBIDDEN, message=message)

        with PT_CR_BUILD_COMPARISON.time():
            comparisons = info_parser.build_comparisons(
                systems_with_profiles,
                baseline_results,
                hsp_results,
                reference_id,
                short_circuit,
            )

        metrics.systems_compared.observe(len(system_ids))
        if data_format == "csv":
            output = make_response(_csvify(comparisons))
            output.headers[
                "Content-Disposition"] = "attachment; filename=export.csv"
            output.headers["Content-type"] = "text/csv"
            return output
        else:
            return jsonify(comparisons)

    except ItemNotReturned as error:
        message = error.message
        current_app.logger.audit(str(HTTPStatus.NOT_FOUND) + " " + message,
                                 request=request,
                                 success=False)
        raise HTTPError(HTTPStatus.NOT_FOUND, message=message)