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
16 changes: 8 additions & 8 deletions doc/sphinx/_static/js/get_options.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ function currentPackage(){
function httpGetAsync(targetUrl, callback)
{
var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() {
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
callback(xmlHttp.responseText);
}
xmlHttp.open("GET", targetUrl, true); // true for asynchronous
xmlHttp.open("GET", targetUrl, true); // true for asynchronous
xmlHttp.send(null);
}

Expand All @@ -45,8 +45,8 @@ function hideSelectors(selectors){

function populateOptions(optionSelector, otherSelectors){
if(currentPackage()){
var versionRequestUrl = "https://azuresdkdocs.blob.core.windows.net/$web/" + SELECTED_LANGUAGE + "/" + currentPackage() + "/versioning/versions"
var versionRequestUrl = "https://azuresdkdocs.z19.web.core.windows.net/" + SELECTED_LANGUAGE + "/" + currentPackage() + "/versioning/versions"

httpGetAsync(versionRequestUrl, function(responseText){
if(responseText){
options = responseText.match(/[^\r\n]+/g)
Expand All @@ -68,7 +68,7 @@ function populateOptions(optionSelector, otherSelectors){

function populateVersionDropDown(selector, values){
var select = $(selector)

$('option', select).remove()

$.each(values, function(index, text) {
Expand All @@ -80,17 +80,17 @@ function populateVersionDropDown(selector, values){
select.selectedIndex = 0
}
else {
select.val(version)
select.val(version)
}
}

function getPackageUrl(language, package, version){
return "https://azuresdkdocs.blob.core.windows.net/$web/" + language + "/" + package + "/"+ version + "/index.html"
return "https://azuresdkdocs.z19.web.core.windows.net/" + language + "/" + package + "/"+ version + "/index.html"
}

function populateIndexList(selector, packageName)
{
url = "https://azuresdkdocs.blob.core.windows.net/$web/" + SELECTED_LANGUAGE + "/" + packageName + "/versioning/versions"
url = "https://azuresdkdocs.z19.web.windows.net/" + SELECTED_LANGUAGE + "/" + packageName + "/versioning/versions"

httpGetAsync(url, function (responseText){
if(responseText){
Expand Down
4 changes: 2 additions & 2 deletions doc/sphinx/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@
'trio': ('https://trio.readthedocs.io/en/stable/', None),
'msal': ('https://msal-python.readthedocs.io/en/latest/', None),
# Azure packages
'azure-core': ('https://azuresdkdocs.blob.core.windows.net/$web/python/azure-core/latest/', None),
'azure-identity': ('https://azuresdkdocs.blob.core.windows.net/$web/python/azure-identity/latest/', None),
'azure-core': ('https://azuresdkdocs.z19.web.core.windows.net/python/azure-core/latest/', None),
'azure-identity': ('https://azuresdkdocs.z19.web.core.windows.net/python/azure-identity/latest/', None),
}

autodoc_member_order = 'groupwise'
Expand Down
4 changes: 2 additions & 2 deletions doc/sphinx/individual_build_conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@
'trio': ('https://trio.readthedocs.io/en/stable/', None),
'msal': ('https://msal-python.readthedocs.io/en/latest/', None),
# Azure packages
'azure-core': ('https://azuresdkdocs.blob.core.windows.net/$web/python/azure-core/1.0.0/', None),
'azure-identity': ('https://azuresdkdocs.blob.core.windows.net/$web/python/azure-identity/1.0.0/', None),
'azure-core': ('https://azuresdkdocs.z19.web.core.windows.net/python/azure-core/1.0.0/', None),
'azure-identity': ('https://azuresdkdocs.z19.web.core.windows.net/python/azure-identity/1.0.0/', None),
}

autodoc_member_order = 'groupwise'
Expand Down
6 changes: 6 additions & 0 deletions sdk/cosmos/azure-cosmos/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
## Release History

### 4.9.1 (2025-10-03)

#### Other Changes
* Added an option to override AAD audience scope through environment variable. See [PR 42228](https://github.com/Azure/azure-sdk-for-python/pull/42228).
* Added a fallback mechanism to AAD scope override. See [PR 42731](https://github.com/Azure/azure-sdk-for-python/pull/42731).

### 4.9.0 (2024-11-18)

#### Features Added
Expand Down
40 changes: 35 additions & 5 deletions sdk/cosmos/azure-cosmos/azure/cosmos/_auth_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,32 @@
# Licensed under the MIT License. See LICENSE.txt in the project root for
# license information.
# -------------------------------------------------------------------------
from typing import TypeVar, Any, MutableMapping, cast
from typing import TypeVar, Any, MutableMapping, cast, Optional

from azure.core.pipeline import PipelineRequest
from azure.core.pipeline.policies import BearerTokenCredentialPolicy
from azure.core.pipeline.transport import HttpRequest as LegacyHttpRequest
from azure.core.rest import HttpRequest
from azure.core.credentials import AccessToken
from azure.core.exceptions import HttpResponseError

from .http_constants import HttpHeaders
from ._constants import _Constants as Constants

HTTPRequestType = TypeVar("HTTPRequestType", HttpRequest, LegacyHttpRequest)


# NOTE: This class accesses protected members (_scopes, _token) of the parent class
# to implement fallback and scope-switching logic not exposed by the public API.
# Composition was considered, but still required accessing protected members, so inheritance is retained
# for seamless Azure SDK pipeline integration.
class CosmosBearerTokenCredentialPolicy(BearerTokenCredentialPolicy):
AadDefaultScope = Constants.AAD_DEFAULT_SCOPE

def __init__(self, credential, account_scope: str, override_scope: Optional[str] = None):
self._account_scope = account_scope
self._override_scope = override_scope
self._current_scope = override_scope or account_scope
super().__init__(credential, self._current_scope)

@staticmethod
def _update_headers(headers: MutableMapping[str, str], token: str) -> None:
Expand All @@ -34,9 +46,26 @@ def on_request(self, request: PipelineRequest[HTTPRequestType]) -> None:

:param ~azure.core.pipeline.PipelineRequest request: the request
"""
super().on_request(request)
# The None-check for self._token is done in the parent on_request
self._update_headers(request.http_request.headers, cast(AccessToken, self._token).token)
tried_fallback = False
while True:
try:
super().on_request(request)
# The None-check for self._token is done in the parent on_request
self._update_headers(request.http_request.headers, cast(AccessToken, self._token).token)
break
except HttpResponseError as ex:
# Only fallback if not using override, not already tried, and error is AADSTS500011
if (
not self._override_scope and
not tried_fallback and
self._current_scope != self.AadDefaultScope and
"AADSTS500011" in str(ex)
):
self._scopes = (self.AadDefaultScope,)
self._current_scope = self.AadDefaultScope
tried_fallback = True
continue
raise

def authorize_request(self, request: PipelineRequest[HTTPRequestType], *scopes: str, **kwargs: Any) -> None:
"""Acquire a token from the credential and authorize the request with it.
Expand All @@ -47,6 +76,7 @@ def authorize_request(self, request: PipelineRequest[HTTPRequestType], *scopes:
:param ~azure.core.pipeline.PipelineRequest request: the request
:param str scopes: required scopes of authentication
"""

super().authorize_request(request, *scopes, **kwargs)
# The None-check for self._token is done in the parent authorize_request
self._update_headers(request.http_request.headers, cast(AccessToken, self._token).token)
3 changes: 3 additions & 0 deletions sdk/cosmos/azure-cosmos/azure/cosmos/_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ class _Constants:
# ServiceDocument Resource
EnableMultipleWritableLocations: Literal["enableMultipleWriteLocations"] = "enableMultipleWriteLocations"

AAD_DEFAULT_SCOPE: str = "https://cosmos.azure.com/.default"
AAD_SCOPE_OVERRIDE: str = "AZURE_COSMOS_AAD_SCOPE_OVERRIDE"

# Error code translations
ERROR_TRANSLATIONS: Dict[int, str] = {
400: "BAD_REQUEST - Request being sent is invalid.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class CredentialDict(TypedDict, total=False):
clientSecretCredential: TokenCredential


class CosmosClientConnection: # pylint: disable=too-many-public-methods,too-many-instance-attributes
class CosmosClientConnection: # pylint: disable=too-many-public-methods,too-many-instance-attributes,too-many-statements
"""Represents a document client.

Provides a client-side logical representation of the Azure Cosmos
Expand Down Expand Up @@ -129,7 +129,6 @@ def __init__(
The connection policy for the client.
:param documents.ConsistencyLevel consistency_level:
The default consistency policy for client operations.

"""
self.url_connection = url_connection
self.master_key: Optional[str] = None
Expand Down Expand Up @@ -195,9 +194,13 @@ def __init__(

credentials_policy = None
if self.aad_credentials:
scope = base.create_scope_from_url(self.url_connection)
credentials_policy = CosmosBearerTokenCredentialPolicy(self.aad_credentials, scope)

scope_override = os.environ.get(Constants.AAD_SCOPE_OVERRIDE, "")
account_scope = base.create_scope_from_url(self.url_connection)
credentials_policy = CosmosBearerTokenCredentialPolicy(
self.aad_credentials,
account_scope=account_scope,
override_scope=scope_override if scope_override else None
)
policies = [
HeadersPolicy(**kwargs),
ProxyPolicy(proxies=proxies),
Expand Down
2 changes: 1 addition & 1 deletion sdk/cosmos/azure-cosmos/azure/cosmos/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

VERSION = "4.9.0"
VERSION = "4.9.1"
40 changes: 35 additions & 5 deletions sdk/cosmos/azure-cosmos/azure/cosmos/aio/_auth_policy_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,32 @@
# license information.
# -------------------------------------------------------------------------

from typing import Any, MutableMapping, TypeVar, cast
from typing import Any, MutableMapping, TypeVar, cast, Optional

from azure.core.pipeline.policies import AsyncBearerTokenCredentialPolicy
from azure.core.pipeline import PipelineRequest
from azure.core.pipeline.transport import HttpRequest as LegacyHttpRequest
from azure.core.rest import HttpRequest
from azure.core.credentials import AccessToken
from azure.core.exceptions import HttpResponseError

from ..http_constants import HttpHeaders
from .._constants import _Constants as Constants

HTTPRequestType = TypeVar("HTTPRequestType", HttpRequest, LegacyHttpRequest)


# NOTE: This class accesses protected members (_scopes, _token) of the parent class
# to implement fallback and scope-switching logic not exposed by the public API.
# Composition was considered, but still required accessing protected members, so inheritance is retained
# for seamless Azure SDK pipeline integration.
class AsyncCosmosBearerTokenCredentialPolicy(AsyncBearerTokenCredentialPolicy):
AadDefaultScope = Constants.AAD_DEFAULT_SCOPE

def __init__(self, credential, account_scope: str, override_scope: Optional[str] = None):
self._account_scope = account_scope
self._override_scope = override_scope
self._current_scope = override_scope or account_scope
super().__init__(credential, self._current_scope)

@staticmethod
def _update_headers(headers: MutableMapping[str, str], token: str) -> None:
Expand All @@ -35,9 +47,26 @@ async def on_request(self, request: PipelineRequest[HTTPRequestType]) -> None:
:type request: ~azure.core.pipeline.PipelineRequest
:raises: :class:`~azure.core.exceptions.ServiceRequestError`
"""
await super().on_request(request)
# The None-check for self._token is done in the parent on_request
self._update_headers(request.http_request.headers, cast(AccessToken, self._token).token)
tried_fallback = False
while True:
try:
await super().on_request(request)
# The None-check for self._token is done in the parent on_request
self._update_headers(request.http_request.headers, cast(AccessToken, self._token).token)
break
except HttpResponseError as ex:
# Only fallback if not using override, not already tried, and error is AADSTS500011
if (
not self._override_scope and
not tried_fallback and
self._current_scope != self.AadDefaultScope and
"AADSTS500011" in str(ex)
):
self._scopes = (self.AadDefaultScope,)
self._current_scope = self.AadDefaultScope
tried_fallback = True
continue
raise

async def authorize_request(self, request: PipelineRequest[HTTPRequestType], *scopes: str, **kwargs: Any) -> None:
"""Acquire a token from the credential and authorize the request with it.
Expand All @@ -48,6 +77,7 @@ async def authorize_request(self, request: PipelineRequest[HTTPRequestType], *sc
:param ~azure.core.pipeline.PipelineRequest request: the request
:param str scopes: required scopes of authentication
"""

await super().authorize_request(request, *scopes, **kwargs)
# The None-check for self._token is done in the parent authorize_request
self._update_headers(request.http_request.headers, cast(AccessToken, self._token).token)
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ class CredentialDict(TypedDict, total=False):
clientSecretCredential: AsyncTokenCredential


class CosmosClientConnection: # pylint: disable=too-many-public-methods,too-many-instance-attributes
class CosmosClientConnection: # pylint: disable=too-many-public-methods,too-many-instance-attributes,too-many-statements
"""Represents a document client.

Provides a client-side logical representation of the Azure Cosmos
Expand Down Expand Up @@ -132,7 +132,6 @@ def __init__(
The connection policy for the client.
:param documents.ConsistencyLevel consistency_level:
The default consistency policy for client operations.

"""
self.url_connection = url_connection
self.master_key: Optional[str] = None
Expand Down Expand Up @@ -200,9 +199,13 @@ def __init__(

credentials_policy = None
if self.aad_credentials:
scope = base.create_scope_from_url(self.url_connection)
credentials_policy = AsyncCosmosBearerTokenCredentialPolicy(self.aad_credentials, scope)

scope_override = os.environ.get(Constants.AAD_SCOPE_OVERRIDE, "")
account_scope = base.create_scope_from_url(self.url_connection)
credentials_policy = AsyncCosmosBearerTokenCredentialPolicy(
self.aad_credentials,
account_scope,
scope_override
)
policies = [
HeadersPolicy(**kwargs),
ProxyPolicy(proxies=proxies),
Expand Down
Loading