Skip to content
Merged
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
17 changes: 15 additions & 2 deletions pymongo/auth_shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def __hash__(self) -> int:


GSSAPIProperties = namedtuple(
"GSSAPIProperties", ["service_name", "canonicalize_host_name", "service_realm"]
"GSSAPIProperties", ["service_name", "canonicalize_host_name", "service_realm", "service_host"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't look like we actually do anything with service_host. Why add it if it's not used?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Argh, I made the changes in the synchronous files and they got wiped out! I'll make a follow up PR tomorrow.

)
"""Mechanism properties for GSSAPI authentication."""

Expand All @@ -87,6 +87,16 @@ def __hash__(self) -> int:
"""Mechanism properties for MONGODB-AWS authentication."""


def _validate_canonicalize_host_name(value: str | bool) -> str | bool:
valid_names = [False, True, "none", "forward", "forwardAndReverse"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So "none", "forward", and "forwardAndReverse" are all supposed to be treated the same as True? Is that accurate?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, you'll see in the follow up PR how they're used in practice.

if value in ["true", "false", True, False]:
return value in ["true", True]

if value not in valid_names:
raise ValueError(f"CANONICALIZE_HOST_NAME '{value}' not in valid options: {valid_names}")
return value


def _build_credentials_tuple(
mech: str,
source: Optional[str],
Expand All @@ -103,12 +113,15 @@ def _build_credentials_tuple(
raise ValueError("authentication source must be $external or None for GSSAPI")
properties = extra.get("authmechanismproperties", {})
service_name = properties.get("SERVICE_NAME", "mongodb")
canonicalize = bool(properties.get("CANONICALIZE_HOST_NAME", False))
service_host = properties.get("SERVICE_HOST", None)
canonicalize = properties.get("CANONICALIZE_HOST_NAME", "false")
canonicalize = _validate_canonicalize_host_name(canonicalize)
service_realm = properties.get("SERVICE_REALM")
props = GSSAPIProperties(
service_name=service_name,
canonicalize_host_name=canonicalize,
service_realm=service_realm,
service_host=service_host,
)
# Source is always $external.
return MongoCredential(mech, "$external", user, passwd, props, None)
Expand Down
12 changes: 11 additions & 1 deletion pymongo/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@
# Default value for serverMonitoringMode
SERVER_MONITORING_MODE = "auto" # poll/stream/auto

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


def partition_node(node: str) -> tuple[str, int]:
"""Split a host:port string into (host, int(port)) pair."""
Expand Down Expand Up @@ -423,6 +426,7 @@ def validate_read_preference_tags(name: str, value: Any) -> list[dict[str, str]]
_MECHANISM_PROPS = frozenset(
[
"SERVICE_NAME",
"SERVICE_HOST",
"CANONICALIZE_HOST_NAME",
"SERVICE_REALM",
"AWS_SESSION_TOKEN",
Expand Down Expand Up @@ -476,7 +480,9 @@ def validate_auth_mechanism_properties(option: str, value: Any) -> dict[str, Uni
)

if key == "CANONICALIZE_HOST_NAME":
props[key] = validate_boolean_or_string(key, val)
from pymongo.auth_shared import _validate_canonicalize_host_name

props[key] = _validate_canonicalize_host_name(val)
else:
props[key] = val

Expand Down Expand Up @@ -867,6 +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
):
raise
if warn:
warnings.warn(str(exc), stacklevel=2)
else:
Expand Down
97 changes: 49 additions & 48 deletions test/auth/legacy/connection-string.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
},
{
"description": "should accept generic mechanism property (GSSAPI)",
"uri": "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:true",
"uri": "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:forward,SERVICE_HOST:example.com",
"valid": true,
"credential": {
"username": "[email protected]",
Expand All @@ -89,10 +89,46 @@
"mechanism": "GSSAPI",
"mechanism_properties": {
"SERVICE_NAME": "other",
"CANONICALIZE_HOST_NAME": true
"SERVICE_HOST": "example.com",
"CANONICALIZE_HOST_NAME": "forward"
}
}
},
{
"description": "should accept forwardAndReverse hostname canonicalization (GSSAPI)",
"uri": "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:forwardAndReverse",
"valid": true,
"credential": {
"username": "[email protected]",
"password": null,
"source": "$external",
"mechanism": "GSSAPI",
"mechanism_properties": {
"SERVICE_NAME": "other",
"CANONICALIZE_HOST_NAME": "forwardAndReverse"
}
}
},
{
"description": "should accept no hostname canonicalization (GSSAPI)",
"uri": "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:none",
"valid": true,
"credential": {
"username": "[email protected]",
"password": null,
"source": "$external",
"mechanism": "GSSAPI",
"mechanism_properties": {
"SERVICE_NAME": "other",
"CANONICALIZE_HOST_NAME": "none"
}
}
},
{
"description": "must raise an error when the hostname canonicalization is invalid",
"uri": "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:invalid",
"valid": false
},
{
"description": "should accept the password (GSSAPI)",
"uri": "mongodb://user%40DOMAIN.COM:password@localhost/?authMechanism=GSSAPI&authSource=$external",
Expand Down Expand Up @@ -127,47 +163,6 @@
"uri": "mongodb://localhost/?authMechanism=GSSAPI",
"valid": false
},
{
"description": "should recognize the mechanism (MONGODB-CR)",
"uri": "mongodb://user:password@localhost/?authMechanism=MONGODB-CR",
"valid": true,
"credential": {
"username": "user",
"password": "password",
"source": "admin",
"mechanism": "MONGODB-CR",
"mechanism_properties": null
}
},
{
"description": "should use the database when no authSource is specified (MONGODB-CR)",
"uri": "mongodb://user:password@localhost/foo?authMechanism=MONGODB-CR",
"valid": true,
"credential": {
"username": "user",
"password": "password",
"source": "foo",
"mechanism": "MONGODB-CR",
"mechanism_properties": null
}
},
{
"description": "should use the authSource when specified (MONGODB-CR)",
"uri": "mongodb://user:password@localhost/foo?authMechanism=MONGODB-CR&authSource=bar",
"valid": true,
"credential": {
"username": "user",
"password": "password",
"source": "bar",
"mechanism": "MONGODB-CR",
"mechanism_properties": null
}
},
{
"description": "should throw an exception if no username is supplied (MONGODB-CR)",
"uri": "mongodb://localhost/?authMechanism=MONGODB-CR",
"valid": false
},
{
"description": "should recognize the mechanism (MONGODB-X509)",
"uri": "mongodb://CN%3DmyName%2COU%3DmyOrgUnit%2CO%3DmyOrg%2CL%3DmyLocality%2CST%3DmyState%2CC%3DmyCountry@localhost/?authMechanism=MONGODB-X509",
Expand Down Expand Up @@ -474,14 +469,14 @@
}
},
{
"description": "should throw an exception if username and password is specified for test environment (MONGODB-OIDC)",
"description": "should throw an exception if supplied a password (MONGODB-OIDC)",
"uri": "mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:test",
"valid": false,
"credential": null
},
{
"description": "should throw an exception if username is specified for test environment (MONGODB-OIDC)",
"uri": "mongodb://principalName@localhost/?authMechanism=MONGODB-OIDC&ENVIRONMENT:test",
"description": "should throw an exception if username is specified for test (MONGODB-OIDC)",
"uri": "mongodb://principalName@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:test",
"valid": false,
"credential": null
},
Expand All @@ -492,11 +487,17 @@
"credential": null
},
{
"description": "should throw an exception if neither provider nor callbacks specified (MONGODB-OIDC)",
"description": "should throw an exception if neither environment nor callbacks specified (MONGODB-OIDC)",
"uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC",
"valid": false,
"credential": null
},
{
"description": "should throw an exception when unsupported auth property is specified (MONGODB-OIDC)",
"uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=UnsupportedProperty:unexisted",
"valid": false,
"credential": null
},
{
"description": "should recognise the mechanism with azure provider (MONGODB-OIDC)",
"uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:foo",
Expand Down Expand Up @@ -627,4 +628,4 @@
"credential": null
}
]
}
}
32 changes: 6 additions & 26 deletions test/connection_string/test/valid-auth.json
Original file line number Diff line number Diff line change
Expand Up @@ -220,29 +220,8 @@
"options": null
},
{
"description": "Escaped user info and database (MONGODB-CR)",
"uri": "mongodb://%24am:f%3Azzb%40z%2Fz%[email protected]/admin%3F?authMechanism=MONGODB-CR",
"valid": true,
"warning": false,
"hosts": [
{
"type": "ipv4",
"host": "127.0.0.1",
"port": null
}
],
"auth": {
"username": "$am",
"password": "f:zzb@z/z=",
"db": "admin?"
},
"options": {
"authmechanism": "MONGODB-CR"
}
},
{
"description": "Subdelimiters in user/pass don't need escaping (MONGODB-CR)",
"uri": "mongodb://!$&'()*+,;=:!$&'()*+,;[email protected]/admin?authMechanism=MONGODB-CR",
"description": "Subdelimiters in user/pass don't need escaping (PLAIN)",
"uri": "mongodb://!$&'()*+,;=:!$&'()*+,;[email protected]/admin?authMechanism=PLAIN",
"valid": true,
"warning": false,
"hosts": [
Expand All @@ -258,7 +237,7 @@
"db": "admin"
},
"options": {
"authmechanism": "MONGODB-CR"
"authmechanism": "PLAIN"
}
},
{
Expand All @@ -284,7 +263,7 @@
},
{
"description": "Escaped username (GSSAPI)",
"uri": "mongodb://user%40EXAMPLE.COM:secret@localhost/?authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:true&authMechanism=GSSAPI",
"uri": "mongodb://user%40EXAMPLE.COM:secret@localhost/?authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:forward,SERVICE_HOST:example.com&authMechanism=GSSAPI",
"valid": true,
"warning": false,
"hosts": [
Expand All @@ -303,7 +282,8 @@
"authmechanism": "GSSAPI",
"authmechanismproperties": {
"SERVICE_NAME": "other",
"CANONICALIZE_HOST_NAME": true
"SERVICE_HOST": "example.com",
"CANONICALIZE_HOST_NAME": "forward"
}
}
},
Expand Down
4 changes: 2 additions & 2 deletions test/connection_string/test/valid-options.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"tests": [
{
"description": "Option names are normalized to lowercase",
"uri": "mongodb://alice:[email protected]/admin?AUTHMechanism=MONGODB-CR",
"uri": "mongodb://alice:[email protected]/admin?AUTHMechanism=PLAIN",
"valid": true,
"warning": false,
"hosts": [
Expand All @@ -18,7 +18,7 @@
"db": "admin"
},
"options": {
"authmechanism": "MONGODB-CR"
"authmechanism": "PLAIN"
}
},
{
Expand Down
Loading