def _get_cached_signatures(signature_cache, query_hash):
    try:
        s = signature_cache.get(query_hash)
        if s is None or s == b'':
            return s
        return make_file_response(s,
                                  f"al_signatures_{query_hash[:7]}.zip",
                                  len(s),
                                  content_type="application/zip")
    except Exception:  # pylint: disable=W0702
        LOGGER.exception('Failed to read cached signatures:')

    return None
def backup(**_):
    """
    Create a backup of the current system configuration

    Variables:
    None

    Arguments:
    None

    Data Block:
    None

    Result example:
    <SERVICE BACKUP>
    """
    services = {'type': 'backup', 'server': config.ui.fqdn, 'data': {}}

    for service in STORAGE.service_delta.stream_search("*:*",
                                                       fl="id",
                                                       as_obj=False):
        name = service['id']
        service_output = {
            'config': STORAGE.service_delta.get(name, as_obj=False),
            'versions': {}
        }
        for service_version in STORAGE.service.stream_search(f"name:{name}",
                                                             fl="id",
                                                             as_obj=False):
            version_id = service_version['id']
            service_output['versions'][version_id] = STORAGE.service.get(
                version_id, as_obj=False)

        services['data'][name] = service_output

    out = yaml.dump(services, indent=2)
    return make_file_response(out,
                              name=f"{config.ui.fqdn}_service_backup.yml",
                              size=len(out),
                              content_type="application/json")
def download_signatures(**kwargs):
    """
    Download signatures from the system.

    Variables:
    None

    Arguments:
    query       => Query used to filter the signatures
                   Default: All deployed signatures

    Data Block:
    None

    Result example:
    <A zip file containing all signatures files from the different sources>
    """
    user = kwargs['user']
    query = request.args.get('query', 'status:DEPLOYED')

    access = user['access_control']
    last_modified = STORAGE.get_signature_last_modified()

    query_hash = sha256(
        f'{query}.{access}.{last_modified}'.encode('utf-8')).hexdigest()

    with forge.get_cachestore('al_ui.signature') as signature_cache:
        response = _get_cached_signatures(signature_cache, query_hash)
        if response:
            return response

        with Lock(f"al_signatures_{query_hash[:7]}.zip", 30):
            response = _get_cached_signatures(signature_cache, query_hash)
            if response:
                return response

            output_files = {}

            keys = [
                k['id'] for k in STORAGE.signature.stream_search(
                    query, fl="id", access_control=access, as_obj=False)
            ]
            signature_list = sorted(STORAGE.signature.multiget(
                keys, as_dictionary=False, as_obj=False),
                                    key=lambda x: x['order'])

            for sig in signature_list:
                out_fname = f"{sig['type']}/{sig['source']}"
                output_files.setdefault(out_fname, [])
                output_files[out_fname].append(sig['data'])

            output_zip = InMemoryZip()
            for fname, data in output_files.items():
                output_zip.append(fname, "\n\n".join(data))

            rule_file_bin = output_zip.read()

            signature_cache.save(query_hash,
                                 rule_file_bin,
                                 ttl=DEFAULT_CACHE_TTL)

            return make_file_response(rule_file_bin,
                                      f"al_signatures_{query_hash[:7]}.zip",
                                      len(rule_file_bin),
                                      content_type="application/zip")
def get_ontology_for_alert(alert_id, **kwargs):
    """
    WARNING:
        This APIs output is considered stable but the ontology model itself is still in its
        alpha state. Do not use the results of this API in a production system just yet.

    Get all ontology files for a given alert

    Variables:
    alert_id         => Alert ID to get ontology files for

    Arguments:
    sha256      => Only get ontology files for this file, multiple values allowed (optional)
    service     => Only get ontology files for this service, multiple values allowed (optional)

    Data Block:
    None

    Result example:      (File where each line is a result ontology record)
    {"header":{"md5":"5fa76...submitter":"admin"}}
    {"header":{"md5":"6c3af...submitter":"admin"}}
    {"header":{"md5":"c8e69...submitter":"admin"}}
    """
    user = kwargs['user']
    sha256s = request.args.getlist('sha256', None)
    services = request.args.getlist('service', None)

    # Get alert from ID
    alert = STORAGE.alert.get(alert_id, as_obj=False)
    if not alert:
        return make_api_response(
            "", f"There are not alert with this ID: {alert_id}", 404)
    if not Classification.is_accessible(user['classification'],
                                        alert['classification']):
        return make_api_response(
            "",
            f"Your are not allowed get ontology files for this alert: {alert_id}",
            403)

    # Get related submission
    submission = STORAGE.submission.get(alert['sid'], as_obj=False)
    if not submission:
        return make_api_response(
            "", f"The submission related to the alert is missing: {alert_id}",
            404)
    if not Classification.is_accessible(user['classification'],
                                        submission['classification']):
        return make_api_response(
            "",
            f"Your are not allowed get ontology files for the submission related to this alert: {alert_id}",
            403)

    # Get all the results keys
    keys = [k for k in submission['results'] if not k.endswith(".e")]

    # Only use keys matching theses sha256s
    if sha256s:
        tmp_keys = []
        for sha256 in sha256s:
            tmp_keys.extend([k for k in keys if k.startswith(sha256)])
        keys = tmp_keys

    # Only use keys matching theses services
    if services:
        tmp_keys = []
        for service in services:
            tmp_keys.extend([k for k in keys if f".{service}." in k])
        keys = tmp_keys

    # Pull the results for the keys
    try:
        results = STORAGE.result.multiget(keys,
                                          as_dictionary=False,
                                          as_obj=False)
    except MultiKeyError as e:
        results = e.partial_output

    # Compile information to be added to the ontology
    updates = {
        'parent': alert['file']['sha256'],
        'metadata': alert.get('metadata', {}),
        'date': alert['ts'],
        'source_system': config.ui.fqdn,
        'sid': submission['sid'],
        'submitted_classification': submission['classification'],
        'submitter': submission['params']['submitter']
    }

    # Set the list of file names
    fnames = {x['sha256']: [x['name']] for x in submission['files']}

    # Generate ontology files based of the results
    sio = generate_ontology_file(results, user, updates=updates, fnames=fnames)
    data = sio.getvalue()
    return make_file_response(data, f"alert_{alert_id}.ontology", len(data))
def get_ontology_for_file(sha256, **kwargs):
    """
    WARNING:
        This APIs output is considered stable but the ontology model itself is still in its
        alpha state. Do not use the results of this API in a production system just yet.

    Get all ontology files for a given file

    Variables:
    sha256      => Hash of the files to fetch ontology files for

    Arguments:
    service     => Only get ontology files for this service, multiple values allowed (optional)
    all         => If there multiple ontology results for the same file get them all

    Data Block:
    None

    Result example:      (File where each line is a result ontology record)
    {"header":{"md5":"5fa76...submitter":"admin"}}
    {"header":{"md5":"5fa76...submitter":"admin"}}
    {"header":{"md5":"5fa76...submitter":"admin"}}
    """
    user = kwargs['user']
    services = request.args.getlist('service', None)
    all = request.args.get('all', 'false').lower() in ['true', '']

    # Get file data for hash
    file_data = STORAGE.file.get(sha256, as_obj=False)
    if not file_data:
        return make_api_response(
            "", f"There are not file with this hash: {sha256}", 404)
    if not Classification.is_accessible(user['classification'],
                                        file_data['classification']):
        return make_api_response(
            "",
            f"Your are not allowed get ontology files for this hash: {sha256}",
            403)

    # Generate the queries to get the results
    query = f"id:{sha256}* AND response.supplementary.name:*.ontology"
    filters = []
    if services:
        filters.append(" OR ".join(
            [f'response.service_name:{service}' for service in services]))

    # Get the result keys
    if all:
        keys = [
            x['id'] for x in STORAGE.result.stream_search(
                query,
                fl="id",
                filters=filters,
                access_control=user["access_control"],
                as_obj=False)
        ]
    else:
        service_resp = STORAGE.result.grouped_search(
            "response.service_name",
            query=query,
            fl='id',
            filters=filters,
            sort="created desc",
            access_control=user["access_control"],
            as_obj=False)

        keys = [
            k for service in service_resp['items']
            for k in service['items'][0].values()
        ]

    # Pull the results for the keys
    try:
        results = STORAGE.result.multiget(keys,
                                          as_dictionary=False,
                                          as_obj=False)
    except MultiKeyError as e:
        results = e.partial_output

    # Compile information to be added to the ontology
    updates = {
        'date': file_data['seen']['last'],
        'source_system': config.ui.fqdn,
        'submitted_classification': file_data['classification']
    }

    # Generate ontology files based of the results
    sio = generate_ontology_file(results, user, updates=updates)
    data = sio.getvalue()
    return make_file_response(data, f"file_{sha256}.ontology", len(data))