diff --git a/aurweb/routers/packages.py b/aurweb/routers/packages.py index f8ac764a..2de82aaa 100644 --- a/aurweb/routers/packages.py +++ b/aurweb/routers/packages.py @@ -932,7 +932,9 @@ async def pkgbase_unvote(request: Request, name: str): status_code=HTTPStatus.SEE_OTHER) -def disown_pkgbase(pkgbase: models.PackageBase, disowner: models.User): +def pkgbase_disown_instance(request: Request, pkgbase: models.PackageBase): + disowner = request.user + conn = db.ConnectionExecutor(db.get_engine().raw_connection()) notif = notify.DisownNotification(conn, disowner.ID, pkgbase.ID) @@ -990,7 +992,7 @@ async def pkgbase_disown_post(request: Request, name: str, return render_template(request, "packages/disown.html", context, status_code=HTTPStatus.BAD_REQUEST) - disown_pkgbase(pkgbase, request.user) + pkgbase_disown_instance(request, pkgbase) return RedirectResponse(f"/pkgbase/{name}", status_code=HTTPStatus.SEE_OTHER) @@ -1191,6 +1193,40 @@ async def packages_adopt(request: Request, package_ids: List[int] = [], return (True, ["The selected packages have been adopted."]) + +async def packages_disown(request: Request, package_ids: List[int] = [], + confirm: bool = False, **kwargs): + if not package_ids: + return (False, ["You did not select any packages to disown."]) + + if not confirm: + return (False, ["The selected packages have not been disowned, " + "check the confirmation checkbox."]) + + bases = set() + package_ids = set(package_ids) + packages = db.query(models.Package).filter( + models.Package.ID.in_(package_ids)).all() + + for pkg in packages: + if pkg.PackageBase not in bases: + bases.update({pkg.PackageBase}) + + # Check that the user has credentials for every package they selected. + for pkgbase in bases: + has_cred = request.user.has_credential("CRED_PKGBASE_DISOWN", + approved=[pkgbase.Maintainer]) + if not has_cred: + # TODO: This error needs to be translated. + return (False, ["You are not allowed to disown one " + "of the packages you selected."]) + + # Now, really disown the bases. + for pkgbase in bases: + pkgbase_disown_instance(request, pkgbase) + + return (True, ["The selected packages have been disowned."]) + # A mapping of action string -> callback functions used within the # `packages_post` route below. We expect any action callback to # return a tuple in the format: (succeeded: bool, message: List[str]). @@ -1199,6 +1235,7 @@ PACKAGE_ACTIONS = { "notify": packages_notify, "unnotify": packages_unnotify, "adopt": packages_adopt, + "disown": packages_disown, } diff --git a/po/aurweb.pot b/po/aurweb.pot index 83e884b8..849cae75 100644 --- a/po/aurweb.pot +++ b/po/aurweb.pot @@ -1036,6 +1036,10 @@ msgstr "" msgid "You must be logged in before you can disown packages." msgstr "" +#: aurweb/routers/packages.py +msgid "You are not allowed to disown one of the packages you selected." +msgstr "" + #: lib/pkgbasefuncs.inc.php msgid "You did not select any packages to adopt." msgstr "" diff --git a/test/test_packages_routes.py b/test/test_packages_routes.py index 37827e46..df028430 100644 --- a/test/test_packages_routes.py +++ b/test/test_packages_routes.py @@ -2259,3 +2259,62 @@ def test_packages_post_adopt(client: TestClient, user: User, successes = get_successes(resp.text) expected = "The selected packages have been adopted." assert successes[0].text.strip() == expected + + +def test_packages_post_disown(client: TestClient, user: User, + maintainer: User, package: Package): + # Initially prove that we have a maintainer: `maintainer`. + assert package.PackageBase.Maintainer is not None + assert package.PackageBase.Maintainer == maintainer + + # Try to run the disown action with no IDs; get an error. + cookies = {"AURSID": maintainer.login(Request(), "testPassword")} + with client as request: + resp = request.post("/packages", data={ + "action": "disown" + }, cookies=cookies) + assert resp.status_code == int(HTTPStatus.BAD_REQUEST) + errors = get_errors(resp.text) + expected = "You did not select any packages to disown." + assert errors[0].text.strip() == expected + assert package.PackageBase.Maintainer is not None + + # Try to disown `package` without giving the confirm argument. + with client as request: + resp = request.post("/packages", data={ + "action": "disown", + "IDs": [package.ID] + }, cookies=cookies) + assert resp.status_code == int(HTTPStatus.BAD_REQUEST) + assert package.PackageBase.Maintainer is not None + errors = get_errors(resp.text) + expected = ("The selected packages have not been disowned, " + "check the confirmation checkbox.") + assert errors[0].text.strip() == expected + + # Now, try to disown `package` without credentials (as `user`). + user_cookies = {"AURSID": user.login(Request(), "testPassword")} + with client as request: + resp = request.post("/packages", data={ + "action": "disown", + "IDs": [package.ID], + "confirm": True + }, cookies=user_cookies) + assert resp.status_code == int(HTTPStatus.BAD_REQUEST) + assert package.PackageBase.Maintainer is not None + errors = get_errors(resp.text) + expected = "You are not allowed to disown one of the packages you selected." + assert errors[0].text.strip() == expected + + # Now, let's really disown `package` as `maintainer`. + with client as request: + resp = request.post("/packages", data={ + "action": "disown", + "IDs": [package.ID], + "confirm": True + }, cookies=cookies) + + assert package.PackageBase.Maintainer is None + successes = get_successes(resp.text) + expected = "The selected packages have been disowned." + assert successes[0].text.strip() == expected