Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions gvm/protocols/gmp/_gmpnext.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
71 changes: 71 additions & 0 deletions gvm/protocols/gmp/requests/next/_credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions tests/protocols/gmpnext/entities/credentials/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
)
Original file line number Diff line number Diff line change
@@ -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'<verify_active_directory_credential credential_id="credential-id"/>'
)

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"<verify_active_directory_credential>"
b"<host>dc.example.org</host>"
b"<bind_dn>CN=Scanner,DC=example,DC=org</bind_dn>"
b"<bind_password>secret</bind_password>"
b"<base_dn>DC=example,DC=org</base_dn>"
b"<tls_mode>ldaps</tls_mode>"
b"</verify_active_directory_credential>"
)

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"<verify_active_directory_credential>"
b"<host>dc.example.org</host>"
b"<port>389</port>"
b"<bind_dn>CN=Scanner,DC=example,DC=org</bind_dn>"
b"<bind_password>secret&lt;&amp;</bind_password>"
b"<base_dn>DC=example,DC=org</base_dn>"
b"<tls_mode>starttls</tls_mode>"
b"<ca_certificate>certificate&lt;&amp;</ca_certificate>"
b"</verify_active_directory_credential>"
)

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,
)
7 changes: 7 additions & 0 deletions tests/protocols/gmpnext/entities/test_credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
GmpCreateCredentialStoreCredentialTestMixin,
GmpModifyActiveDirectoryCredentialTestMixin,
GmpModifyCredentialStoreCredentialTestMixin,
GmpVerifyActiveDirectoryCredentialTestMixin,
)


Expand Down Expand Up @@ -52,3 +53,9 @@ class GMPModifyCredentialTestCase(
GMPTestCase,
):
pass


class GMPVerifyActiveDirectoryCredentialTestCase(
GmpVerifyActiveDirectoryCredentialTestMixin, GMPTestCase
):
pass
Loading