Repository: privacyidea/privacyidea

Issue:
Confirmation dialog for deletion actions in the WebUI
**Is your feature request related to a problem? Please describe.**
When deleting an entry in the privacyIDEA WebUI (a policy, an event, a task, a realm...), the deletion is immediate and no rollback is possible.

The risk of deleting an entry by mistake is high, especially in the "policies" section where the "Disable" button is close to the "Delete" button. A deletion mistake can cause the authentication service (or any other functionality) to be unavailable while the deleted entry is reconfigured.

**Describe the solution you'd like**
Get a confirmation message from the user when clicking the delete button.
If the user selects the "Delete" button, the deletion is done, but if the user clicks "Cancel", nothing happens.

**Describe alternatives you've considered**
In addition, rather than permanently deleting the data, move it to a trash section, which could be found in the WebUI and which would enable the recovery or permanent deletion of the data.

![Confirmation dialog](https://user-images.githubusercontent.com/110045809/187668313-185eb427-33dc-4e49-bd7b-3e2265e01596.png)
Confirmation dialog for deletion actions in the WebUI
**Is your feature request related to a problem? Please describe.**
When deleting an entry in the privacyIDEA WebUI (a policy, an event, a task, a realm...), the deletion is immediate and no rollback is possible.

The risk of deleting an entry by mistake is high, especially in the "policies" section where the "Disable" button is close to the "Delete" button. A deletion mistake can cause the authentication service (or any other functionality) to be unavailable while the deleted entry is reconfigured.

**Describe the solution you'd like**
Get a confirmation message from the user when clicking the delete button.
If the user selects the "Delete" button, the deletion is done, but if the user clicks "Cancel", nothing happens.

**Describe alternatives you've considered**
In addition, rather than permanently deleting the data, move it to a trash section, which could be found in the WebUI and which would enable the recovery or permanent deletion of the data.

![Confirmation dialog](https://user-images.githubusercontent.com/110045809/187668313-185eb427-33dc-4e49-bd7b-3e2265e01596.png)


Discussion:

Resolving patch:
diff --git a/privacyidea/api/lib/postpolicy.py b/privacyidea/api/lib/postpolicy.py
--- a/privacyidea/api/lib/postpolicy.py
+++ b/privacyidea/api/lib/postpolicy.py
@@ -576,6 +576,8 @@ def get_webui_settings(request, response):
                                      user=loginname, realm=realm).any()
         hide_buttons = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.HIDE_BUTTONS,
                                      user=loginname, realm=realm).any()
+        deletion_confirmation = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.DELETION_CONFIRMATION,
+                                     user=loginname, realm=realm).any()
         default_tokentype_pol = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.DEFAULT_TOKENTYPE,
                                               user=loginname, realm=realm).action_values(unique=True)
         show_seed = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.SHOW_SEED,
@@ -649,6 +651,7 @@ def get_webui_settings(request, response):
         content["result"]["value"]["token_rollover"] = token_rollover
         content["result"]["value"]["hide_welcome"] = hide_welcome
         content["result"]["value"]["hide_buttons"] = hide_buttons
+        content["result"]["value"]["deletion_confirmation"] = deletion_confirmation
         content["result"]["value"]["show_seed"] = show_seed
         content["result"]["value"]["show_node"] = get_privacyidea_node() if show_node else ""
         content["result"]["value"]["subscription_status"] = subscription_status()
diff --git a/privacyidea/lib/policy.py b/privacyidea/lib/policy.py
--- a/privacyidea/lib/policy.py
+++ b/privacyidea/lib/policy.py
@@ -366,6 +366,7 @@ class ACTION(object):
     SEARCH_ON_ENTER = "search_on_enter"
     TIMEOUT_ACTION = "timeout_action"
     AUTH_CACHE = "auth_cache"
+    DELETION_CONFIRMATION = "deletion_confirmation"
     HIDE_BUTTONS = "hide_buttons"
     HIDE_WELCOME = "hide_welcome_info"
     SHOW_SEED = "show_seed"
@@ -2556,6 +2557,11 @@ def get_static_policy_definitions(scope=None):
                 'desc': _("Per default disabled actions result in disabled buttons. When"
                           " checking this action, buttons of disabled actions are hidden.")
             },
+            ACTION.DELETION_CONFIRMATION: {
+                'type': 'bool',
+                'desc': _("If this is checked, there will be a confirmation prompt when "
+                          "deleting policies, events, mresolver, resolver or periodic tasks!")
+            },
             ACTION.SHOW_SEED: {
                 'type': 'bool',
                 'desc': _("If this is checked, the seed "


Test patch:
diff --git a/tests/test_api_roles.py b/tests/test_api_roles.py
--- a/tests/test_api_roles.py
+++ b/tests/test_api_roles.py
@@ -1036,6 +1036,7 @@ def test_41_webui_settings(self):
             ACTION.LOGOUTTIME, 200))
         set_policy(name="webui4", scope=SCOPE.WEBUI, action="{0!s}={1!s}".format(
             ACTION.AUDITPAGESIZE, 20))
+        set_policy(name="webui5", scope=SCOPE.WEBUI, action=ACTION.DELETION_CONFIRMATION)
         with self.app.test_request_context('/auth',
                                            method='POST',
                                            data={"username": "selfservice@realm1",
@@ -1056,6 +1057,10 @@ def test_41_webui_settings(self):
             self.assertTrue(result2.get("status"), res.data)
             # Test logout time
             self.assertEqual(result2.get("value").get("logout_time"), 200)
+            # check if value is True if deletion_confirmation is set
+            self.assertEqual(result.get("value").get("deletion_confirmation"), True)
+
+        delete_policy("webui5")
 
     def test_42_auth_timelimit_maxfail(self):
         self.setUp_user_realm2()