diff --git a/atst/routes/ccpo.py b/atst/routes/ccpo.py index c2846aa1..a1a3a87c 100644 --- a/atst/routes/ccpo.py +++ b/atst/routes/ccpo.py @@ -26,7 +26,8 @@ def activity_history(): @user_can(Permissions.VIEW_CCPO_USER, message="view ccpo users") def users(): users = Users.get_ccpo_users() - return render_template("ccpo/users.html", users=users) + users_info = [(user, CCPOUserForm(obj=user)) for user in users] + return render_template("ccpo/users.html", users_info=users_info) @bp.route("/ccpo-users/new") @@ -56,3 +57,12 @@ def confirm_new_user(): Users.give_ccpo_perms(user) flash("ccpo_user_added", user_name=user.full_name) return redirect(url_for("ccpo.users")) + + +@bp.route("/ccpo-users/remove-access/", methods=["POST"]) +@user_can(Permissions.DELETE_CCPO_USER, message="remove ccpo user") +def remove_access(user_id): + user = Users.get(user_id) + Users.revoke_ccpo_perms(user) + flash("ccpo_user_removed", user_name=user.full_name) + return redirect(url_for("ccpo.users")) diff --git a/atst/utils/flash.py b/atst/utils/flash.py index e4f852ea..b511f4d2 100644 --- a/atst/utils/flash.py +++ b/atst/utils/flash.py @@ -40,6 +40,11 @@ MESSAGES = { "message_template": translate("ccpo.form.user_not_found_text"), "category": "info", }, + "ccpo_user_removed": { + "title_template": translate("flash.success"), + "message_template": "You have successfully removed {{ user_name }}'s CCPO permissions.", + "category": "success", + }, "environment_added": { "title_template": translate("flash.success"), "message_template": """ diff --git a/templates/ccpo/users.html b/templates/ccpo/users.html index bc77a83f..f5ea52a8 100644 --- a/templates/ccpo/users.html +++ b/templates/ccpo/users.html @@ -1,6 +1,9 @@ {% extends "base.html" %} +{% from "components/alert.html" import Alert %} +{% from "components/delete_confirmation.html" import DeleteConfirmation %} {% from "components/icon.html" import Icon %} +{% from "components/modal.html" import Modal %} {% block content %}
@@ -16,14 +19,30 @@ {{ "common.name" | translate }} {{ "common.email" | translate }} {{ "common.dod_id" | translate }} + {% if user_can(permissions.DELETE_CCPO_USER) %} + + {% endif %} - {% for user in users %} + {% for user, form in users_info %} + {% set modal_id = "disable_ccpo_user_{}".format(user.dod_id) %} + {% set disable_button_class = 'button-danger-outline' %} + {% if user == g.current_user %} + {% set disable_button_class = "usa-button-disabled" %} + {% endif %} + {{ user.full_name }} {{ user.email }} {{ user.dod_id }} + {% if user_can(permissions.DELETE_CCPO_USER) %} + + + {{ "common.disable" | translate }} + + + {% endif %} {% endfor %} @@ -36,4 +55,27 @@ {% endif %} + {% if user_can(permissions.DELETE_CCPO_USER) %} + {% for user, form in users_info %} + {% set modal_id = "disable_ccpo_user_{}".format(user.dod_id) %} + {% call Modal(name=modal_id) %} + {{ + Alert( + title=("components.modal.destructive_title" | translate), + message=("ccpo.disable_user.alert_message" | translate({"user_name": user.full_name})), + level="warning" + ) + }} + {{ + DeleteConfirmation( + modal_id=modal_id, + delete_text='Remove Access', + delete_action=(url_for('ccpo.remove_access', user_id=user.id)), + form=form, + confirmation_text='remove' + ) + }} + {% endcall %} + {% endfor %} + {% endif %} {% endblock %} diff --git a/tests/routes/test_ccpo.py b/tests/routes/test_ccpo.py index 618eb9b9..c4a8e2a4 100644 --- a/tests/routes/test_ccpo.py +++ b/tests/routes/test_ccpo.py @@ -1,5 +1,6 @@ from flask import url_for +from atst.domain.users import Users from atst.utils.localization import translate from tests.factories import UserFactory @@ -45,10 +46,19 @@ def test_confirm_new_user(user_session, client): ) assert new_user.dod_id in response.data.decode() - # give person with out ATAT account CCPO permissions + # give person without ATAT account CCPO permissions response = client.post( url_for("ccpo.confirm_new_user"), data={"dod_id": random_dod_id}, follow_redirects=True, ) assert random_dod_id not in response.data.decode() + + +def test_remove_access(user_session, client): + ccpo = UserFactory.create_ccpo() + user = UserFactory.create_ccpo() + user_session(ccpo) + + response = client.post(url_for("ccpo.remove_access", user_id=user.id)) + assert user not in Users.get_ccpo_users() diff --git a/tests/test_access.py b/tests/test_access.py index f459ea06..7da4868e 100644 --- a/tests/test_access.py +++ b/tests/test_access.py @@ -161,6 +161,17 @@ def test_ccpo_confirm_new_user_access(post_url_assert_status): post_url_assert_status(rando, url, 404, data={"dod_id": user.dod_id}) +# ccpo.remove_access +def test_ccpo_remove_access(post_url_assert_status): + ccpo = user_with(PermissionSets.MANAGE_CCPO_USERS) + rando = user_with() + user = UserFactory.create_ccpo() + + url = url_for("ccpo.remove_access", user_id=user.id) + post_url_assert_status(rando, url, 404) + post_url_assert_status(ccpo, url, 302) + + # applications.access_environment def test_applications_access_environment_access(get_url_assert_status): dev = UserFactory.create() diff --git a/translations.yaml b/translations.yaml index fd97ae8f..f03ed93c 100644 --- a/translations.yaml +++ b/translations.yaml @@ -37,6 +37,9 @@ ccpo: return_link: Return to list of CCPO users user_not_found_title: User not found user_not_found_text: To add someone as a CCPO user, they must already have an ATAT account. + disable_user: + alert_message: "Confirm removing CCPO superuser access from {user_name}" + remove_button: Remove Access common: cancel: Cancel close: Close @@ -46,6 +49,7 @@ common: deactivate: Deactivate delete_confirm: 'Please type the word {word} to confirm:' dod_id: DoD ID + disable: Disable edit: Edit email: Email members: Members