mirror of
https://gitlab.archlinux.org/archlinux/aurweb.git
synced 2025-02-03 10:43:03 +01:00
feat(FastAPI): add /packages (post) action: 'delete'
Improvements: - Package deletion now creates a PackageRequest on behalf of the deleter if one does not yet exist. - All package deletions are now logged to keep track of who did what. Signed-off-by: Kevin Morris <kevr@0cost.org>
This commit is contained in:
parent
81417ea8b2
commit
60bffa4fb6
3 changed files with 121 additions and 1 deletions
|
@ -9,7 +9,7 @@ from sqlalchemy import and_, case
|
||||||
import aurweb.filters
|
import aurweb.filters
|
||||||
import aurweb.packages.util
|
import aurweb.packages.util
|
||||||
|
|
||||||
from aurweb import db, defaults, l10n, models, util
|
from aurweb import db, defaults, l10n, logging, models, util
|
||||||
from aurweb.auth import auth_required
|
from aurweb.auth import auth_required
|
||||||
from aurweb.models.package_request import ACCEPTED_ID, PENDING_ID, REJECTED_ID
|
from aurweb.models.package_request import ACCEPTED_ID, PENDING_ID, REJECTED_ID
|
||||||
from aurweb.models.relation_type import CONFLICTS_ID
|
from aurweb.models.relation_type import CONFLICTS_ID
|
||||||
|
@ -20,6 +20,7 @@ from aurweb.scripts import notify, popupdate
|
||||||
from aurweb.scripts.rendercomment import update_comment_render
|
from aurweb.scripts.rendercomment import update_comment_render
|
||||||
from aurweb.templates import make_context, make_variable_context, render_raw_template, render_template
|
from aurweb.templates import make_context, make_variable_context, render_raw_template, render_template
|
||||||
|
|
||||||
|
logger = logging.get_logger(__name__)
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@ -1227,6 +1228,47 @@ async def packages_disown(request: Request, package_ids: List[int] = [],
|
||||||
|
|
||||||
return (True, ["The selected packages have been disowned."])
|
return (True, ["The selected packages have been disowned."])
|
||||||
|
|
||||||
|
|
||||||
|
async def packages_delete(request: Request, package_ids: List[int] = [],
|
||||||
|
confirm: bool = False, merge_into: str = str(),
|
||||||
|
**kwargs):
|
||||||
|
if not package_ids:
|
||||||
|
return (False, ["You did not select any packages to delete."])
|
||||||
|
|
||||||
|
if not confirm:
|
||||||
|
return (False, ["The selected packages have not been deleted, "
|
||||||
|
"check the confirmation checkbox."])
|
||||||
|
|
||||||
|
if not request.user.has_credential("CRED_PKGBASE_DELETE"):
|
||||||
|
return (False, ["You do not have permission to delete packages."])
|
||||||
|
|
||||||
|
# A "memo" used to store names of packages that we delete.
|
||||||
|
# We'll use this to log out a message about the deletions that occurred.
|
||||||
|
deleted_pkgs = []
|
||||||
|
|
||||||
|
# set-ify package_ids and query the database for related records.
|
||||||
|
package_ids = set(package_ids)
|
||||||
|
packages = db.query(models.Package).filter(
|
||||||
|
models.Package.ID.in_(package_ids)).all()
|
||||||
|
|
||||||
|
if len(packages) != len(package_ids):
|
||||||
|
# Let the user know there was an issue with their input: they have
|
||||||
|
# provided at least one package_id which does not exist in the DB.
|
||||||
|
# TODO: This error has not yet been translated.
|
||||||
|
return (False, ["One of the packages you selected does not exist."])
|
||||||
|
|
||||||
|
# Now let's actually walk through and delete all of the packages,
|
||||||
|
# using the same method we use in our /pkgbase/{name}/delete route.
|
||||||
|
for pkg in packages:
|
||||||
|
deleted_pkgs.append(pkg.Name)
|
||||||
|
delete_package(request.user, pkg)
|
||||||
|
|
||||||
|
# Log out the fact that this happened for accountability.
|
||||||
|
logger.info(f"Privileged user '{request.user.Username}' deleted the "
|
||||||
|
f"following packages: {str(deleted_pkgs)}.")
|
||||||
|
|
||||||
|
return (True, ["The selected packages have been deleted."])
|
||||||
|
|
||||||
# A mapping of action string -> callback functions used within the
|
# A mapping of action string -> callback functions used within the
|
||||||
# `packages_post` route below. We expect any action callback to
|
# `packages_post` route below. We expect any action callback to
|
||||||
# return a tuple in the format: (succeeded: bool, message: List[str]).
|
# return a tuple in the format: (succeeded: bool, message: List[str]).
|
||||||
|
@ -1236,6 +1278,7 @@ PACKAGE_ACTIONS = {
|
||||||
"unnotify": packages_unnotify,
|
"unnotify": packages_unnotify,
|
||||||
"adopt": packages_adopt,
|
"adopt": packages_adopt,
|
||||||
"disown": packages_disown,
|
"disown": packages_disown,
|
||||||
|
"delete": packages_delete,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1020,6 +1020,10 @@ msgstr ""
|
||||||
msgid "You did not select any packages to delete."
|
msgid "You did not select any packages to delete."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: aurweb/routers/packages.py
|
||||||
|
msgid "One of the packages you selected does not exist."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: lib/pkgbasefuncs.inc.php
|
#: lib/pkgbasefuncs.inc.php
|
||||||
msgid "The selected packages have been deleted."
|
msgid "The selected packages have been deleted."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
|
@ -2318,3 +2318,76 @@ def test_packages_post_disown(client: TestClient, user: User,
|
||||||
successes = get_successes(resp.text)
|
successes = get_successes(resp.text)
|
||||||
expected = "The selected packages have been disowned."
|
expected = "The selected packages have been disowned."
|
||||||
assert successes[0].text.strip() == expected
|
assert successes[0].text.strip() == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_packages_post_delete(caplog: pytest.fixture, client: TestClient,
|
||||||
|
user: User, tu_user: User, package: Package):
|
||||||
|
|
||||||
|
# First, let's try to use the delete action with no packages IDs.
|
||||||
|
user_cookies = {"AURSID": user.login(Request(), "testPassword")}
|
||||||
|
with client as request:
|
||||||
|
resp = request.post("/packages", data={
|
||||||
|
"action": "delete"
|
||||||
|
}, cookies=user_cookies)
|
||||||
|
assert resp.status_code == int(HTTPStatus.BAD_REQUEST)
|
||||||
|
errors = get_errors(resp.text)
|
||||||
|
expected = "You did not select any packages to delete."
|
||||||
|
assert errors[0].text.strip() == expected
|
||||||
|
|
||||||
|
# Now, let's try to delete real packages without supplying "confirm".
|
||||||
|
with client as request:
|
||||||
|
resp = request.post("/packages", data={
|
||||||
|
"action": "delete",
|
||||||
|
"IDs": [package.ID]
|
||||||
|
}, cookies=user_cookies)
|
||||||
|
assert resp.status_code == int(HTTPStatus.BAD_REQUEST)
|
||||||
|
errors = get_errors(resp.text)
|
||||||
|
expected = ("The selected packages have not been deleted, "
|
||||||
|
"check the confirmation checkbox.")
|
||||||
|
assert errors[0].text.strip() == expected
|
||||||
|
|
||||||
|
# And again, with everything, but `user` doesn't have permissions.
|
||||||
|
with client as request:
|
||||||
|
resp = request.post("/packages", data={
|
||||||
|
"action": "delete",
|
||||||
|
"IDs": [package.ID],
|
||||||
|
"confirm": True
|
||||||
|
}, cookies=user_cookies)
|
||||||
|
assert resp.status_code == int(HTTPStatus.BAD_REQUEST)
|
||||||
|
errors = get_errors(resp.text)
|
||||||
|
expected = "You do not have permission to delete packages."
|
||||||
|
assert errors[0].text.strip() == expected
|
||||||
|
|
||||||
|
# Now, let's switch over to making the requests as a TU.
|
||||||
|
# However, this next request will be rejected due to supplying
|
||||||
|
# an invalid package ID.
|
||||||
|
tu_cookies = {"AURSID": tu_user.login(Request(), "testPassword")}
|
||||||
|
with client as request:
|
||||||
|
resp = request.post("/packages", data={
|
||||||
|
"action": "delete",
|
||||||
|
"IDs": [0],
|
||||||
|
"confirm": True
|
||||||
|
}, cookies=tu_cookies)
|
||||||
|
assert resp.status_code == int(HTTPStatus.BAD_REQUEST)
|
||||||
|
errors = get_errors(resp.text)
|
||||||
|
expected = "One of the packages you selected does not exist."
|
||||||
|
assert errors[0].text.strip() == expected
|
||||||
|
|
||||||
|
# Whoo. Now, let's finally make a valid request as `tu_user`
|
||||||
|
# to delete `package`.
|
||||||
|
with client as request:
|
||||||
|
resp = request.post("/packages", data={
|
||||||
|
"action": "delete",
|
||||||
|
"IDs": [package.ID],
|
||||||
|
"confirm": True
|
||||||
|
}, cookies=tu_cookies)
|
||||||
|
assert resp.status_code == int(HTTPStatus.OK)
|
||||||
|
successes = get_successes(resp.text)
|
||||||
|
expected = "The selected packages have been deleted."
|
||||||
|
assert successes[0].text.strip() == expected
|
||||||
|
|
||||||
|
# Expect that the package deletion was logged.
|
||||||
|
packages = [package.Name]
|
||||||
|
expected = (f"Privileged user '{tu_user.Username}' deleted the "
|
||||||
|
f"following packages: {str(packages)}.")
|
||||||
|
assert expected in caplog.text
|
||||||
|
|
Loading…
Add table
Reference in a new issue