Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion pymongo/asynchronous/auth_oidc.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def _get_authenticator(
properties = credentials.mechanism_properties

# Validate that the address is allowed.
if not properties.environment:
if properties.human_callback is not None:
found = False
allowed_hosts = properties.allowed_hosts
for patt in allowed_hosts:
Expand Down
8 changes: 5 additions & 3 deletions pymongo/auth_shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ def _validate_canonicalize_host_name(value: str | bool) -> str | bool:
def _build_credentials_tuple(
mech: str,
source: Optional[str],
user: str,
passwd: str,
user: Optional[str],
passwd: Optional[str],
extra: Mapping[str, Any],
database: Optional[str],
) -> MongoCredential:
Expand Down Expand Up @@ -161,6 +161,8 @@ def _build_credentials_tuple(
"::1",
]
allowed_hosts = properties.get("ALLOWED_HOSTS", default_allowed)
if properties.get("ALLOWED_HOSTS", None) is not None and human_callback is None:
raise ConfigurationError("ALLOWED_HOSTS is only valid with OIDC_HUMAN_CALLBACK")
msg = (
"authentication with MONGODB-OIDC requires providing either a callback or a environment"
)
Expand Down Expand Up @@ -207,7 +209,7 @@ def _build_credentials_tuple(
environment=environ,
allowed_hosts=allowed_hosts,
token_resource=token_resource,
username=user,
username=user or "",
)
return MongoCredential(mech, "$external", user, passwd, oidc_props, _Cache())

Expand Down
6 changes: 4 additions & 2 deletions pymongo/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -873,8 +873,10 @@ def get_setter_key(x: str) -> str:
validator = _get_validator(opt, URI_OPTIONS_VALIDATOR_MAP, normed_key=normed_key)
validated = validator(opt, value)
except (ValueError, TypeError, ConfigurationError) as exc:
if normed_key == "authmechanismproperties" and any(
p in str(exc) for p in _MECH_PROP_MUST_RAISE
if (
normed_key == "authmechanismproperties"
and any(p in str(exc) for p in _MECH_PROP_MUST_RAISE)
and "is not a supported auth mechanism property" not in str(exc)
):
raise
if warn:
Expand Down
2 changes: 1 addition & 1 deletion pymongo/synchronous/auth_oidc.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def _get_authenticator(
properties = credentials.mechanism_properties

# Validate that the address is allowed.
if not properties.environment:
if properties.human_callback is not None:
found = False
allowed_hosts = properties.allowed_hosts
for patt in allowed_hosts:
Expand Down
38 changes: 33 additions & 5 deletions test/auth_oidc/test_auth_oidc.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,17 @@
from pymongo._azure_helpers import _get_azure_response
from pymongo._gcp_helpers import _get_gcp_response
from pymongo.auth_oidc_shared import _get_k8s_token
from pymongo.auth_shared import _build_credentials_tuple
from pymongo.cursor_shared import CursorType
from pymongo.errors import AutoReconnect, ConfigurationError, OperationFailure
from pymongo.hello import HelloCompat
from pymongo.operations import InsertOne
from pymongo.synchronous.auth_oidc import OIDCCallback, OIDCCallbackContext, OIDCCallbackResult
from pymongo.synchronous.auth_oidc import (
OIDCCallback,
OIDCCallbackContext,
OIDCCallbackResult,
_get_authenticator,
)
from pymongo.uri_parser import parse_uri

ROOT = Path(__file__).parent.parent.resolve()
Expand Down Expand Up @@ -103,7 +109,6 @@ def fail_point(self, command_args):
client.close()


@pytest.mark.auth_oidc
class TestAuthOIDCHuman(OIDCTestBase):
uri: str

Expand Down Expand Up @@ -838,12 +843,35 @@ def test_2_4_invalid_client_configuration_with_callback(self):
self.create_client(authmechanismproperties=props)

def test_2_5_invalid_use_of_ALLOWED_HOSTS(self):
# Create an OIDC configured client with auth mechanism properties `{"ENVIRONMENT": "azure", "ALLOWED_HOSTS": []}`.
props: Dict = {"ENVIRONMENT": "azure", "ALLOWED_HOSTS": []}
# Create an OIDC configured client with auth mechanism properties `{"ENVIRONMENT": "test", "ALLOWED_HOSTS": []}`.
props: Dict = {"ENVIRONMENT": "test", "ALLOWED_HOSTS": []}
# Assert it returns a client configuration error.
with self.assertRaises(ConfigurationError):
self.create_client(authmechanismproperties=props)

# Create an OIDC configured client with auth mechanism properties `{"OIDC_CALLBACK": "<my_callback>", "ALLOWED_HOSTS": []}`.
props: Dict = {"OIDC_CALLBACK": self.create_request_cb(), "ALLOWED_HOSTS": []}
# Assert it returns a client configuration error.
with self.assertRaises(ConfigurationError):
self.create_client(authmechanismproperties=props)

def test_2_6_ALLOWED_HOSTS_defaults_ignored(self):
# Create a MongoCredential for OIDC with a machine callback.
props = {"OIDC_CALLBACK": self.create_request_cb()}
extra = dict(authmechanismproperties=props)
mongo_creds = _build_credentials_tuple("MONGODB-OIDC", None, "foo", None, extra, "test")
# Assert that creating an authenticator for example.com does not result in an error.
authenticator = _get_authenticator(mongo_creds, ("example.com", 30))
assert authenticator.properties.username == "foo"

# Create a MongoCredential for OIDC with an ENVIRONMENT.
props = {"ENVIRONMENT": "test"}
extra = dict(authmechanismproperties=props)
mongo_creds = _build_credentials_tuple("MONGODB-OIDC", None, None, None, extra, "test")
# Assert that creating an authenticator for example.com does not result in an error.
authenticator = _get_authenticator(mongo_creds, ("example.com", 30))
assert authenticator.properties.username == ""

def test_3_1_authentication_failure_with_cached_tokens_fetch_a_new_token_and_retry(self):
# Create a MongoClient and an OIDC callback that implements the provider logic.
client = self.create_client()
Expand Down Expand Up @@ -909,7 +937,7 @@ def test_3_3_unexpected_error_code_does_not_clear_cache(self):
# Assert that the callback has been called once.
self.assertEqual(self.request_called, 1)

def test_4_1_reauthentication_succeds(self):
def test_4_1_reauthentication_succeeds(self):
# Create a ``MongoClient`` configured with a custom OIDC callback that
# implements the provider logic.
client = self.create_client()
Expand Down
Loading