def compare_head_base(self):
        """
        Compare the head to the base and return dict containing the following fields.
                {
                    "status": "diverged",
                    "ahead_by": 5,
                    "behind_by": 211,
                    "total_commits": 5
                }
        :returns dictionary containing status, ahead_by, behind_by and total_commits
        :raise Doesnt catch exceptions - they need to be caught by caller.
        """
        self._logger.info("compare_head_base")
        result = {}

        if self._loaded:
            compare_url = self._head_repo.compare_url
            compare_url = compare_url.replace("{base}", self._base_repo.label)
            compare_url = compare_url.replace(
                "{head}", urllib.quote(self._head_repo.label))
            api_query = GithubAPI(self._config)
            response = api_query.process_api_query(compare_url)
            if response:
                result["status"] = response.get("status")
                result["ahead_by"] = response.get("ahead_by")
                result["behind_by"] = response.get("behind_by")
                result["total_commits"] = response.get("total_commits")

        return result
    def comment_on_pull_request(self, pull_request_comment=""):
        """
        Add a comment to the pull request.
        :param pull_request_comment: Comment to add to the pull request.
        :raise Doesnt catch exceptions - they need to be caught by caller.
        """
        self._logger.info("Commenting on pull request")

        if self._loaded:
            api_query = GithubAPI(self._config)
            api_query.add_pull_request_comment(self._comments_url,
                                               pull_request_comment)
Exemple #3
0
 def __init__(self, framework, pull_request, logger, processor):
     self.framework = framework
     self.pull_request = pull_request
     self._logger = logger
     self.which_processor = processor
     self.ScorebotConfig = ScorebotConfig
     self.github_config = GithubConfig()
     self.github_api = GithubAPI(self.github_config)
 def get_exemption(self, framework, pull_request_url, commit_id):
     github_api = GithubAPI(self.github_config)
     # Update metrics
     repo = re.findall(
         r"(?<=https://github.{0}.com/)(.*?)(?=/pull)".format(
             constants.GITHUB_DOMAIN), pull_request_url)[0].split("/")[1]
     EnforcementMetrics.objects.filter(repo=repo, commit_id=commit_id, framework=framework, status="failure").\
         update(status="exemption")
     api_url = github_api.generate_api_url_from_pull_request_url(
         pull_request_url)
     api_url = api_url[:api_url.find("/pulls/")]
     status_url = api_url + "/statuses/" + commit_id
     data = {
         "state": "success",
         "context": constants.STATUS_CONTEXT,
         "description": "SCORE Bot exemption"
     }
     github_api.post_status_check(status_url, data)
    def close_pull_request(self, pull_request_comment=""):
        """
        Close the pull request adding a commit comment and a pull request comment. Return the response.

        :param pull_request_comment: Comment to add to the pull request.
        :return: Pull request execution response data
        :raise Doesnt catch exceptions - they need to be caught by caller.
        """
        self._logger.info("Closing pull request")

        result = None
        if self._loaded:
            # Close the pull request
            api_query = GithubAPI(self._config)
            result = api_query.close_pull_request(self._api_url)

            # Add comment to pull request now that it has been executed
            if pull_request_comment:
                self.comment_on_pull_request(pull_request_comment)

        return result
Exemple #6
0
    def __init__(self, framework, processor="CI"):
        super(ScorebotProcessor, self).__init__()
        self.framework = framework
        self.processor = processor

        if self.framework == "CPP":
            self.ScorebotControl = ScorebotControlCpp
        elif self.framework == "Java":
            self.ScorebotControl = ScorebotControlJava
        elif self.framework == "Kraken":
            self.ScorebotControl = ScorebotControlKraken
        self.file_extensions_to_include = [
            x.strip(" ") for x in ScorebotConfig.objects.filter(
                config="file_extensions_" +
                str(self.framework)).values("value")[0]["value"].split(",")
        ]
        self.pull_request_job = ""
        self.diff_patch = {}
        self.notify_pp = {}
        self.notify = {}
        self.p_levels = {}
        self.excluded_paths = {}

        self.github_config = GithubConfig()
        self.github_api = GithubAPI(self.github_config)
        self.pull_request = GithubPullRequest()

        self._processor_name = "Scorebot Processor " + str(self.framework)
        self.pull_request_url = self.pull_request.url

        self._logger = logging.getLogger(__name__)

        self.ScorebotUsecases = ScorebotUsecases(self.framework,
                                                 self.pull_request,
                                                 self._logger, processor)
        self.EnforcementFunctions = EnforcementFunctions(
            self.pull_request, self.file_extensions_to_include, self._logger)
        self.EnforcementFunctions.p_levels = self.p_levels
    def check_write_permission(self, api_url, framework):
        github_api = GithubAPI(self.github_config)
        enforce_bool = True

        collab_url = api_url + "/collaborators/"
        user = "******"

        try:
            github_api.check_user_permission(collab_url, user)["permission"]
        except Exception as err:
            BlacklistedPrsLog.objects.create(
                pull_request_url=self.pull_request.url,
                user=self.pull_request.user.username.decode("utf-8"),
                repo=self.pull_request.base_repo.name,
                framework=framework)
            self._logger.info(
                "{0}\n{1}\n".format(type(err), traceback.format_exc()) +
                self.pull_request.url)
            self._logger.info("No permission to update commit status on: " +
                              self.pull_request.url)
            enforce_bool = False

        return enforce_bool
    def load(self, github_config, pull_request_url, load_patches=False):
        """
        Calls GitHub APIs and uses the API responses to fill in the PullRequest.
        :param github_config: GithubConfig
        :param pull_request_url: GitHub pull request URL
        :param load_patches: Boolean: If True then load file patches else do not load the patches.
        :return: Boolean: True if loaded else False
        :raises GithubAPIError, GithubError, Exception
        """
        self._logger.info("Loading pull request: {0}".format(pull_request_url))
        self._reset()
        self._load_patches = load_patches
        self._config = github_config

        try:
            github_api = GithubAPI(self._config)
            request_url = github_api.generate_api_url_from_pull_request_url(
                pull_request_url)

        except exceptions.GithubError as err:
            self._logger.error("\n{0}\n{1}\n".format(type(err),
                                                     traceback.format_exc()))
            raise

        except Exception as err:
            self._logger.error("\n{0}\n{1}\n".format(type(err),
                                                     traceback.format_exc()))
            raise

        # We need to test the mergeablity at this point due to an issue with GitHub.
        # We are going to load the pull request no matter what but we are going to loop on it
        # for a period of time or (until mergeable is not None or merged is true or the state is not open).
        total_time_in_seconds = 90  # total time we will loop
        sleep_time_in_seconds = 5  # loop increments
        sleep_counter = 0
        try:
            while sleep_counter < total_time_in_seconds:
                self._logger.info(
                    "Loading pull request from API URL: {0}".format(
                        request_url))
                self._load_pull_request(
                    github_api.process_api_query(request_url))
                # Break if mergeable is set or merged is true or the state is not open
                if self._mergeable is not None or self._merged or self._state.lower(
                ) != "open":
                    break
                self._logger.info("Waiting on mergeable field to be set.")
                time.sleep(sleep_time_in_seconds)
                sleep_counter += sleep_time_in_seconds

            self._logger.info("Mergeable: {0}".format(self._mergeable))

            self._logger.info("Loading issues from URL {0}".format(
                self._issues_url))
            self._load_closed_by(github_api.process_api_query(
                self._issues_url))

            self._logger.info("Loading user from URL {0}".format(
                self._user_url))
            self._load_user(github_api.process_api_query(self._user_url))

            self._logger.info("Loading files from URL {0}".format(
                self._files_url))
            file_query_response, header = github_api.process_api_query_with_header(
                self._files_url)

            while file_query_response:
                self._load_files(file_query_response)
                if not header.links or "next" not in header.links:
                    # No more links for files
                    break

                else:
                    file_query_response, header = github_api.process_api_query_with_header(
                        header.links["next"]["url"])

            self._logger.info("Loading commits from URL {0}".format(
                self._commits_url))
            file_query_response, header = github_api.process_api_query_with_header(
                self._commits_url)

            while file_query_response:
                self._load_commits(file_query_response)
                if not header.links or "next" not in header.links:
                    # No more links for files
                    break

                else:
                    file_query_response, header = github_api.process_api_query_with_header(
                        header.links["next"]["url"])

        except exceptions.GithubAPIError as err:
            self._logger.error("\n{0}\n{1}\n".format(type(err),
                                                     traceback.format_exc()))
            raise

        except Exception as err:
            self._logger.error("\n{0}\n{1}\n".format(type(err),
                                                     traceback.format_exc()))
            raise

        self._logger.info("GitHub pull request loaded")
        self._loaded = True
        return self._loaded
 def _load_comments(self):
     """
     Load the comments using the comments_url.
     """
     github_api = GithubAPI(self._config)
     self._comments = github_api.process_api_query(self._comments_url)
Exemple #10
0
class ScorebotProcessor(object):
    def __init__(self, framework, processor="CI"):
        super(ScorebotProcessor, self).__init__()
        self.framework = framework
        self.processor = processor

        if self.framework == "CPP":
            self.ScorebotControl = ScorebotControlCpp
        elif self.framework == "Java":
            self.ScorebotControl = ScorebotControlJava
        elif self.framework == "Kraken":
            self.ScorebotControl = ScorebotControlKraken
        self.file_extensions_to_include = [
            x.strip(" ") for x in ScorebotConfig.objects.filter(
                config="file_extensions_" +
                str(self.framework)).values("value")[0]["value"].split(",")
        ]
        self.pull_request_job = ""
        self.diff_patch = {}
        self.notify_pp = {}
        self.notify = {}
        self.p_levels = {}
        self.excluded_paths = {}

        self.github_config = GithubConfig()
        self.github_api = GithubAPI(self.github_config)
        self.pull_request = GithubPullRequest()

        self._processor_name = "Scorebot Processor " + str(self.framework)
        self.pull_request_url = self.pull_request.url

        self._logger = logging.getLogger(__name__)

        self.ScorebotUsecases = ScorebotUsecases(self.framework,
                                                 self.pull_request,
                                                 self._logger, processor)
        self.EnforcementFunctions = EnforcementFunctions(
            self.pull_request, self.file_extensions_to_include, self._logger)
        self.EnforcementFunctions.p_levels = self.p_levels

    def _init_job_log(self,
                      pull_request_url=None,
                      repo_url=None,
                      custom_field=None):
        """
        Set up the log for the job being processed.
        :param pull_request_url: pull request url for the active job
        :param repo_url: repo_url
        :param custom_field: field to use instead of repo name. e.g. push
        """
        self._logger.debug("_init_job_log")

        job_log_name = logging_util.get_job_log_name(
            pull_request_url=pull_request_url,
            repo_url=repo_url,
            custom_field=custom_field)
        logging.config.dictConfig(
            logging_util.get_logging_conf(self._processor_name, job_log_name))

    @staticmethod
    def notify_pp_helper():
        """
        If the mode is notify_pp, then we are sending a/b email
        :return mode and pp_var
        """

        mode = "notify"
        a_b = random.randint(1, 2)
        if a_b == 1:
            pp_var = "a"
        else:
            pp_var = "b"

        return mode, pp_var

    def finalize_web_hook_job(self, web_hook_job):
        """
        Finalize the Github web-hook job.
        :param web_hook_job: WebhookPR or WebhookPush object that has been processed.
        """
        self._logger.debug("_finalize_web_hook_job")

        web_hook_job.processed = True
        web_hook_job.completed_time = timezone.now()
        web_hook_job.save(update_fields=["processed", "completed_time"])

    def get_file_diff(self):
        """
        Get patch diff for the PR and parse file to create a dictionary,
        where key is the filename, and value is the diff patch
        """
        self._logger.debug("get_diff_patch")

        patch_content = self.github_api.get_diff_patch(
            self.pull_request.api_url)

        content_split = patch_content.decode("utf-8").split("diff --git a/")

        for diff in content_split:
            filename = diff[:diff.find(" b/")].strip()
            content = diff[diff.find("@@"):]
            self.diff_patch[filename] = content

    def clean_patch(self, patch, file_name):
        """
        Remove comments and removed lines from the changed code from the commit.
        :param patch: Changed code as part of the commit
        :param file_name: Changed file name as part of the commit
        :return: patch free of comments and removed lines
        """
        self._logger.debug("_clean_patch")
        pull_request_url = self.pull_request_url
        stripped_patch = []
        if patch is None:
            if not self.diff_patch:
                self.get_file_diff()
            if file_name in self.diff_patch:
                patch = self.diff_patch[file_name].decode("utf-8", "ignore")

        try:
            if not patch:
                return stripped_patch
            elif patch:
                # Remove removed lines from the patch
                patch1 = re.sub(re.compile("(?m)^-.*?$"), "", patch)
                # Remove comments of style /* comments */ from the patch
                patch2 = re.sub(re.compile("/\*.*?\*/", re.DOTALL), "", patch1)
                # Remove comments of style // comments from the patch
                stripped_patch = re.sub(re.compile("//.*?\n"), "", patch2)

        except Exception as err:
            self._logger.critical("Exception during clean patch: {0}\n{1}\n".
                                  format(type(err), traceback.format_exc()) +
                                  pull_request_url)
            self.finalize_web_hook_job(pull_request_url)

        return stripped_patch

    def create_message(self, template, file_list, enforce_bool, generic_bool,
                       security_category):
        curr_message = ""
        a_b_message = "\nThe appropriate actions must be taken before merging.\n" \
                      "Not fixing this might block the release of the code to production."

        header, intro, security_issue, how_to_fix, references, reference_name = template
        curr_message += "<b>" + header + "</b>\n\n" + intro + " #<a href=" + self.pull_request.url + ">" + \
                        str(self.pull_request.number) + "</a>"

        if self.notify_pp[security_category] == "b":
            curr_message += a_b_message

        # For enforce bool - file_list = set(filename, sha)
        # For non enforce - file_list = set(file names)

        if enforce_bool:
            exemption_url = ScorebotConfig.objects.filter(
                config="exception_url").values()[0]["value"]
            if generic_bool:
                files = "<table style='border: 1px solid black'><tr>" \
                        "<th style='border: 1px solid black'>Commit</th>" \
                        "<th style='border: 1px solid black'>Variable</th>" \
                        "<th style='border: 1px solid black'>Files(s)</th>" \
                        "<th style='border: 1px solid black'>Exemption Form</th></tr>"
                temp_d = {}
                for (variable, commit_list) in file_list:
                    for (filename, sha) in commit_list:
                        if sha not in temp_d:
                            temp_d[sha] = {variable: {filename}}
                        else:
                            if variable not in temp_d[sha]:
                                temp_d[sha].update({variable: {filename}})
                            else:
                                temp_d[sha][variable].add(filename)
                for sha in temp_d:
                    for (variable, file_list) in temp_d[sha].items():
                        exemption_url += "?" + urlencode(
                            {
                                "pr": self.pull_request.url,
                                "framework": self.framework,
                                "commit_id": sha
                            })
                        exemption_link = "<a href=" + exemption_url + ">Create exemption request</a>\n"
                        files += "<tr><td style='border: 1px solid black'>" + sha + \
                                 "</td><td style='border: 1px solid black'>" + variable + \
                                 "</td><td style='border: 1px solid black'>" + ",\n".join(list(file_list)) + \
                                 "</td><td style='border: 1px solid black'>" + exemption_link + \
                                 "</td></tr>"
                files += "</table>"
            else:
                files = "<table style='border: 1px solid black'><tr>" \
                        "<th style='border: 1px solid black'>Commit</th>" \
                        "<th style='border: 1px solid black'>File(s)</th>" \
                        "<th style='border: 1px solid black'>Exemption Form</th></tr>"
                temp_d = {}
                for (filename, sha) in file_list:
                    if sha not in temp_d:
                        temp_d[sha] = set([])
                    temp_d[sha].add(filename)
                for sha in temp_d:
                    exemption_url += "?" + urlencode({
                        "pr": self.pull_request.url,
                        "framework": self.framework,
                        "commit_id": sha
                    })
                    exemption_link = "<a href=" + exemption_url + ">Create exemption request</a>\n"
                    files += "<tr><td style='border: 1px solid black'>" + sha + \
                             "</td><td style='border: 1px solid black'>" + ",\n".join(list(temp_d[sha])) + \
                             "</td><td style='border: 1px solid black'>" + exemption_link + \
                             "</td></tr>"
                files += "</table>"

        else:
            if generic_bool:
                files = "<table style='border: 1px solid black'><tr>" \
                        "<th style='border: 1px solid black'>Variable" \
                        "</th><th style='border: 1px solid black'>File(s)</th></tr>"
                for (variable, file_name) in file_list:
                    files += "<tr><td style='border: 1px solid black'>" + variable + \
                             "</td><td style='border: 1px solid black'>" + ",\n".join(list(file_name)) + \
                             "</td></tr>"
                files += "</table>"
            else:
                files = "<table style='border: 1px solid black'><tr>" \
                        "<th style='border: 1px solid black'>File(s)</th></tr>"
                for filename in list(file_list):
                    files += "<tr><td style='border: 1px solid black'>" + filename + "</td></tr>"
                files += "</table>"
        curr_message += files + "\n\n"

        if security_issue:
            curr_message += "\n\n<b>What is the security issue?</b>\n\n" + security_issue
        if how_to_fix:
            curr_message += "\n\n<b>How to fix it?</b>\n\n" + how_to_fix

        if references:
            curr_message += "\n\n<b>References:</b>\n"
            references = references.split("\r\n")
            reference_name = reference_name.split("\r\n")
            for ref in range(len(references)):
                curr_message += "<a href=" + references[
                    ref] + ">" + reference_name[ref] + "</a>\n"
        curr_message += "\n\n\n\n"

        return curr_message

    def create_message_list(self, vulnerability_d, enforce_bool):
        """
        Create dictionary of messages to email, where key is security category
         and value is list of messages for the security category
        :param vulnerability_d: dictionary where key is security category and
        if enforced: value is dictionary of dictionaries, ordered by sha id,
                     then filename, and then set of patterns found
        if not-enforced: value is list of tuples with (variable, filename)
        :param enforce_bool: bool to determine whether to add commit info
        :return: dictionary with the messages
        """
        message_list = {}

        for security_category in vulnerability_d:
            message_list[security_category] = []
            messages = []
            # For enforce bool - d[pattern] = set(filename, sha)
            # For non enforce - d[pattern] = set(file names)
            variable_file_list = vulnerability_d[security_category]

            if self.framework == "CPP":
                messages = MessageList.objects.filter(
                    security_category=security_category,
                    cpp=True).values_list("function_name", "header", "intro",
                                          "security_issue", "how_to_fix",
                                          "references", "reference_name")
            elif self.framework == "Java":
                messages = MessageList.objects.filter(
                    security_category=security_category,
                    java=True).values_list("function_name", "header", "intro",
                                           "security_issue", "how_to_fix",
                                           "references", "reference_name")
            elif self.framework == "Kraken":
                messages = MessageList.objects.filter(
                    security_category=security_category,
                    kraken=True).values_list("function_name", "header",
                                             "intro", "security_issue",
                                             "how_to_fix", "references",
                                             "reference_name")

            temp_msg_d = {}
            generic_list = []
            for template in messages:
                # function name, header, intro, security issue, how to fix, references, reference name
                temp_msg_d[template[0]] = [
                    (template[1], template[2], template[3], template[4],
                     template[5], template[6])
                ]

            for (variable, file_list) in variable_file_list.items():
                # File list for enforce bool - d[sha] = set(file names)
                # File list for non enforce bool - set(file names)
                if variable in temp_msg_d:
                    # Specific message for variable
                    generic_bool = False
                    template = temp_msg_d[variable]
                    curr_message = self.create_message(template[0], file_list,
                                                       enforce_bool,
                                                       generic_bool,
                                                       security_category)
                    message_list[security_category].append(curr_message)
                else:
                    # Generic message for category
                    generic_list.append((variable, file_list))
            if generic_list:
                generic_bool = True
                template = temp_msg_d[security_category]
                curr_message = self.create_message(template[0], generic_list,
                                                   enforce_bool, generic_bool,
                                                   security_category)
                message_list[security_category].append(curr_message)

        return message_list

    def _add_comment_to_pull_request(self, pull_request, pr_comment):
        """
        Add the message to the pull request.
        :param pull_request: loaded pull request object
        :param pr_comment: List - List with banned function message and solution.
        :return:
        """
        self._logger.debug("_add_comment_to_pull_request")

        comment = "<pre>{0}</pre>".format("<br>".join(pr_comment))
        pull_request.comment_on_pull_request(comment)

    def _send_email_to_stakeholders(self, pull_request_url, owner,
                                    message_list):
        """
        Send email to the domain lock owners and the change owner.
        :param pull_request_url: URL of the pull request
        :param owner: username of the person who made the changes
        :param message_list: List of the vulnerabilities found and how to fix
        """
        self._logger.debug("_send_email_to_stakeholders")

        template_prefix = "score_bot_email"
        subject = "SCORE Bot: Detected possible insecure code in files changed by {0}"
        email_info = {"pull_request": pull_request_url, "owner": owner}
        owner_email = "{0}@{1}.com".format(owner, constants.DOMAIN)

        score_bot_email = Email()
        email_info["message"] = message_list
        recipients = "{0}".format(
            owner_email) + ", " + constants.SCOREBOT_DL_EMAIL_ADDRESS
        email_subject = subject.format(owner)
        score_bot_email.send_email(template_prefix, email_subject, recipients,
                                   email_info)

    def scan_files(self, category_list, files_to_scan, enforce_bool, status,
                   sha, exempt):
        """
        Scan for all categories for each file
        :param category_list: list of tuples with (use cases, mode) to be scanned for
        :param files_to_scan: list of objects with metadata like filename, status, etc
        :param enforce_bool: bool for whether enforced or not
        :param status: status of the commit for enforcement
        :param sha: commit id to record in metrics
        :param exempt: exemption status
        :return: dictionary of all found vulnerabilities
        """
        excluded_folder_found = []
        excluded_path_found = []
        exclude_folders = []
        search_patch_categories = []
        vulnerability_d = {}

        self.file_extensions_to_include = [
            x.strip(" ") for x in ScorebotConfig.objects.filter(
                config="file_extensions_" +
                str(self.framework)).values("value")[0]["value"].split(",")
        ]

        if self.framework == "CPP":
            search_patch_categories = list(
                set(
                    PatternList.objects.filter(cpp=True).values_list(
                        "security_category", flat=True)))
            exclude_folders = ScorebotConfig.objects.filter(
                config="excluded_folders", cpp=True).values("value")

        elif self.framework == "Java":
            search_patch_categories = list(
                set(
                    PatternList.objects.filter(java=True).values_list(
                        "security_category", flat=True)))
            exclude_folders = ScorebotConfig.objects.filter(
                config="excluded_folders", java=True).values("value")

        elif self.framework == "Kraken":
            search_patch_categories = list(
                set(
                    PatternList.objects.filter(kraken=True).values_list(
                        "security_category", flat=True)))
            exclude_folders = ScorebotConfig.objects.filter(
                config="excluded_folders", kraken=True).values("value")

        if exclude_folders:
            exclude_folders = [
                x.strip(" ") for x in exclude_folders[0]["value"].split(",")
            ]

        for item in files_to_scan:
            filename = item["filename"]
            if exclude_folders:
                excluded_folder_found = [
                    folder_name for folder_name in exclude_folders
                    if re.findall(
                        "(^" + folder_name + "/)|(/" + folder_name +
                        "/)", filename.lower())
                ]
            if excluded_folder_found:
                continue
            if item["status"] == "removed":
                continue

            # Go through each security category and determine if file scans and patch scans
            for (security_category, mode) in category_list:
                excluded_paths = self.excluded_paths[security_category].split(
                    ",")
                if excluded_paths:
                    excluded_path_found = [
                        folder_name for folder_name in excluded_paths
                        if re.findall(
                            "(^" + folder_name.strip() + "/)|"
                            "(/" + folder_name.strip() +
                            "/)", filename.lower())
                    ]
                if excluded_path_found:
                    continue
                vulnerability_found = False
                patterns_found = []
                file_list_d = {}
                ext = os.path.splitext(filename)[1]
                if ext.lower() in self.file_extensions_to_include:
                    if "patch" in item:
                        # Scan patch
                        stripped_patch = self.clean_patch(
                            item["patch"], filename)
                        if security_category in search_patch_categories:
                            patterns_found, vulnerability_found = self.ScorebotUsecases.\
                                scan_patch_pattern(stripped_patch, security_category)

                if vulnerability_found:
                    vulnerability_d, file_list_d, status = self.record_metrics(
                        patterns_found, enforce_bool, filename, sha,
                        security_category, exempt, mode, file_list_d,
                        vulnerability_d, status)

        return vulnerability_d, status

    def record_metrics(self, patterns_found, enforce_bool, filename, sha,
                       security_category, exempt, mode, file_list_d,
                       vulnerability_d, status):
        for pattern in patterns_found:
            if enforce_bool:
                # Record in enforce metrics
                if exempt:
                    metrics_status = "exemption"
                else:
                    metrics_status = "failure"
                    status = "failure"

                EnforcementMetrics.objects.create(
                    function_name=pattern,
                    priority_level=self.p_levels[security_category],
                    security_category=security_category,
                    framework=self.framework,
                    file_name=filename,
                    pull_request_url=self.pull_request.url,
                    repo=self.pull_request.base_repo.name,
                    branch=self.pull_request.base_repo.ref,
                    commit_id=sha,
                    user=self.pull_request.user.username.decode("utf-8"),
                    notification_pp=self.notify_pp[security_category],
                    status=metrics_status)

                if mode is not "silent":
                    if sha not in file_list_d:
                        file_list_d[pattern] = set([])
                    file_list_d[pattern].add((filename, sha))
                else:
                    status = "silent"

            else:
                recorded_metrics = ScorebotMetrics.objects.filter(function_name=pattern,
                                                                  file_name=filename,
                                                                  pull_request_url=self.pull_request.url). \
                    values_list("function_name")
                if not recorded_metrics:
                    ScorebotMetrics.objects.create(
                        function_name=pattern,
                        priority_level=self.p_levels[security_category],
                        security_category=security_category,
                        framework=self.framework,
                        file_name=filename,
                        user=self.pull_request.user.username.decode("utf-8"),
                        repo=self.pull_request.base_repo.name,
                        branch=self.pull_request.base_repo.ref,
                        pull_request_url=self.pull_request.url,
                        scorebot_mode=mode,
                        notification_pp=self.notify_pp[security_category],
                        post_process=False)

                    if mode is not "silent":
                        if pattern not in file_list_d:
                            file_list_d[pattern] = set([])
                        file_list_d[pattern].add(filename)

        if file_list_d:
            if security_category not in vulnerability_d:
                vulnerability_d[security_category] = file_list_d
            else:
                for p in file_list_d:
                    if p not in vulnerability_d[security_category]:
                        vulnerability_d[security_category][p] = file_list_d[p]
                    else:
                        vulnerability_d[security_category][p].update(
                            file_list_d[p])
        return vulnerability_d, file_list_d, status

    def _scan_pull_request(self, category_list):
        """
        Scans each pull request file with all the applicable security categories
        :param category_list: List of tuples with (SCORE Bot categories, mode) to process
        return: two dictionaries, with enforced and non-enforced use cases
        """

        enforce_use_cases = []
        enforce_bool = False
        exempt = False
        excluded_commit = []
        enforce_d = {}
        sha = ""
        status = ""

        whitelist_apps = [
            x.strip(" ") for x in ScorebotConfig.objects.filter(
                config="enforcement_whitelist").values("value")[0]
            ["value"].split(",")
        ]
        enforced_branch = [
            x.strip(" ") for x in ScorebotConfig.objects.filter(
                config="enforcement_branches").values("value")[0]
            ["value"].split(",")
        ]
        blacklist_apps = [
            x.strip(" ") for x in ScorebotConfig.objects.filter(
                config="enforcement_blacklist").values("value")[0]
            ["value"].split(",")
        ]
        exclude_commits = [
            x.strip(" ")
            for x in ScorebotConfig.objects.filter(config="excluded_commits").
            values("value")[0]["value"].split(",")
        ]

        if self.pull_request.base_repo.name.lower() not in blacklist_apps:
            if str(whitelist_apps[0]).strip(" ") == "none" or \
                            self.pull_request.base_repo.name.lower() in [i.strip(" ") for i in whitelist_apps]:
                if enforced_branch and \
                   self.pull_request.base_repo.ref.lower() in [i.strip(" ") for i in enforced_branch]:
                    enforce_use_cases = self.ScorebotControl.objects.filter(enforce=True).\
                        values_list("security_category", flat=True)

        if enforce_use_cases:
            all_enforced_category = \
                [(sec_cat, mode) for (sec_cat, mode) in category_list if sec_cat in enforce_use_cases]
            api_url = self.pull_request.base_repo.api_url

            # If cannot enforce, scan normally
            enforce_bool = self.EnforcementFunctions.check_write_permission(
                api_url, self.framework)
            if enforce_bool:
                enforced_commits = list(
                    set(
                        EnforcementMetrics.objects.filter(
                            repo=self.pull_request.base_repo.name,
                            framework=self.framework).values_list(
                                "commit_id", "status", "pull_request_url",
                                "function_name", "security_category",
                                "framework", "notification_pp")))
                enforced_commit_id = [
                    commit_id for (commit_id, status, pr, func, sec_cat,
                                   framework, pp) in enforced_commits
                ]

                for commit in self.pull_request.commits:
                    category_enforce_list = all_enforced_category
                    silent_modes = [
                        mode for (category, mode) in category_enforce_list
                        if mode == "silent"
                    ]
                    status = "silent" if len(silent_modes) == len(
                        category_enforce_list) else "success"
                    sha = commit.sha
                    commit_url = api_url + "/commits/" + sha
                    status_url = api_url + "/statuses/" + sha
                    override_status = ""

                    if exclude_commits:
                        excluded_commit = [
                            desc for desc in exclude_commits
                            if re.findall(desc.strip(), commit.subject)
                        ]

                    if excluded_commit:
                        if status != "silent":
                            data = {
                                "state": status,
                                "context": constants.STATUS_CONTEXT,
                                "description": "SCORE Bot"
                            }
                            self.github_api.post_status_check(status_url, data)
                        continue

                    # If commit id enforced previously
                    if sha in enforced_commit_id:
                        override_status, category_enforce_list, exempt, enforced_failures_d = \
                            self.EnforcementFunctions.get_enforced_commits(category_enforce_list, enforced_commits, sha)

                        for category in enforced_failures_d:
                            if category not in enforce_d:
                                enforce_d[category] = enforced_failures_d[
                                    category]

                            else:
                                for pattern in enforced_failures_d[category]:
                                    if pattern not in enforce_d[category]:
                                        enforce_d[category][
                                            pattern] = enforced_failures_d[
                                                category][pattern]

                                    else:
                                        enforce_d[category][pattern].update(
                                            enforced_failures_d[category]
                                            [pattern])

                    # Get all the files to be scanned
                    commit_files = self.github_api.get_commit_patch(
                        commit_url.strip())["files"]
                    enforce_d_temp, status = self.scan_files(
                        category_enforce_list, commit_files, enforce_bool,
                        status, sha, exempt)

                    if override_status:
                        status = override_status

                    for category in enforce_d_temp:
                        if category not in enforce_d:
                            enforce_d[category] = enforce_d_temp[category]
                        else:
                            for pattern in enforce_d_temp[category]:
                                if pattern not in enforce_d[category]:
                                    enforce_d[category][
                                        pattern] = enforce_d_temp[category][
                                            pattern]
                                else:
                                    enforce_d[category][pattern].update(
                                        enforce_d_temp[category][pattern])

                    if status is not "silent":
                        data = {
                            "state": status,
                            "context": constants.STATUS_CONTEXT,
                            "description": "SCORE Bot"
                        }
                        self.github_api.post_status_check(status_url, data)

                # Prevent duplicate scanning for enforce/non-enforce
                category_list = list(
                    set(category_list) - set(all_enforced_category))
                enforce_bool = False
                exempt = False

        # Non-enforce use cases
        pr_files, header = self.github_api.process_api_query_with_header(
            self.pull_request.files_url)
        non_enforce_d, status = self.scan_files(category_list, pr_files,
                                                enforce_bool, status, sha,
                                                exempt)

        return enforce_d, non_enforce_d

    def process_pull_request(self, pull_request_job):
        """
        Process the Github web-hook branch pull request data that is queued up:
            - Check if the pull request has any security vulnerabilities.
              If yes add comment to the pull request and send email (if notify)
        :param pull_request_job: WebhookPR object that has not been processed.
        """
        self.pull_request_job = pull_request_job
        pull_request_url = pull_request_job.pull_request_url
        self._init_job_log(pull_request_url=pull_request_url)
        self._logger.info("process_pull_request: {}".format(pull_request_url))

        try:
            # Load the pull request
            try:
                self.pull_request.load(self.github_config, pull_request_url,
                                       True)
            except Exception as err:
                self.finalize_web_hook_job(pull_request_job)
                self._logger.critical(
                    "{0}\n{1}\n".format(type(err), traceback.format_exc()) +
                    self.pull_request.url)

            # Load p_levels for each use case
            use_case_config_list = self.ScorebotControl.objects.values_list(
                "security_category", "priority_level", "excluded_paths")
            for (sec_cat, p_level, exclude_path) in use_case_config_list:
                self.p_levels[sec_cat] = p_level
                self.excluded_paths[sec_cat] = exclude_path

            if self.pull_request.loaded and self.pull_request.files:
                category_list = []
                # Check notify_pp mode (a/b email)
                notify_pp_list = self.ScorebotControl.objects.filter(silent=False, notify=True, notify_pp=True).\
                    values_list("security_category", flat=True)
                self._logger.info(
                    "Categories with notify_pp mode on: {}".format(
                        notify_pp_list))

                # Determine a/b message
                for category in notify_pp_list:
                    mode, ab_var = self.notify_pp_helper()
                    category_list.append((category, mode))
                    self.notify_pp[category] = ab_var

                # Check notify mode
                notify_list = self.ScorebotControl.objects.filter(silent=False, notify=True, notify_pp=False).\
                    values_list("security_category", flat=True)
                self._logger.info(
                    "Categories with notify only mode on: {}".format(
                        notify_list))

                # Check for silent mode use cases
                silent_list = self.ScorebotControl.objects.filter(
                    silent=True).values_list("security_category", flat=True)
                self._logger.info(
                    "Categories with silent only mode on: {}".format(
                        silent_list))

                for (category, mode) in [(category, "notify") for category in notify_list] + \
                                        [(category, "silent") for category in silent_list]:
                    ab_var = "no"
                    category_list.append((category, mode))
                    self.notify_pp[category] = ab_var

                enforced_d, non_enforce_d = self._scan_pull_request(
                    category_list)
                message_list = []
                duplicate_msg_list = []

                if enforced_d:
                    enforced = True
                    message_list = [
                        "<h3>SCORE Bot Message</h3>Fix the following security issues before merging:"
                    ]
                    msg_enforced = self.create_message_list(
                        enforced_d, enforced)
                    for sec_cat in msg_enforced:
                        message_list.append("\n\n\n\n".join(
                            msg_enforced[sec_cat]))
                if non_enforce_d:
                    enforced = False
                    message_list += ["<h3>SCORE Bot Warnings</h3>"]
                    msg_non_enforced = self.create_message_list(
                        non_enforce_d, enforced)
                    for sec_cat in msg_non_enforced:
                        message_list.append("\n\n\n\n".join(
                            msg_non_enforced[sec_cat]))

                if message_list:
                    user = self.pull_request.user.username.decode("utf-8")
                    self._add_comment_to_pull_request(self.pull_request,
                                                      message_list)
                    self._send_email_to_stakeholders(self.pull_request.url,
                                                     user, message_list)

                # Send additional email to separate DL
                if duplicate_msg_list:
                    for (sec_cat, extra_message) in duplicate_msg_list:
                        self._send_email_to_stakeholders(
                            self.pull_request.url,
                            self.pull_request.user.username.decode("utf-8"),
                            extra_message)

            else:
                self._logger.error(
                    "Pull request not loaded or missing files: {0}".format(
                        self.pull_request.url))

            self.finalize_web_hook_job(pull_request_job)
        except exceptions.GithubAPIError:
            pull_request_url = pull_request_job.pull_request_url
            self.finalize_web_hook_job(pull_request_job)
            self._logger.critical("Invalid pull request:  " + pull_request_url)
            pass
        except ConnectionError:
            self.finalize_web_hook_job(pull_request_job)
            self._logger.critical("Github Connection Error:  " +
                                  self.pull_request.url)
            pass

        except Exception as err:
            self.finalize_web_hook_job(pull_request_job)
            self._logger.critical(
                "{0}\n{1}\n".format(type(err), traceback.format_exc()) +
                self.pull_request.url)