diff --git a/gvm/protocols/gmp/_gmpnext.py b/gvm/protocols/gmp/_gmpnext.py
index 4b3e4894..7e21c966 100644
--- a/gvm/protocols/gmp/_gmpnext.py
+++ b/gvm/protocols/gmp/_gmpnext.py
@@ -449,6 +449,46 @@ def modify_active_directory_credential(
)
)
+ def verify_active_directory_credential(
+ self,
+ *,
+ credential_id: EntityID | None = None,
+ host: str | None = None,
+ bind_dn: str | None = None,
+ bind_password: str | None = None,
+ base_dn: str | None = None,
+ tls_mode: str | None = None,
+ port: int | None = None,
+ ca_certificate: str | None = None,
+ ) -> T:
+ """Verify a saved AD credential or supplied AD connection settings.
+
+ Requires AD integration to be enabled in gvmd. Provide either
+ credential_id alone or the required inline connection fields.
+
+ Args:
+ credential_id: UUID of a saved AD credential.
+ host: AD server hostname or IP address.
+ bind_dn: Distinguished name used to bind to the directory.
+ bind_password: Password used for the bind.
+ base_dn: Base distinguished name for directory searches.
+ tls_mode: Either "ldaps" or "starttls".
+ port: Optional server port, from 1 to 65535.
+ ca_certificate: Optional CA certificate in PEM format.
+ """
+ return self._send_request_and_transform_response(
+ Credentials.verify_active_directory_credential(
+ credential_id=credential_id,
+ host=host,
+ bind_dn=bind_dn,
+ bind_password=bind_password,
+ base_dn=base_dn,
+ tls_mode=tls_mode,
+ port=port,
+ ca_certificate=ca_certificate,
+ )
+ )
+
def create_credential_store_credential(
self,
name: str,
diff --git a/gvm/protocols/gmp/requests/next/_credentials.py b/gvm/protocols/gmp/requests/next/_credentials.py
index 17192161..a24bf5d1 100644
--- a/gvm/protocols/gmp/requests/next/_credentials.py
+++ b/gvm/protocols/gmp/requests/next/_credentials.py
@@ -249,6 +249,77 @@ def modify_active_directory_credential(
cmd.add_element("ca_certificate", str(ca_certificate))
return cmd
+ @classmethod
+ def verify_active_directory_credential(
+ cls,
+ *,
+ credential_id: EntityID | None = None,
+ host: str | None = None,
+ bind_dn: str | None = None,
+ bind_password: str | None = None,
+ base_dn: str | None = None,
+ tls_mode: str | None = None,
+ port: int | None = None,
+ ca_certificate: str | None = None,
+ ) -> Request:
+ """Verify a saved AD credential or supplied AD connection settings."""
+ function = cls.verify_active_directory_credential.__name__
+
+ has_inline_fields = (
+ host is not None
+ or bind_dn is not None
+ or bind_password is not None
+ or base_dn is not None
+ or tls_mode is not None
+ or port is not None
+ or ca_certificate is not None
+ )
+
+ if credential_id is not None:
+ if not credential_id:
+ raise RequiredArgument(
+ function=function, argument="credential_id"
+ )
+ if has_inline_fields:
+ raise InvalidArgument(
+ function=function, argument="credential_id"
+ )
+
+ cmd = XmlCommand("verify_active_directory_credential")
+ cmd.set_attribute("credential_id", str(credential_id))
+ return cmd
+
+ if not host:
+ raise RequiredArgument(function=function, argument="host")
+ if not bind_dn:
+ raise RequiredArgument(function=function, argument="bind_dn")
+ if not bind_password:
+ raise RequiredArgument(function=function, argument="bind_password")
+ if not base_dn:
+ raise RequiredArgument(function=function, argument="base_dn")
+ if not tls_mode:
+ raise RequiredArgument(function=function, argument="tls_mode")
+
+ if tls_mode not in ("ldaps", "starttls"):
+ raise InvalidArgument(function=function, argument="tls_mode")
+
+ if port is not None and (
+ not isinstance(port, int) or not 1 <= port <= 65535
+ ):
+ raise InvalidArgument(function=function, argument="port")
+
+ cmd = XmlCommand("verify_active_directory_credential")
+ cmd.add_element("host", host)
+ if port is not None:
+ cmd.add_element("port", str(port))
+ cmd.add_element("bind_dn", bind_dn)
+ cmd.add_element("bind_password", bind_password)
+ cmd.add_element("base_dn", base_dn)
+ cmd.add_element("tls_mode", tls_mode)
+ if ca_certificate is not None:
+ cmd.add_element("ca_certificate", ca_certificate)
+ return cmd
+
@classmethod
def create_credential_store_credential(
cls,
diff --git a/tests/protocols/gmpnext/entities/credentials/__init__.py b/tests/protocols/gmpnext/entities/credentials/__init__.py
index e1425d24..a57a05ae 100644
--- a/tests/protocols/gmpnext/entities/credentials/__init__.py
+++ b/tests/protocols/gmpnext/entities/credentials/__init__.py
@@ -14,10 +14,14 @@
from .test_modify_credential_store_credential import (
GmpModifyCredentialStoreCredentialTestMixin,
)
+from .test_verify_active_directory_credential import (
+ GmpVerifyActiveDirectoryCredentialTestMixin,
+)
__all__ = (
"GmpCreateActiveDirectoryCredentialTestMixin",
"GmpCreateCredentialStoreCredentialTestMixin",
"GmpModifyActiveDirectoryCredentialTestMixin",
"GmpModifyCredentialStoreCredentialTestMixin",
+ "GmpVerifyActiveDirectoryCredentialTestMixin",
)
diff --git a/tests/protocols/gmpnext/entities/credentials/test_verify_active_directory_credential.py b/tests/protocols/gmpnext/entities/credentials/test_verify_active_directory_credential.py
new file mode 100644
index 00000000..bfeed659
--- /dev/null
+++ b/tests/protocols/gmpnext/entities/credentials/test_verify_active_directory_credential.py
@@ -0,0 +1,112 @@
+# SPDX-FileCopyrightText: 2026 Greenbone AG
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+
+from gvm.errors import InvalidArgument, RequiredArgument
+
+
+class GmpVerifyActiveDirectoryCredentialTestMixin:
+ def test_verify_saved_ad_credential(self):
+ self.gmp.verify_active_directory_credential(
+ credential_id="credential-id"
+ )
+
+ self.connection.send.has_been_called_with(
+ b''
+ )
+
+ def test_verify_inline_ad_credential(self):
+ self.gmp.verify_active_directory_credential(
+ host="dc.example.org",
+ bind_dn="CN=Scanner,DC=example,DC=org",
+ bind_password="secret",
+ base_dn="DC=example,DC=org",
+ tls_mode="ldaps",
+ )
+
+ self.connection.send.has_been_called_with(
+ b""
+ b"dc.example.org"
+ b"CN=Scanner,DC=example,DC=org"
+ b"secret"
+ b"DC=example,DC=org"
+ b"ldaps"
+ b""
+ )
+
+ def test_verify_inline_ad_credential_with_optional_fields(self):
+ self.gmp.verify_active_directory_credential(
+ host="dc.example.org",
+ bind_dn="CN=Scanner,DC=example,DC=org",
+ bind_password="secret<&",
+ base_dn="DC=example,DC=org",
+ tls_mode="starttls",
+ port=389,
+ ca_certificate="certificate<&",
+ )
+
+ self.connection.send.has_been_called_with(
+ b""
+ b"dc.example.org"
+ b"389"
+ b"CN=Scanner,DC=example,DC=org"
+ b"secret<&"
+ b"DC=example,DC=org"
+ b"starttls"
+ b"certificate<&"
+ b""
+ )
+
+ def test_verify_without_saved_id_or_inline_fields(self):
+ with self.assertRaises(RequiredArgument):
+ self.gmp.verify_active_directory_credential()
+
+ with self.assertRaises(RequiredArgument):
+ self.gmp.verify_active_directory_credential(credential_id="")
+
+ def test_verify_saved_id_with_inline_fields(self):
+ for field in ("host", "port", "ca_certificate"):
+ with self.subTest(field=field):
+ with self.assertRaises(InvalidArgument):
+ self.gmp.verify_active_directory_credential(
+ credential_id="credential-id", **{field: "value"}
+ )
+
+ def test_verify_inline_ad_credential_missing_required_field(self):
+ fields = {
+ "host": "dc.example.org",
+ "bind_dn": "CN=Scanner,DC=example,DC=org",
+ "bind_password": "secret",
+ "base_dn": "DC=example,DC=org",
+ "tls_mode": "ldaps",
+ }
+ for field in fields:
+ with self.subTest(field=field):
+ with self.assertRaises(RequiredArgument):
+ self.gmp.verify_active_directory_credential(
+ **{**fields, field: ""}
+ )
+
+ def test_verify_inline_ad_credential_invalid_tls_mode(self):
+ with self.assertRaises(InvalidArgument):
+ self.gmp.verify_active_directory_credential(
+ host="dc.example.org",
+ bind_dn="CN=Scanner,DC=example,DC=org",
+ bind_password="secret",
+ base_dn="DC=example,DC=org",
+ tls_mode="ldap",
+ )
+
+ def test_verify_inline_ad_credential_invalid_port(self):
+ for port in (0, 65536, "389"):
+ with self.subTest(port=port):
+ with self.assertRaises(InvalidArgument):
+ self.gmp.verify_active_directory_credential(
+ host="dc.example.org",
+ bind_dn="CN=Scanner,DC=example,DC=org",
+ bind_password="secret",
+ base_dn="DC=example,DC=org",
+ tls_mode="ldaps",
+ port=port,
+ )
diff --git a/tests/protocols/gmpnext/entities/test_credentials.py b/tests/protocols/gmpnext/entities/test_credentials.py
index 27724bee..f952f5a3 100644
--- a/tests/protocols/gmpnext/entities/test_credentials.py
+++ b/tests/protocols/gmpnext/entities/test_credentials.py
@@ -17,6 +17,7 @@
GmpCreateCredentialStoreCredentialTestMixin,
GmpModifyActiveDirectoryCredentialTestMixin,
GmpModifyCredentialStoreCredentialTestMixin,
+ GmpVerifyActiveDirectoryCredentialTestMixin,
)
@@ -52,3 +53,9 @@ class GMPModifyCredentialTestCase(
GMPTestCase,
):
pass
+
+
+class GMPVerifyActiveDirectoryCredentialTestCase(
+ GmpVerifyActiveDirectoryCredentialTestMixin, GMPTestCase
+):
+ pass