Ejemplo n.º 1
0
def test_open_close_request(user: User, user2: User, pkgreq: PackageRequest,
                            pkgbases: List[PackageBase]):
    set_tu([user])
    pkgbase = pkgbases[0]

    # Send an open request notification.
    notif = notify.RequestOpenNotification(user2.ID, pkgreq.ID,
                                           pkgreq.RequestType.Name, pkgbase.ID)
    notif.send()
    assert Email.count() == 1

    email = Email(1).parse()
    assert email.headers.get("To") == aur_request_ml
    assert email.headers.get("Cc") == ", ".join([user.Email, user2.Email])
    expected = f"[PRQ#{pkgreq.ID}] Orphan Request for {pkgbase.Name}"
    assert email.headers.get("Subject") == expected

    expected = f"""\
{user2.Username} [1] filed an orphan request for {pkgbase.Name} [2]:

This is a request test comment.

[1] {aur_location}/account/{user2.Username}/
[2] {aur_location}/pkgbase/{pkgbase.Name}/\
"""
    assert email.body == expected

    # Now send a closure notification on the pkgbase we just opened.
    notif = notify.RequestCloseNotification(user2.ID, pkgreq.ID, "rejected")
    notif.send()
    assert Email.count() == 2

    email = Email(2).parse()
    assert email.headers.get("To") == aur_request_ml
    assert email.headers.get("Cc") == ", ".join([user.Email, user2.Email])
    expected = f"[PRQ#{pkgreq.ID}] Orphan Request for {pkgbase.Name} Rejected"
    assert email.headers.get("Subject") == expected

    expected = f"""\
Request #{pkgreq.ID} has been rejected by {user2.Username} [1].

[1] {aur_location}/account/{user2.Username}/\
"""
    assert email.body == expected

    # Test auto-accept.
    notif = notify.RequestCloseNotification(0, pkgreq.ID, "accepted")
    notif.send()
    assert Email.count() == 3

    email = Email(3).parse()
    assert email.headers.get("To") == aur_request_ml
    assert email.headers.get("Cc") == ", ".join([user.Email, user2.Email])
    expected = (f"[PRQ#{pkgreq.ID}] Orphan Request for "
                f"{pkgbase.Name} Accepted")
    assert email.headers.get("Subject") == expected

    expected = (f"Request #{pkgreq.ID} has been accepted automatically "
                "by the Arch User Repository\npackage request system.")
    assert email.body == expected
Ejemplo n.º 2
0
async def request_close_post(request: Request,
                             id: int,
                             comments: str = Form(default=str())):
    pkgreq = get_pkgreq_by_id(id)

    # `pkgreq`.User can close their own request.
    approved = [pkgreq.User]
    if not request.user.has_credential(creds.PKGREQ_CLOSE, approved=approved):
        # Request user doesn't have permission here: redirect to '/'.
        return RedirectResponse("/", status_code=HTTPStatus.SEE_OTHER)

    context = make_context(request, "Close Request")
    context["pkgreq"] = pkgreq

    now = time.utcnow()
    with db.begin():
        pkgreq.Closer = request.user
        pkgreq.ClosureComment = comments
        pkgreq.ClosedTS = now
        pkgreq.Status = REJECTED_ID

    notify_ = notify.RequestCloseNotification(request.user.ID, pkgreq.ID,
                                              pkgreq.status_display())
    notify_.send()

    return RedirectResponse("/requests", status_code=HTTPStatus.SEE_OTHER)
Ejemplo n.º 3
0
def test_close_request_closure_comment(user: User, user2: User,
                                       pkgreq: PackageRequest,
                                       pkgbases: List[PackageBase]):
    pkgbase = pkgbases[0]
    with db.begin():
        pkgreq.ClosureComment = "This is a test closure comment."

    notif = notify.RequestCloseNotification(user2.ID, pkgreq.ID, "accepted")
    notif.send()
    assert Email.count() == 1

    email = Email(1).parse()
    assert email.headers.get("To") == aur_request_ml
    assert email.headers.get("Cc") == ", ".join([user.Email, user2.Email])
    expected = f"[PRQ#{pkgreq.ID}] Orphan Request for {pkgbase.Name} Accepted"
    assert email.headers.get("Subject") == expected

    expected = f"""\
Request #{pkgreq.ID} has been accepted by {user2.Username} [1]:

This is a test closure comment.

[1] {aur_location}/account/{user2.Username}/\
"""
    assert email.body == expected
Ejemplo n.º 4
0
def test_close_request_comaintainer_cc(user: User, user2: User,
                                       pkgreq: PackageRequest,
                                       pkgbases: List[PackageBase]):
    pkgbase = pkgbases[0]
    with db.begin():
        db.create(models.PackageComaintainer,
                  PackageBase=pkgbase,
                  User=user2,
                  Priority=1)

    notif = notify.RequestCloseNotification(0, pkgreq.ID, "accepted")
    notif.send()
    assert Email.count() == 1

    email = Email(1).parse()
    assert email.headers.get("To") == aur_request_ml
    assert email.headers.get("Cc") == ", ".join([user.Email, user2.Email])
Ejemplo n.º 5
0
async def pkgbase_request_post(request: Request,
                               name: str,
                               type: str = Form(...),
                               merge_into: str = Form(default=None),
                               comments: str = Form(default=str()),
                               next: str = Form(default=str())):
    pkgbase = get_pkg_or_base(name, PackageBase)

    # Create our render context.
    context = await make_variable_context(request, "Submit Request")
    context["pkgbase"] = pkgbase

    types = {"deletion": DELETION_ID, "merge": MERGE_ID, "orphan": ORPHAN_ID}

    if type not in types:
        # In the case that someone crafted a POST request with an invalid
        # type, just return them to the request form with BAD_REQUEST status.
        return render_template(request,
                               "pkgbase/request.html",
                               context,
                               status_code=HTTPStatus.BAD_REQUEST)

    try:
        validate.request(pkgbase, type, comments, merge_into, context)
    except ValidationError as exc:
        logger.error(f"Request Validation Error: {str(exc.data)}")
        context["errors"] = exc.data
        return render_template(request, "pkgbase/request.html", context)

    # All good. Create a new PackageRequest based on the given type.
    now = time.utcnow()
    with db.begin():
        pkgreq = db.create(PackageRequest,
                           ReqTypeID=types.get(type),
                           User=request.user,
                           RequestTS=now,
                           PackageBase=pkgbase,
                           PackageBaseName=pkgbase.Name,
                           MergeBaseName=merge_into,
                           Comments=comments,
                           ClosureComment=str())

    # Prepare notification object.
    notif = notify.RequestOpenNotification(request.user.ID,
                                           pkgreq.ID,
                                           type,
                                           pkgreq.PackageBase.ID,
                                           merge_into=merge_into or None)

    # Send the notification now that we're out of the DB scope.
    notif.send()

    auto_orphan_age = config.getint("options", "auto_orphan_age")
    auto_delete_age = config.getint("options", "auto_delete_age")

    ood_ts = pkgbase.OutOfDateTS or 0
    flagged = ood_ts and (now - ood_ts) >= auto_orphan_age
    is_maintainer = pkgbase.Maintainer == request.user
    outdated = (now - pkgbase.SubmittedTS) <= auto_delete_age

    if type == "orphan" and flagged:
        # This request should be auto-accepted.
        with db.begin():
            pkgbase.Maintainer = None
            pkgreq.Status = ACCEPTED_ID
        notif = notify.RequestCloseNotification(request.user.ID, pkgreq.ID,
                                                pkgreq.status_display())
        notif.send()
        logger.debug(f"New request #{pkgreq.ID} is marked for auto-orphan.")
    elif type == "deletion" and is_maintainer and outdated:
        # This request should be auto-accepted.
        notifs = actions.pkgbase_delete_instance(request,
                                                 pkgbase,
                                                 comments=comments)
        util.apply_all(notifs, lambda n: n.send())
        logger.debug(f"New request #{pkgreq.ID} is marked for auto-deletion.")

    # Redirect the submitting user to /packages.
    return RedirectResponse("/packages", status_code=HTTPStatus.SEE_OTHER)
Ejemplo n.º 6
0
def handle_request(request: Request,
                   reqtype_id: int,
                   pkgbase: PackageBase,
                   target: PackageBase = None) -> List[notify.Notification]:
    """
    Handle package requests before performing an action.

    The actions we're interested in are disown (orphan), delete and
    merge. There is now an automated request generation and closure
    notification when a privileged user performs one of these actions
    without a pre-existing request. They all commit changes to the
    database, and thus before calling, state should be verified to
    avoid leaked database records regarding these requests.

    Otherwise, we accept and reject requests based on their state
    and send out the relevent notifications.

    :param requester: User who needs this a `pkgbase` request handled
    :param reqtype_id: RequestType.ID
    :param pkgbase: PackageBase which the request is about
    :param target: Optional target to merge into
    """
    notifs: List[notify.Notification] = []

    # If it's an orphan request, perform further verification
    # regarding existing requests.
    if reqtype_id == ORPHAN_ID:
        if not verify_orphan_request(request.user, pkgbase):
            _ = l10n.get_translator_for_request(request)
            raise InvariantError(
                _("No due existing orphan requests to accept for %s.") %
                pkgbase.Name)

    # Produce a base query for requests related to `pkgbase`, based
    # on ReqTypeID matching `reqtype_id`, pending status and a correct
    # PackagBaseName column.
    query: orm.Query = pkgbase.requests.filter(
        and_(PackageRequest.ReqTypeID == reqtype_id,
             PackageRequest.Status == PENDING_ID,
             PackageRequest.PackageBaseName == pkgbase.Name))

    # Build a query for records we should accept. For merge requests,
    # this is specific to a matching MergeBaseName. For others, this
    # just ends up becoming `query`.
    accept_query: orm.Query = query
    if target:
        # If a `target` was supplied, filter by MergeBaseName
        accept_query = query.filter(
            PackageRequest.MergeBaseName == target.Name)

    # Build an accept list out of `accept_query`.
    to_accept: List[PackageRequest] = accept_query.all()
    accepted_ids: Set[int] = set(p.ID for p in to_accept)

    # Build a reject list out of `query` filtered by IDs not found
    # in `to_accept`. That is, unmatched records of the same base
    # query properties.
    to_reject: List[PackageRequest] = query.filter(
        ~PackageRequest.ID.in_(accepted_ids)).all()

    # If we have no requests to accept, create a new one.
    # This is done to increase tracking of actions occurring
    # through the website.
    if not to_accept:
        utcnow = time.utcnow()
        with db.begin():
            pkgreq = db.create(PackageRequest,
                               ReqTypeID=reqtype_id,
                               RequestTS=utcnow,
                               User=request.user,
                               PackageBase=pkgbase,
                               PackageBaseName=pkgbase.Name,
                               Comments="Autogenerated by aurweb.",
                               ClosureComment=str())

            # If it's a merge request, set MergeBaseName to `target`.Name.
            if pkgreq.ReqTypeID == MERGE_ID:
                pkgreq.MergeBaseName = target.Name

            # Add the new request to `to_accept` and allow standard
            # flow to continue afterward.
            to_accept.append(pkgreq)

    # Update requests with their new status and closures.
    with db.begin():
        util.apply_all(
            to_accept, lambda p: close_pkgreq(p, request.user, pkgbase, target,
                                              ACCEPTED_ID))
        util.apply_all(
            to_reject, lambda p: close_pkgreq(p, request.user, pkgbase, target,
                                              REJECTED_ID))

    # Create RequestCloseNotifications for all requests involved.
    for pkgreq in (to_accept + to_reject):
        notif = notify.RequestCloseNotification(request.user.ID, pkgreq.ID,
                                                pkgreq.status_display())
        notifs.append(notif)

    # Return notifications to the caller for sending.
    return notifs