Skip to content

Commit 92d6a73

Browse files
authored
PYTHON-3906 & PYTHON-2867 Implement GSSAPI ServiceHost support and expand canonicalization options (mongodb#1983)
1 parent ad3292e commit 92d6a73

File tree

4 files changed

+78
-12
lines changed

4 files changed

+78
-12
lines changed

pymongo/auth_shared.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def __hash__(self) -> int:
7777

7878

7979
GSSAPIProperties = namedtuple(
80-
"GSSAPIProperties", ["service_name", "canonicalize_host_name", "service_realm"]
80+
"GSSAPIProperties", ["service_name", "canonicalize_host_name", "service_realm", "service_host"]
8181
)
8282
"""Mechanism properties for GSSAPI authentication."""
8383

@@ -86,6 +86,16 @@ def __hash__(self) -> int:
8686
"""Mechanism properties for MONGODB-AWS authentication."""
8787

8888

89+
def _validate_canonicalize_host_name(value: str | bool) -> str | bool:
90+
valid_names = [False, True, "none", "forward", "forwardAndReverse"]
91+
if value in ["true", "false", True, False]:
92+
return value in ["true", True]
93+
94+
if value not in valid_names:
95+
raise ValueError(f"CANONICALIZE_HOST_NAME '{value}' not in valid options: {valid_names}")
96+
return value
97+
98+
8999
def _build_credentials_tuple(
90100
mech: str,
91101
source: Optional[str],
@@ -102,12 +112,15 @@ def _build_credentials_tuple(
102112
raise ValueError("authentication source must be $external or None for GSSAPI")
103113
properties = extra.get("authmechanismproperties", {})
104114
service_name = properties.get("SERVICE_NAME", "mongodb")
105-
canonicalize = bool(properties.get("CANONICALIZE_HOST_NAME", False))
115+
service_host = properties.get("SERVICE_HOST", None)
116+
canonicalize = properties.get("CANONICALIZE_HOST_NAME", "false")
117+
canonicalize = _validate_canonicalize_host_name(canonicalize)
106118
service_realm = properties.get("SERVICE_REALM")
107119
props = GSSAPIProperties(
108120
service_name=service_name,
109121
canonicalize_host_name=canonicalize,
110122
service_realm=service_realm,
123+
service_host=service_host,
111124
)
112125
# Source is always $external.
113126
return MongoCredential(mech, "$external", user, passwd, props, None)

pymongo/common.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@
139139
# Default value for serverMonitoringMode
140140
SERVER_MONITORING_MODE = "auto" # poll/stream/auto
141141

142+
# Auth mechanism properties that must raise an error instead of warning if they invalidate.
143+
_MECH_PROP_MUST_RAISE = ["CANONICALIZE_HOST_NAME"]
144+
142145

143146
def partition_node(node: str) -> tuple[str, int]:
144147
"""Split a host:port string into (host, int(port)) pair."""
@@ -423,6 +426,7 @@ def validate_read_preference_tags(name: str, value: Any) -> list[dict[str, str]]
423426
_MECHANISM_PROPS = frozenset(
424427
[
425428
"SERVICE_NAME",
429+
"SERVICE_HOST",
426430
"CANONICALIZE_HOST_NAME",
427431
"SERVICE_REALM",
428432
"AWS_SESSION_TOKEN",
@@ -476,7 +480,9 @@ def validate_auth_mechanism_properties(option: str, value: Any) -> dict[str, Uni
476480
)
477481

478482
if key == "CANONICALIZE_HOST_NAME":
479-
props[key] = validate_boolean_or_string(key, val)
483+
from pymongo.auth_shared import _validate_canonicalize_host_name
484+
485+
props[key] = _validate_canonicalize_host_name(val)
480486
else:
481487
props[key] = val
482488

@@ -867,6 +873,10 @@ def get_setter_key(x: str) -> str:
867873
validator = _get_validator(opt, URI_OPTIONS_VALIDATOR_MAP, normed_key=normed_key)
868874
validated = validator(opt, value)
869875
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
878+
):
879+
raise
870880
if warn:
871881
warnings.warn(str(exc), stacklevel=2)
872882
else:

test/auth/legacy/connection-string.json

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080
},
8181
{
8282
"description": "should accept generic mechanism property (GSSAPI)",
83-
"uri": "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:true",
83+
"uri": "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:forward,SERVICE_HOST:example.com",
8484
"valid": true,
8585
"credential": {
8686
"username": "[email protected]",
@@ -89,10 +89,46 @@
8989
"mechanism": "GSSAPI",
9090
"mechanism_properties": {
9191
"SERVICE_NAME": "other",
92-
"CANONICALIZE_HOST_NAME": true
92+
"SERVICE_HOST": "example.com",
93+
"CANONICALIZE_HOST_NAME": "forward"
9394
}
9495
}
9596
},
97+
{
98+
"description": "should accept forwardAndReverse hostname canonicalization (GSSAPI)",
99+
"uri": "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:forwardAndReverse",
100+
"valid": true,
101+
"credential": {
102+
"username": "[email protected]",
103+
"password": null,
104+
"source": "$external",
105+
"mechanism": "GSSAPI",
106+
"mechanism_properties": {
107+
"SERVICE_NAME": "other",
108+
"CANONICALIZE_HOST_NAME": "forwardAndReverse"
109+
}
110+
}
111+
},
112+
{
113+
"description": "should accept no hostname canonicalization (GSSAPI)",
114+
"uri": "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:none",
115+
"valid": true,
116+
"credential": {
117+
"username": "[email protected]",
118+
"password": null,
119+
"source": "$external",
120+
"mechanism": "GSSAPI",
121+
"mechanism_properties": {
122+
"SERVICE_NAME": "other",
123+
"CANONICALIZE_HOST_NAME": "none"
124+
}
125+
}
126+
},
127+
{
128+
"description": "must raise an error when the hostname canonicalization is invalid",
129+
"uri": "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:invalid",
130+
"valid": false
131+
},
96132
{
97133
"description": "should accept the password (GSSAPI)",
98134
"uri": "mongodb://user%40DOMAIN.COM:password@localhost/?authMechanism=GSSAPI&authSource=$external",
@@ -433,14 +469,14 @@
433469
}
434470
},
435471
{
436-
"description": "should throw an exception if username and password is specified for test environment (MONGODB-OIDC)",
472+
"description": "should throw an exception if supplied a password (MONGODB-OIDC)",
437473
"uri": "mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:test",
438474
"valid": false,
439475
"credential": null
440476
},
441477
{
442-
"description": "should throw an exception if username is specified for test environment (MONGODB-OIDC)",
443-
"uri": "mongodb://principalName@localhost/?authMechanism=MONGODB-OIDC&ENVIRONMENT:test",
478+
"description": "should throw an exception if username is specified for test (MONGODB-OIDC)",
479+
"uri": "mongodb://principalName@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:test",
444480
"valid": false,
445481
"credential": null
446482
},
@@ -451,11 +487,17 @@
451487
"credential": null
452488
},
453489
{
454-
"description": "should throw an exception if neither provider nor callbacks specified (MONGODB-OIDC)",
490+
"description": "should throw an exception if neither environment nor callbacks specified (MONGODB-OIDC)",
455491
"uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC",
456492
"valid": false,
457493
"credential": null
458494
},
495+
{
496+
"description": "should throw an exception when unsupported auth property is specified (MONGODB-OIDC)",
497+
"uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=UnsupportedProperty:unexisted",
498+
"valid": false,
499+
"credential": null
500+
},
459501
{
460502
"description": "should recognise the mechanism with azure provider (MONGODB-OIDC)",
461503
"uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:foo",
@@ -586,4 +628,4 @@
586628
"credential": null
587629
}
588630
]
589-
}
631+
}

test/connection_string/test/valid-auth.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@
263263
},
264264
{
265265
"description": "Escaped username (GSSAPI)",
266-
"uri": "mongodb://user%40EXAMPLE.COM:secret@localhost/?authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:true&authMechanism=GSSAPI",
266+
"uri": "mongodb://user%40EXAMPLE.COM:secret@localhost/?authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:forward,SERVICE_HOST:example.com&authMechanism=GSSAPI",
267267
"valid": true,
268268
"warning": false,
269269
"hosts": [
@@ -282,7 +282,8 @@
282282
"authmechanism": "GSSAPI",
283283
"authmechanismproperties": {
284284
"SERVICE_NAME": "other",
285-
"CANONICALIZE_HOST_NAME": true
285+
"SERVICE_HOST": "example.com",
286+
"CANONICALIZE_HOST_NAME": "forward"
286287
}
287288
}
288289
},

0 commit comments

Comments
 (0)