<commit_before>"""
Controller for all synchronous web operations. These are handled by the main web service endpoint.

Async operations are handled by teh async_operations controller.

"""

import json
import time

import connexion
from flask import abort, jsonify, Response
from werkzeug.exceptions import HTTPException

from anchore_engine.configuration import localconfig
from anchore_engine.services.policy_engine.api.models import Image as ImageMsg
from anchore_engine.services.policy_engine.api.models import ImageUpdateNotification, FeedUpdateNotification, \
    ImageVulnerabilityListing, \
    ImageIngressRequest, ImageIngressResponse, LegacyVulnerabilityReport
from anchore_engine.services.policy_engine.api.models import PolicyEvaluation, PolicyEvaluationProblem
from anchore_engine.db import Image, get_thread_scoped_session as get_session
from anchore_engine.services.policy_engine.engine.policy.bundles import get_bundle, build_bundle, \
    build_empty_error_execution
from anchore_engine.services.policy_engine.engine.policy.exceptions import InitializationError
from anchore_engine.services.policy_engine.engine.policy.gate import ExecutionContext
from anchore_engine.services.policy_engine.engine.tasks import FeedsUpdateTask
from anchore_engine.services.policy_engine.engine.tasks import ImageLoadTask
from anchore_engine.services.policy_engine.engine.vulnerabilities import have_vulnerabilities_for
from anchore_engine.services.policy_engine.engine.vulnerabilities import vulnerabilities_for_image
from anchore_engine.services.policy_engine.engine.feeds import get_selected_feeds_to_sync
from anchore_engine.db import DistroNamespace
from anchore_engine.subsys import logger as log
TABLE_STYLE_HEADER_LIST = ['CVE_ID', 'Severity', '*Total_Affected', 'Vulnerable_Package', 'Fix_Available', 'Fix_Images', 'Rebuild_Images', 'URL']


def get_status():
    """
    Generic status return common to all services
    :return:
    """
    httpcode = 500
    try:
        return_object = {
            'busy': False,
            'up': True,
            'message': 'all good'
        }
        httpcode = 200
    except Exception as err:
        return_object = str(err)

    return (return_object, httpcode)


def create_feed_update(notification):
    """
    Creates a feed data update notification.

    :param notification:
    :return:
    """
    if not connexion.request.is_json:
        abort(400)

    notification = FeedUpdateNotification.from_dict(notification)
    result = []
    try:
        feeds = get_selected_feeds_to_sync(localconfig.get_config())
        task = FeedsUpdateTask(feeds_to_sync=feeds)
        result = task.execute()
    except HTTPException:
        raise
    except Exception as e:
        log.exception('Error executing feed update task')
        abort(Response(status=500, response=json.dumps({'error': 'feed sync failure', 'details': 'Failure syncing feed: {}'.format(e.message)}), mimetype='application/json'))

    return jsonify(['{}/{}'.format(x[0], x[1]) for x in result]), 200


class ImageMessageMapper(object):
    """
    Map the msg to db and vice-versa
    """
    rfc2339_date_fmt = '%Y-%m-%dT%H:%M:%SZ'

    def db_to_msg(self, db_obj):
        msg = ImageMsg()
        msg.last_modified = db_obj.last_modified.strftime(self.rfc2339_date_fmt)
        msg.created_at = db_obj.created_at.strftime(self.rfc2339_date_fmt)
        msg.distro_namespace = db_obj.distro_namespace
        msg.user_id = db_obj.user_id
        msg.state = db_obj.state
        msg.id = db_obj.id
        msg.digest = db_obj.digest
        msg.tags = []
        return msg

    def msg_to_db(self, msg):
        db_obj = Image()
        db_obj.id = msg.id
        db_obj.digest = msg.digest
        db_obj.user_id = msg.user_id
        db_obj.created_at = msg.created_at
        db_obj.last_modified = msg.last_modified
        return db_obj

msg_mapper = ImageMessageMapper()


def list_image_users(page=None):
    """
    Returns the list of users the system knows about, based on images from users.
    Queries the set of users in the images list.

    :return: List of user_id strings
    """
    db = get_session()
    if not db:
        db = get_session()
    try:
        users = db.query(Image.user_id).group_by(Image.user_id).all()
        img_user_set = set([rec[0] for rec in users])
    finally:
        db.close()

    return list(img_user_set)


def list_user_images(user_id):
    """
    Given a user_id, returns a list of Image objects scoped to that user.

    :param user_id: str user identifier
    :return: List of Image (messsage) objects
    """
    db = get_session()
    try:
        imgs = [msg_mapper.db_to_msg(i).to_dict() for i in db.query(Image).filter(Image.user_id == user_id).all()]
    finally:
        db.close()

    return imgs


def delete_image(user_id, image_id):
    """
    DELETE the image and all resources for it. Returns 204 - No Content on success

    :param user_id:
    :param image_id:
    :return:
    """
    db = get_session()
    try:
        log.info('Deleting image {}/{} and all associated resources'.format(user_id, image_id))
        img = db.query(Image).get((image_id, user_id))
        if img:
            for pkg_vuln in img.vulnerabilities():
                db.delete(pkg_vuln)
            db.delete(img)
        else:
            abort(404)
        db.commit()
    except:
        db.rollback()
        abort(500)


def problem_from_exception(eval_exception, severity=None):
    """
    Constructs a messaging-layer PolicyEvaluationProblem from the given exception instance.

    :param eval_exception:
    :return: an initialized PolicyEvaluationProblem
    """
    if not eval_exception:
        return None

    prob = PolicyEvaluationProblem()
    prob.details = eval_exception.message
    prob.problem_type = eval_exception.__class__.__name__
    if hasattr(eval_exception, 'severity') and eval_exception.severity:
        prob.severity = eval_exception.severity
    elif severity:
        prob.severity = severity
    else:
        prob.severity = 'error'
    return prob


def check_user_image_inline(user_id, image_id, tag, bundle):
    """
    Execute a policy evaluation using the info in the request body including the bundle content

    :param user_id:<commit_msg>Fixes #16. Makes image deletion from the policy engine service idempotent<commit_after>"""
Controller for all synchronous web operations. These are handled by the main web service endpoint.

Async operations are handled by teh async_operations controller.

"""

import json
import time

import connexion
from flask import abort, jsonify, Response
from werkzeug.exceptions import HTTPException

from anchore_engine.configuration import localconfig
from anchore_engine.services.policy_engine.api.models import Image as ImageMsg
from anchore_engine.services.policy_engine.api.models import ImageUpdateNotification, FeedUpdateNotification, \
    ImageVulnerabilityListing, \
    ImageIngressRequest, ImageIngressResponse, LegacyVulnerabilityReport
from anchore_engine.services.policy_engine.api.models import PolicyEvaluation, PolicyEvaluationProblem
from anchore_engine.db import Image, get_thread_scoped_session as get_session
from anchore_engine.services.policy_engine.engine.policy.bundles import get_bundle, build_bundle, \
    build_empty_error_execution
from anchore_engine.services.policy_engine.engine.policy.exceptions import InitializationError
from anchore_engine.services.policy_engine.engine.policy.gate import ExecutionContext
from anchore_engine.services.policy_engine.engine.tasks import FeedsUpdateTask
from anchore_engine.services.policy_engine.engine.tasks import ImageLoadTask
from anchore_engine.services.policy_engine.engine.vulnerabilities import have_vulnerabilities_for
from anchore_engine.services.policy_engine.engine.vulnerabilities import vulnerabilities_for_image
from anchore_engine.services.policy_engine.engine.feeds import get_selected_feeds_to_sync
from anchore_engine.db import DistroNamespace
from anchore_engine.subsys import logger as log
TABLE_STYLE_HEADER_LIST = ['CVE_ID', 'Severity', '*Total_Affected', 'Vulnerable_Package', 'Fix_Available', 'Fix_Images', 'Rebuild_Images', 'URL']


def get_status():
    """
    Generic status return common to all services
    :return:
    """
    httpcode = 500
    try:
        return_object = {
            'busy': False,
            'up': True,
            'message': 'all good'
        }
        httpcode = 200
    except Exception as err:
        return_object = str(err)

    return (return_object, httpcode)


def create_feed_update(notification):
    """
    Creates a feed data update notification.

    :param notification:
    :return:
    """
    if not connexion.request.is_json:
        abort(400)

    notification = FeedUpdateNotification.from_dict(notification)
    result = []
    try:
        feeds = get_selected_feeds_to_sync(localconfig.get_config())
        task = FeedsUpdateTask(feeds_to_sync=feeds)
        result = task.execute()
    except HTTPException:
        raise
    except Exception as e:
        log.exception('Error executing feed update task')
        abort(Response(status=500, response=json.dumps({'error': 'feed sync failure', 'details': 'Failure syncing feed: {}'.format(e.message)}), mimetype='application/json'))

    return jsonify(['{}/{}'.format(x[0], x[1]) for x in result]), 200


class ImageMessageMapper(object):
    """
    Map the msg to db and vice-versa
    """
    rfc2339_date_fmt = '%Y-%m-%dT%H:%M:%SZ'

    def db_to_msg(self, db_obj):
        msg = ImageMsg()
        msg.last_modified = db_obj.last_modified.strftime(self.rfc2339_date_fmt)
        msg.created_at = db_obj.created_at.strftime(self.rfc2339_date_fmt)
        msg.distro_namespace = db_obj.distro_namespace
        msg.user_id = db_obj.user_id
        msg.state = db_obj.state
        msg.id = db_obj.id
        msg.digest = db_obj.digest
        msg.tags = []
        return msg

    def msg_to_db(self, msg):
        db_obj = Image()
        db_obj.id = msg.id
        db_obj.digest = msg.digest
        db_obj.user_id = msg.user_id
        db_obj.created_at = msg.created_at
        db_obj.last_modified = msg.last_modified
        return db_obj

msg_mapper = ImageMessageMapper()


def list_image_users(page=None):
    """
    Returns the list of users the system knows about, based on images from users.
    Queries the set of users in the images list.

    :return: List of user_id strings
    """
    db = get_session()
    if not db:
        db = get_session()
    try:
        users = db.query(Image.user_id).group_by(Image.user_id).all()
        img_user_set = set([rec[0] for rec in users])
    finally:
        db.close()

    return list(img_user_set)


def list_user_images(user_id):
    """
    Given a user_id, returns a list of Image objects scoped to that user.

    :param user_id: str user identifier
    :return: List of Image (messsage) objects
    """
    db = get_session()
    try:
        imgs = [msg_mapper.db_to_msg(i).to_dict() for i in db.query(Image).filter(Image.user_id == user_id).all()]
    finally:
        db.close()

    return imgs


def delete_image(user_id, image_id):
    """
    DELETE the image and all resources for it. Returns 204 - No Content on success

    :param user_id:
    :param image_id:
    :return:
    """
    db = get_session()
    try:
        log.info('Deleting image {}/{} and all associated resources'.format(user_id, image_id))
        img = db.query(Image).get((image_id, user_id))
        if img:
            for pkg_vuln in img.vulnerabilities():
                db.delete(pkg_vuln)
            db.delete(img)
            db.commit()
        else:
            db.rollback()

        # Idempotently return 204. This isn't properly RESTY, but idempotency on delete makes clients much cleaner.
        return (None, 204)
    except HTTPException:
        raise
    except Exception:
        log.exception('Error processing DELETE request for image {}/{}'.format(user_id, image_id))
        db.rollback()
        abort(500)


def problem_from_exception(eval_exception, severity=None):
    """
    Constructs a messaging-layer PolicyEvaluationProblem from the given exception instance.

    :param eval_exception:
    :return: an initialized PolicyEvaluationProblem
    """
    if not eval_exception:
        return None

    prob = PolicyEvaluationProblem()
    prob.details = eval_exception.message
    prob.problem_type = eval_exception.__class__.__name__
    if hasattr(eval_exception, 'severity') and eval_exception.severity:
        prob.severity = eval_exception.severity
    elif severity:
        prob.severity = severity
    else:
        prob.severity = 'error'
    return prob


def check_user_image_inline(user_id, image_id, tag, bundle):
    """
    Execute a policy evaluation using the info in the request body including the bundle content

    :param user_id:
{'fraction_non_alphanumeric': 0.08764323319913286, 'fraction_numerical': 0.006426138123257975, 'mean_word_length': 3.3056666666666668, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6892333', 'n_tokens_mistral': 4001, 'n_tokens_neox': 3821, 'n_words': 979}