Repository: zaproxy/zaproxy
Issue: ascan disableAllScanners API endpoint no longer disabled all scanners

### Describe the bug

As of ZAP `w2022-03-07`, using the API endpoint `ascan/action/disableAllScanners/` with a policy file no longer disabled scanners that are identified in that policy file as enabled.

### Steps to reproduce the behavior

The issue is best demonstrated using the following scripts.

Create a file named `test.py`
```py
import time
from subprocess import Popen

from zapv2 import ZAPv2 as ZAP

def print_totals(json):
  totals = {
    'enabled': 0,
    'disabled': 0,
  }

  for scanner in json['scanners']:
    if scanner['enabled'] == 'true':
      totals['enabled'] = totals['enabled'] + 1
    else:
      totals['disabled'] = totals['disabled'] + 1
  
  print(totals)

port = '42248'

settings = [
  '/zap/zap.sh', '-daemon',
  '-config', 'network.localServers.mainProxy.address=0.0.0.0',
  '-config', f'network.localServers.mainProxy.port={port}',
  '-dir', '/home/zap',
  '-config', 'api.disablekey=true',
  '-config', 'api.addrs.addr.name=.*',
  '-config', 'api.addrs.addr.regex=true',
  '-silent'
]

connection_details = {'http': f'http://127.0.0.1:{port}', 'https': f'http://127.0.0.1:{port}'}

p = Popen(settings)
print('sleeping...')
time.sleep(10)
print('done')
print('ZAP should be running')

zap = ZAP(proxies=connection_details)

policy_name = 'API-Minimal'

url = zap.base + 'ascan/view/scanners'
before_response = zap._request(url, {'scanPolicyName': policy_name, 'apikey': ''})
print_totals(before_response)
zap.ascan.disable_all_scanners(policy_name)
after_response = zap._request(url, {'scanPolicyName': policy_name, 'apikey': ''})
print_totals(after_response)
```

create a bash script `run.sh` with the contents

```sh
#!/bin/bash

docker run -d -it --name zap-test owasp/zap2docker-weekly:w2022-07-25
docker cp test.py zap-test:/zap
docker exec -it zap-test mkdir /home/zap/policies
docker exec -it zap-test cp /home/zap/.ZAP_D/policies/API-Minimal.policy /home/zap/policies
docker exec -it zap-test python /zap/test.py
docker kill zap-test
```

Make the script executable, `chmod 755 run.sh`

Run the script.

The script will 

- start the ZAP docker container
- copy the test python script to it
- move the API policy to the expected policy location
- run the python script (which will...)
  - start ZAP
  - query the API to get all the scanners for the API policy, then count the number of enabled and disabled scanner
  - call ascan `disabledAllScanners` with the API policy
  - query the API to get all the scanners for the API policy, then count the number of enabled and disabled scanner
- kill the container

The output I've experienced is:
```
{'enabled': 25, 'disabled': 36}
{'enabled': 16, 'disabled': 45}
```

### Expected behavior

All the scanners for the policy should be disabled. On versions of ZAP before `w2022-03-07`, the output of the above script is:

```
{'enabled': 25, 'disabled': 36}
{'enabled': 0, 'disabled': 61}
```

### Software versions

since w2022-03-07

### Screenshots

_No response_

### Errors from the zap.log file

_No response_

### Additional context

_No response_

### Would you like to help fix this issue?

- [ ] Yes

Changed files:
zap/src/test/java/org/parosproxy/paros/core/scanner/AbstractPluginUnitTest.java, zap/src/main/java/org/parosproxy/paros/core/scanner/AbstractPlugin.java

diff --git a/zap/src/main/java/org/parosproxy/paros/core/scanner/AbstractPlugin.java b/zap/src/main/java/org/parosproxy/paros/core/scanner/AbstractPlugin.java
index 0e0741764..919c11653 100644
--- a/zap/src/main/java/org/parosproxy/paros/core/scanner/AbstractPlugin.java
+++ b/zap/src/main/java/org/parosproxy/paros/core/scanner/AbstractPlugin.java
@@ -71,6 +71,7 @@
 // ZAP: 2020/11/26 Use Log4j2 getLogger() and deprecate Log4j1.x.
 // ZAP: 2021/07/20 Correct message updated with the scan rule ID header (Issue 6689).
 // ZAP: 2022/06/05 Remove usage of HttpException.
+// ZAP: 2022/08/03 Keep enabled state when setting default alert threshold (Issue 7400).
 package org.parosproxy.paros.core.scanner;
 
 import java.io.IOException;
@@ -831,8 +832,13 @@ public abstract class AbstractPlugin implements Plugin, Comparable<Object> {
 
     @Override
     public void setDefaultAlertThreshold(AlertThreshold level) {
+        AlertThreshold oldDefaultAlertThreshold = defaultAttackThreshold;
         this.defaultAttackThreshold = level;
-        setEnabledFromLevel();
+        if ((defaultAttackThreshold == AlertThreshold.OFF
+                        || oldDefaultAlertThreshold == AlertThreshold.OFF)
+                && getAlertThreshold(true) == AlertThreshold.DEFAULT) {
+            setEnabled(defaultAttackThreshold != AlertThreshold.OFF);
+        }
     }
 
     private void setEnabledFromLevel() {
diff --git a/zap/src/test/java/org/parosproxy/paros/core/scanner/AbstractPluginUnitTest.java b/zap/src/test/java/org/parosproxy/paros/core/scanner/AbstractPluginUnitTest.java
index cfe2992ef..9668845ad 100644
--- a/zap/src/test/java/org/parosproxy/paros/core/scanner/AbstractPluginUnitTest.java
+++ b/zap/src/test/java/org/parosproxy/paros/core/scanner/AbstractPluginUnitTest.java
@@ -26,6 +26,7 @@ import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.nullValue;
 import static org.hamcrest.Matchers.sameInstance;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
 import static org.mockito.BDDMockito.given;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
@@ -35,12 +36,15 @@ import static org.mockito.Mockito.when;
 
 import java.io.IOException;
 import java.security.InvalidParameterException;
+import java.util.stream.Stream;
 import org.apache.commons.configuration.Configuration;
 import org.apache.commons.httpclient.URI;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.MethodSource;
 import org.junit.jupiter.params.provider.ValueSource;
 import org.mockito.ArgumentCaptor;
 import org.parosproxy.paros.Constant;
@@ -214,6 +218,25 @@ class AbstractPluginUnitTest extends PluginTestUtils {
         assertThat(plugin.isEnabled(), is(equalTo(Boolean.FALSE)));
     }
 
+    private static Stream<Arguments> alertThresholdsAndEnabledState() {
+        return Stream.of(AlertThreshold.values())
+                .flatMap(e -> Stream.of(arguments(e, true), arguments(e, false)));
+    }
+
+    @ParameterizedTest
+    @MethodSource("alertThresholdsAndEnabledState")
+    void shouldNotChangeEnabledStateIfNotUsingDefaultThresholdWhenDefaultSet(
+            AlertThreshold defaultThreshold, boolean enabled) {
+        // Given
+        AbstractPlugin plugin = createAbstractPluginWithConfig();
+        plugin.setAlertThreshold(AlertThreshold.MEDIUM);
+        plugin.setEnabled(enabled);
+        // When
+        plugin.setDefaultAlertThreshold(defaultThreshold);
+        // Then
+        assertThat(plugin.isEnabled(), is(equalTo(enabled)));
+    }
+
     @Test
     void shouldBeDisabledWhenOffAndDefaultOffThresholdSet() {
         // Given
@@ -239,6 +262,21 @@ class AbstractPluginUnitTest extends PluginTestUtils {
         assertThat(plugin.isEnabled(), is(equalTo(Boolean.TRUE)));
     }
 
+    @ParameterizedTest
+    @EnumSource(
+            value = AlertThreshold.class,
+            names = {"LOW", "MEDIUM", "HIGH"})
+    void shouldBeReEnabledWhenDefaultThresholdSetFromOff(AlertThreshold threshold) {
+        // Given
+        AbstractPlugin plugin = createAbstractPluginWithConfig();
+        plugin.setAlertThreshold(AlertThreshold.DEFAULT);
+        plugin.setDefaultAlertThreshold(AlertThreshold.OFF);
+        // When
+        plugin.setDefaultAlertThreshold(threshold);
+        // Then
+        assertThat(plugin.isEnabled(), is(equalTo(Boolean.TRUE)));
+    }
+
     @Test
     void shouldFailWhenSettingEnabledStateWithoutConfig() {
         // Given