Skip to content

Commit 6a8a805

Browse files
authored
PYTHON-4845 Ensure ALLOWED_HOSTS is optional for Workload Usage (#1998)
1 parent 41527f0 commit 6a8a805

File tree

5 files changed

+44
-12
lines changed

5 files changed

+44
-12
lines changed

pymongo/asynchronous/auth_oidc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def _get_authenticator(
5555
properties = credentials.mechanism_properties
5656

5757
# Validate that the address is allowed.
58-
if not properties.environment:
58+
if properties.human_callback is not None:
5959
found = False
6060
allowed_hosts = properties.allowed_hosts
6161
for patt in allowed_hosts:

pymongo/auth_shared.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ def _validate_canonicalize_host_name(value: str | bool) -> str | bool:
100100
def _build_credentials_tuple(
101101
mech: str,
102102
source: Optional[str],
103-
user: str,
104-
passwd: str,
103+
user: Optional[str],
104+
passwd: Optional[str],
105105
extra: Mapping[str, Any],
106106
database: Optional[str],
107107
) -> MongoCredential:
@@ -161,6 +161,8 @@ def _build_credentials_tuple(
161161
"::1",
162162
]
163163
allowed_hosts = properties.get("ALLOWED_HOSTS", default_allowed)
164+
if properties.get("ALLOWED_HOSTS", None) is not None and human_callback is None:
165+
raise ConfigurationError("ALLOWED_HOSTS is only valid with OIDC_HUMAN_CALLBACK")
164166
msg = (
165167
"authentication with MONGODB-OIDC requires providing either a callback or a environment"
166168
)
@@ -207,7 +209,7 @@ def _build_credentials_tuple(
207209
environment=environ,
208210
allowed_hosts=allowed_hosts,
209211
token_resource=token_resource,
210-
username=user,
212+
username=user or "",
211213
)
212214
return MongoCredential(mech, "$external", user, passwd, oidc_props, _Cache())
213215

pymongo/common.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -873,8 +873,10 @@ def get_setter_key(x: str) -> str:
873873
validator = _get_validator(opt, URI_OPTIONS_VALIDATOR_MAP, normed_key=normed_key)
874874
validated = validator(opt, value)
875875
except (ValueError, TypeError, ConfigurationError) as exc:
876-
if normed_key == "authmechanismproperties" and any(
877-
p in str(exc) for p in _MECH_PROP_MUST_RAISE
876+
if (
877+
normed_key == "authmechanismproperties"
878+
and any(p in str(exc) for p in _MECH_PROP_MUST_RAISE)
879+
and "is not a supported auth mechanism property" not in str(exc)
878880
):
879881
raise
880882
if warn:

pymongo/synchronous/auth_oidc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def _get_authenticator(
5555
properties = credentials.mechanism_properties
5656

5757
# Validate that the address is allowed.
58-
if not properties.environment:
58+
if properties.human_callback is not None:
5959
found = False
6060
allowed_hosts = properties.allowed_hosts
6161
for patt in allowed_hosts:

test/auth_oidc/test_auth_oidc.py

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,17 @@
3838
from pymongo._azure_helpers import _get_azure_response
3939
from pymongo._gcp_helpers import _get_gcp_response
4040
from pymongo.auth_oidc_shared import _get_k8s_token
41+
from pymongo.auth_shared import _build_credentials_tuple
4142
from pymongo.cursor_shared import CursorType
4243
from pymongo.errors import AutoReconnect, ConfigurationError, OperationFailure
4344
from pymongo.hello import HelloCompat
4445
from pymongo.operations import InsertOne
45-
from pymongo.synchronous.auth_oidc import OIDCCallback, OIDCCallbackContext, OIDCCallbackResult
46+
from pymongo.synchronous.auth_oidc import (
47+
OIDCCallback,
48+
OIDCCallbackContext,
49+
OIDCCallbackResult,
50+
_get_authenticator,
51+
)
4652
from pymongo.uri_parser import parse_uri
4753

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

105111

106-
@pytest.mark.auth_oidc
107112
class TestAuthOIDCHuman(OIDCTestBase):
108113
uri: str
109114

@@ -838,12 +843,35 @@ def test_2_4_invalid_client_configuration_with_callback(self):
838843
self.create_client(authmechanismproperties=props)
839844

840845
def test_2_5_invalid_use_of_ALLOWED_HOSTS(self):
841-
# Create an OIDC configured client with auth mechanism properties `{"ENVIRONMENT": "azure", "ALLOWED_HOSTS": []}`.
842-
props: Dict = {"ENVIRONMENT": "azure", "ALLOWED_HOSTS": []}
846+
# Create an OIDC configured client with auth mechanism properties `{"ENVIRONMENT": "test", "ALLOWED_HOSTS": []}`.
847+
props: Dict = {"ENVIRONMENT": "test", "ALLOWED_HOSTS": []}
843848
# Assert it returns a client configuration error.
844849
with self.assertRaises(ConfigurationError):
845850
self.create_client(authmechanismproperties=props)
846851

852+
# Create an OIDC configured client with auth mechanism properties `{"OIDC_CALLBACK": "<my_callback>", "ALLOWED_HOSTS": []}`.
853+
props: Dict = {"OIDC_CALLBACK": self.create_request_cb(), "ALLOWED_HOSTS": []}
854+
# Assert it returns a client configuration error.
855+
with self.assertRaises(ConfigurationError):
856+
self.create_client(authmechanismproperties=props)
857+
858+
def test_2_6_ALLOWED_HOSTS_defaults_ignored(self):
859+
# Create a MongoCredential for OIDC with a machine callback.
860+
props = {"OIDC_CALLBACK": self.create_request_cb()}
861+
extra = dict(authmechanismproperties=props)
862+
mongo_creds = _build_credentials_tuple("MONGODB-OIDC", None, "foo", None, extra, "test")
863+
# Assert that creating an authenticator for example.com does not result in an error.
864+
authenticator = _get_authenticator(mongo_creds, ("example.com", 30))
865+
assert authenticator.properties.username == "foo"
866+
867+
# Create a MongoCredential for OIDC with an ENVIRONMENT.
868+
props = {"ENVIRONMENT": "test"}
869+
extra = dict(authmechanismproperties=props)
870+
mongo_creds = _build_credentials_tuple("MONGODB-OIDC", None, None, None, extra, "test")
871+
# Assert that creating an authenticator for example.com does not result in an error.
872+
authenticator = _get_authenticator(mongo_creds, ("example.com", 30))
873+
assert authenticator.properties.username == ""
874+
847875
def test_3_1_authentication_failure_with_cached_tokens_fetch_a_new_token_and_retry(self):
848876
# Create a MongoClient and an OIDC callback that implements the provider logic.
849877
client = self.create_client()
@@ -909,7 +937,7 @@ def test_3_3_unexpected_error_code_does_not_clear_cache(self):
909937
# Assert that the callback has been called once.
910938
self.assertEqual(self.request_called, 1)
911939

912-
def test_4_1_reauthentication_succeds(self):
940+
def test_4_1_reauthentication_succeeds(self):
913941
# Create a ``MongoClient`` configured with a custom OIDC callback that
914942
# implements the provider logic.
915943
client = self.create_client()

0 commit comments

Comments
 (0)