Beispiel #1
0
def test_find_txn_with_comment_in_phabricator(phabdouble):
    phab = phabdouble.get_phabricator_client()
    # A sec-approval request adds a comment to a revision.
    mock_comment = phabdouble.comment("my sec-approval request")
    revision = phabdouble.revision()

    # Add the two sec-approval request transactions to Phabricator. This also links the
    # comment transaction to the revision.
    comment_txn = phabdouble.api_object_for(
        phabdouble.transaction("comment", revision, comments=[mock_comment])
    )
    review_txn = phabdouble.api_object_for(
        phabdouble.transaction("reviewers.add", revision)
    )

    # Fetch our comment transaction
    comment = PhabricatorClient.single(comment_txn, "comments")

    # Add the sec-approval request transactions to the database.
    revision = phabdouble.api_object_for(revision)
    sec_approval_request = SecApprovalRequest.build(revision, [comment_txn, review_txn])

    # Search the list of sec-approval transactions for the comment.
    matching_comment = search_sec_approval_request_for_comment(
        phab, sec_approval_request
    )

    assert matching_comment == comment
Beispiel #2
0
def test_build_sec_approval_request_obj(phabdouble):
    phab = phabdouble.get_phabricator_client()
    built_revision = phabdouble.revision()
    response = phab.call_conduit("differential.revision.search",
                                 constraints={"phid": built_revision["phid"]})
    api_revision = phab.single(response, "data")
    # Simulate the transactions that take place when a sec-approval request
    # is made for a revision in Phabricator.
    transactions = [
        {
            "phid": "PHID-XACT-DREV-faketxn1",
            "type": "comment",
            "value": ANY
        },
        {
            "phid": "PHID-XACT-DREV-faketxn2",
            "type": "reviewers.add",
            "value": [f"blocking(bar)"],
        },
    ]

    sec_approval_request = SecApprovalRequest.build(api_revision, transactions)

    assert sec_approval_request.comment_candidates == [
        "PHID-XACT-DREV-faketxn1",
        "PHID-XACT-DREV-faketxn2",
    ]
    assert sec_approval_request.revision_id == api_revision["id"]
    assert sec_approval_request.diff_phid == api_revision["fields"]["diffPHID"]
Beispiel #3
0
def test_build_sec_approval_request_obj(phabdouble):
    revision = phabdouble.api_object_for(phabdouble.revision())
    # Simulate the transactions that take place when a sec-approval request
    # is made for a revision in Phabricator.
    transactions = [
        {"phid": "PHID-XACT-DREV-faketxn1", "type": "comment", "value": ANY},
        {
            "phid": "PHID-XACT-DREV-faketxn2",
            "type": "reviewers.add",
            "value": [f"blocking(bar)"],
        },
    ]

    sec_approval_request = SecApprovalRequest.build(revision, transactions)

    assert sec_approval_request.comment_candidates == [
        "PHID-XACT-DREV-faketxn1",
        "PHID-XACT-DREV-faketxn2",
    ]
    assert sec_approval_request.revision_id == revision["id"]
    assert sec_approval_request.diff_phid == revision["fields"]["diffPHID"]
Beispiel #4
0
def find_title_and_summary_for_landing(
    phab: PhabricatorClient, revision: dict, secure: bool
) -> CommitDescription:
    """Find a commit's title and summary for placing in a commit message.

    This function returns the title and summary so that it can be placed directly
    in a commit message and landed in-tree.  If this function fails to find a
    suitable commit message then an error will be raised.

    If a revision has an alternate commit message given to it by the sec-approval
    process then the alternate message will be returned.

    Args:
        phab: A PhabricatorClient instance.
        revision: A Phabricator Revision object used to generate the commit title
            and summary.
        secure: Bool indicating the revision is security-sensitive and subject to the
            sec-approval process.

    Returns: A CommitDescription object that holds the title and summary. The values
        depend on the public or secure status of the revision.
    """
    if secure:
        # The revision may be somewhere in the sec-approval workflow. We need to find
        # out where in the workflow it is to determine which title and summary to use.

        # Have we already placed a request?
        sec_approval_request = SecApprovalRequest.most_recent_request_for_revision(
            revision
        )

        if sec_approval_request:
            # We have requested a new title and possibly a new summary, too, for the
            # commit.

            logger.info(
                "sec-approval: using alternate title and summary for revision",
                extra={
                    "revision": sec_approval_request.revision_id,
                    "sec_approval_request_database_id": sec_approval_request.id,
                },
            )

            # NOTE: Any problem with fetching and constructing the commit message
            # should raise an exception and fail the whole process.
            try:
                comment = search_sec_approval_request_for_comment(
                    phab, sec_approval_request
                )

                return parse_comment(comment)

            except Exception as e:
                logger.error(
                    "sec-approval: request processing failed",
                    extra={
                        "revision": sec_approval_request.revision_id,
                        "sec_approval_request_database_id": sec_approval_request.id,
                        "reason": str(e),
                    },
                )
                raise

    # Return the revision's original title and summary.
    return CommitDescription(
        title=PhabricatorClient.expect(revision, "fields", "title"),
        summary=PhabricatorClient.expect(revision, "fields", "summary"),
        sanitized=False,
    )
Beispiel #5
0
def find_title_and_summary_for_display(
    phab: PhabricatorClient, revision: dict, secure: bool
) -> CommitDescription:
    """Find a commit's title and summary for display in Lando UI.

    This function is intended to get the commit title and summary for display to the
    end user in Lando UI. This function does NOT produce a commit title and summary
    that are suitable for landing code in a source tree because this function may
    return placeholder text for the UI.

    If a revision has an alternate commit message given to it by the sec-approval
    process then the alternate message will be returned.

    Args:
        phab: A PhabricatorClient instance.
        revision: A Phabricator Revision object used to generate the commit title
            and summary.
        secure: Bool indicating the revision is security-sensitive and subject to the
            sec-approval process.

    Returns: A CommitDescription object that holds the title and summary. The values
        depend on the public or secure status of the revision.
    """
    if secure:
        # The revision may be somewhere in the sec-approval workflow. We need to find
        # out where in the workflow it is to determine which title and summary to use.

        # Have we already placed a request?
        sec_approval_request = SecApprovalRequest.most_recent_request_for_revision(
            revision
        )

        if sec_approval_request:
            # We have requested a new title and possibly a new summary, too, for the
            # commit.

            try:
                comment = search_sec_approval_request_for_comment(
                    phab, sec_approval_request
                )
            except (TransactionSearchError, PhabricatorAPIException) as e:
                logger.error(
                    "sec-approval: request processing failed",
                    extra={
                        "revision": sec_approval_request.revision_id,
                        "sec_approval_request_database_id": sec_approval_request.id,
                        "reason": str(e),
                    },
                )
                raise

            # Parse the comment for display.
            try:
                return parse_comment(comment)
            except CommentParseError as e:
                # Parsing failed, possibly due to a change in the sec-approval
                # request message format. To future-proof the code we'll return a
                # placeholder asking the caller to reference the original Revision if
                # they want more info.
                logger.info(
                    "sec-approval: comment parsing failed, returning placeholder text",
                    extra={
                        "revision": sec_approval_request.revision_id,
                        "sec_approval_request_database_id": sec_approval_request.id,
                        "reason": str(e),
                    },
                )
                return CommitDescription(
                    title="*** please see revision for title ***",
                    summary="",
                    sanitized=True,
                )

    # Return the revision's original title and summary.
    return CommitDescription(
        title=PhabricatorClient.expect(revision, "fields", "title"),
        summary=PhabricatorClient.expect(revision, "fields", "summary"),
        sanitized=False,
    )
Beispiel #6
0
def request_sec_approval(data=None):
    """Update a Revision with a sanitized commit message.

    Kicks off the sec-approval process.

    See https://wiki.mozilla.org/Security/Bug_Approval_Process.

    Args:
        revision_id: The ID of the revision that will have a sanitized commit
            message. e.g. D1234.
        sanitized_message: The sanitized commit message.
    """
    phab = g.phabricator

    revision_id = revision_id_to_int(data["revision_id"])
    alt_message = data["sanitized_message"]

    logger.info(
        "Got request for sec-approval review of revision",
        extra=dict(revision_phid=revision_id),
    )

    if not alt_message:
        return problem(
            400,
            "Empty commit message text",
            "The sanitized commit message text cannot be empty",
            type="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400",
        )

    # FIXME: this is repeated in numerous places in the code. Needs refactoring!
    revision = phab.call_conduit(
        "differential.revision.search",
        constraints={"ids": [revision_id]},
        attachments={"projects": True},
    )
    revision = phab.single(revision, "data", none_when_empty=True)
    if revision is None:
        return problem(
            404,
            "Revision not found",
            "The requested revision does not exist",
            type="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404",
        )

    # Only secure revisions are allowed to follow the sec-approval process.
    if not revision_is_secure(revision, get_secure_project_phid(phab)):
        return problem(
            400,
            "Operation only allowed for secure revisions",
            "Only security-sensitive revisions can be given sanitized commit messages",
            type="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400",
        )

    resulting_transactions = send_sanitized_commit_message_for_review(
        revision["phid"], alt_message, phab
    )

    # Save the transactions that added the sec-approval comment so we can
    # quickly fetch the comment from Phabricator later in the process.
    #
    # NOTE: Each call to Phabricator returns two transactions: one for adding the
    # comment and one for adding the reviewer.  We don't know which transaction is
    # which at this point so we record both of them.
    sa_request = SecApprovalRequest.build(revision, resulting_transactions)
    db.session.add(sa_request)
    db.session.commit()

    return {}, 200