Skip to content

Commit 134d00a

Browse files
committed
PYTHON-4747 Sync auth.py to master
1 parent 8b5479c commit 134d00a

File tree

2 files changed

+35
-217
lines changed

2 files changed

+35
-217
lines changed

pymongo/auth.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Copyright 2024-present MongoDB, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Re-import of synchronous Auth API for compatibility."""
16+
from __future__ import annotations
17+
18+
from pymongo.auth_shared import * # noqa: F403
19+
from pymongo.synchronous.auth import * # noqa: F403
20+
from pymongo.synchronous.auth import __doc__ as original_doc
21+
22+
__doc__ = original_doc

pymongo/synchronous/auth.py

Lines changed: 13 additions & 217 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,12 @@
1818
import functools
1919
import hashlib
2020
import hmac
21-
import os
2221
import socket
23-
import typing
2422
from base64 import standard_b64decode, standard_b64encode
25-
from collections import namedtuple
2623
from typing import (
2724
TYPE_CHECKING,
2825
Any,
2926
Callable,
30-
Dict,
3127
Mapping,
3228
MutableMapping,
3329
Optional,
@@ -36,21 +32,23 @@
3632
from urllib.parse import quote
3733

3834
from bson.binary import Binary
39-
from pymongo.auth_aws import _authenticate_aws
40-
from pymongo.auth_oidc import (
41-
_authenticate_oidc,
42-
_get_authenticator,
43-
_OIDCAzureCallback,
44-
_OIDCGCPCallback,
45-
_OIDCProperties,
46-
_OIDCTestCallback,
35+
from pymongo.auth_shared import (
36+
MongoCredential,
37+
_authenticate_scram_start,
38+
_parse_scram_response,
39+
_xor,
4740
)
4841
from pymongo.errors import ConfigurationError, OperationFailure
4942
from pymongo.saslprep import saslprep
43+
from pymongo.synchronous.auth_aws import _authenticate_aws
44+
from pymongo.synchronous.auth_oidc import (
45+
_authenticate_oidc,
46+
_get_authenticator,
47+
)
5048

5149
if TYPE_CHECKING:
5250
from pymongo.hello import Hello
53-
from pymongo.pool import Connection
51+
from pymongo.synchronous.pool import Connection
5452

5553
HAVE_KERBEROS = True
5654
_USE_PRINCIPAL = False
@@ -66,209 +64,7 @@
6664
HAVE_KERBEROS = False
6765

6866

69-
MECHANISMS = frozenset(
70-
[
71-
"GSSAPI",
72-
"MONGODB-CR",
73-
"MONGODB-OIDC",
74-
"MONGODB-X509",
75-
"MONGODB-AWS",
76-
"PLAIN",
77-
"SCRAM-SHA-1",
78-
"SCRAM-SHA-256",
79-
"DEFAULT",
80-
]
81-
)
82-
"""The authentication mechanisms supported by PyMongo."""
83-
84-
85-
class _Cache:
86-
__slots__ = ("data",)
87-
88-
_hash_val = hash("_Cache")
89-
90-
def __init__(self) -> None:
91-
self.data = None
92-
93-
def __eq__(self, other: object) -> bool:
94-
# Two instances must always compare equal.
95-
if isinstance(other, _Cache):
96-
return True
97-
return NotImplemented
98-
99-
def __ne__(self, other: object) -> bool:
100-
if isinstance(other, _Cache):
101-
return False
102-
return NotImplemented
103-
104-
def __hash__(self) -> int:
105-
return self._hash_val
106-
107-
108-
MongoCredential = namedtuple(
109-
"MongoCredential",
110-
["mechanism", "source", "username", "password", "mechanism_properties", "cache"],
111-
)
112-
"""A hashable namedtuple of values used for authentication."""
113-
114-
115-
GSSAPIProperties = namedtuple(
116-
"GSSAPIProperties", ["service_name", "canonicalize_host_name", "service_realm"]
117-
)
118-
"""Mechanism properties for GSSAPI authentication."""
119-
120-
121-
_AWSProperties = namedtuple("_AWSProperties", ["aws_session_token"])
122-
"""Mechanism properties for MONGODB-AWS authentication."""
123-
124-
125-
def _build_credentials_tuple(
126-
mech: str,
127-
source: Optional[str],
128-
user: str,
129-
passwd: str,
130-
extra: Mapping[str, Any],
131-
database: Optional[str],
132-
) -> MongoCredential:
133-
"""Build and return a mechanism specific credentials tuple."""
134-
if mech not in ("MONGODB-X509", "MONGODB-AWS", "MONGODB-OIDC") and user is None:
135-
raise ConfigurationError(f"{mech} requires a username.")
136-
if mech == "GSSAPI":
137-
if source is not None and source != "$external":
138-
raise ValueError("authentication source must be $external or None for GSSAPI")
139-
properties = extra.get("authmechanismproperties", {})
140-
service_name = properties.get("SERVICE_NAME", "mongodb")
141-
canonicalize = bool(properties.get("CANONICALIZE_HOST_NAME", False))
142-
service_realm = properties.get("SERVICE_REALM")
143-
props = GSSAPIProperties(
144-
service_name=service_name,
145-
canonicalize_host_name=canonicalize,
146-
service_realm=service_realm,
147-
)
148-
# Source is always $external.
149-
return MongoCredential(mech, "$external", user, passwd, props, None)
150-
elif mech == "MONGODB-X509":
151-
if passwd is not None:
152-
raise ConfigurationError("Passwords are not supported by MONGODB-X509")
153-
if source is not None and source != "$external":
154-
raise ValueError("authentication source must be $external or None for MONGODB-X509")
155-
# Source is always $external, user can be None.
156-
return MongoCredential(mech, "$external", user, None, None, None)
157-
elif mech == "MONGODB-AWS":
158-
if user is not None and passwd is None:
159-
raise ConfigurationError("username without a password is not supported by MONGODB-AWS")
160-
if source is not None and source != "$external":
161-
raise ConfigurationError(
162-
"authentication source must be $external or None for MONGODB-AWS"
163-
)
164-
165-
properties = extra.get("authmechanismproperties", {})
166-
aws_session_token = properties.get("AWS_SESSION_TOKEN")
167-
aws_props = _AWSProperties(aws_session_token=aws_session_token)
168-
# user can be None for temporary link-local EC2 credentials.
169-
return MongoCredential(mech, "$external", user, passwd, aws_props, None)
170-
elif mech == "MONGODB-OIDC":
171-
properties = extra.get("authmechanismproperties", {})
172-
callback = properties.get("OIDC_CALLBACK")
173-
human_callback = properties.get("OIDC_HUMAN_CALLBACK")
174-
environ = properties.get("ENVIRONMENT")
175-
token_resource = properties.get("TOKEN_RESOURCE", "")
176-
default_allowed = [
177-
"*.mongodb.net",
178-
"*.mongodb-dev.net",
179-
"*.mongodb-qa.net",
180-
"*.mongodbgov.net",
181-
"localhost",
182-
"127.0.0.1",
183-
"::1",
184-
]
185-
allowed_hosts = properties.get("ALLOWED_HOSTS", default_allowed)
186-
msg = (
187-
"authentication with MONGODB-OIDC requires providing either a callback or a environment"
188-
)
189-
if passwd is not None:
190-
msg = "password is not supported by MONGODB-OIDC"
191-
raise ConfigurationError(msg)
192-
if callback or human_callback:
193-
if environ is not None:
194-
raise ConfigurationError(msg)
195-
if callback and human_callback:
196-
msg = "cannot set both OIDC_CALLBACK and OIDC_HUMAN_CALLBACK"
197-
raise ConfigurationError(msg)
198-
elif environ is not None:
199-
if environ == "test":
200-
if user is not None:
201-
msg = "test environment for MONGODB-OIDC does not support username"
202-
raise ConfigurationError(msg)
203-
callback = _OIDCTestCallback()
204-
elif environ == "azure":
205-
passwd = None
206-
if not token_resource:
207-
raise ConfigurationError(
208-
"Azure environment for MONGODB-OIDC requires a TOKEN_RESOURCE auth mechanism property"
209-
)
210-
callback = _OIDCAzureCallback(token_resource)
211-
elif environ == "gcp":
212-
passwd = None
213-
if not token_resource:
214-
raise ConfigurationError(
215-
"GCP provider for MONGODB-OIDC requires a TOKEN_RESOURCE auth mechanism property"
216-
)
217-
callback = _OIDCGCPCallback(token_resource)
218-
else:
219-
raise ConfigurationError(f"unrecognized ENVIRONMENT for MONGODB-OIDC: {environ}")
220-
else:
221-
raise ConfigurationError(msg)
222-
223-
oidc_props = _OIDCProperties(
224-
callback=callback,
225-
human_callback=human_callback,
226-
environment=environ,
227-
allowed_hosts=allowed_hosts,
228-
token_resource=token_resource,
229-
username=user,
230-
)
231-
return MongoCredential(mech, "$external", user, passwd, oidc_props, _Cache())
232-
233-
elif mech == "PLAIN":
234-
source_database = source or database or "$external"
235-
return MongoCredential(mech, source_database, user, passwd, None, None)
236-
else:
237-
source_database = source or database or "admin"
238-
if passwd is None:
239-
raise ConfigurationError("A password is required.")
240-
return MongoCredential(mech, source_database, user, passwd, None, _Cache())
241-
242-
243-
def _xor(fir: bytes, sec: bytes) -> bytes:
244-
"""XOR two byte strings together."""
245-
return b"".join([bytes([x ^ y]) for x, y in zip(fir, sec)])
246-
247-
248-
def _parse_scram_response(response: bytes) -> Dict[bytes, bytes]:
249-
"""Split a scram response into key, value pairs."""
250-
return dict(
251-
typing.cast(typing.Tuple[bytes, bytes], item.split(b"=", 1))
252-
for item in response.split(b",")
253-
)
254-
255-
256-
def _authenticate_scram_start(
257-
credentials: MongoCredential, mechanism: str
258-
) -> tuple[bytes, bytes, MutableMapping[str, Any]]:
259-
username = credentials.username
260-
user = username.encode("utf-8").replace(b"=", b"=3D").replace(b",", b"=2C")
261-
nonce = standard_b64encode(os.urandom(32))
262-
first_bare = b"n=" + user + b",r=" + nonce
263-
264-
cmd = {
265-
"saslStart": 1,
266-
"mechanism": mechanism,
267-
"payload": Binary(b"n,," + first_bare),
268-
"autoAuthorize": 1,
269-
"options": {"skipEmptyExchange": True},
270-
}
271-
return nonce, first_bare, cmd
67+
_IS_SYNC = True
27268

27369

27470
def _authenticate_scram(credentials: MongoCredential, conn: Connection, mechanism: str) -> None:
@@ -553,7 +349,7 @@ def _authenticate_default(credentials: MongoCredential, conn: Connection) -> Non
553349
source = credentials.source
554350
cmd = conn.hello_cmd()
555351
cmd["saslSupportedMechs"] = source + "." + credentials.username
556-
mechs = conn.command(source, cmd, publish_events=False).get("saslSupportedMechs", [])
352+
mechs = (conn.command(source, cmd, publish_events=False)).get("saslSupportedMechs", [])
557353
if "SCRAM-SHA-256" in mechs:
558354
return _authenticate_scram(credentials, conn, "SCRAM-SHA-256")
559355
else:

0 commit comments

Comments
 (0)