Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Service Bus Named Key Credential #18471

Merged
merged 8 commits into from
May 4, 2021
Merged
Show file tree
Hide file tree
Changes from 6 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
5 changes: 5 additions & 0 deletions sdk/servicebus/azure-servicebus/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@ The preview features related to AMQPAnnotatedMessage introduced in 7.2.0b1 are n

**New Features**

* Added support for using `azure.core.credentials.AzureNamedKeyCredential` as credential for authenticating the clients.
* Support for using `azure.core.credentials.AzureSasCredential` as credential for authenticating the clients is now GA.

**Notes**

* Updated azure-core dependency to 1.14.0.

## 7.2.0b1 (2021-04-07)

**New Features**
Expand Down
23 changes: 21 additions & 2 deletions sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from uamqp import utils, compat
from uamqp.message import MessageProperties

from azure.core.credentials import AccessToken, AzureSasCredential
from azure.core.credentials import AccessToken, AzureSasCredential, AzureNamedKeyCredential

from ._common._configuration import Configuration
from .exceptions import (
Expand Down Expand Up @@ -196,6 +196,23 @@ def get_token(self, *scopes, **kwargs): # pylint:disable=unused-argument
raise ValueError("No token scope provided.")
return _generate_sas_token(scopes[0], self.policy, self.key)

class ServiceBusAzureNamedKeyTokenCredential(object):
"""The named key credential used for authentication.
:param credential: The AzureNamedKeyCredential that should be used.
:type credential: ~azure.core.credentials.AzureNamedKeyCredential
"""

def __init__(self, azure_named_key_credential):
# type: (AzureNamedKeyCredential) -> None
self._credential = azure_named_key_credential
self.token_type = b"servicebus.windows.net:sastoken"

def get_token(self, *scopes, **kwargs): # pylint:disable=unused-argument
# type: (str, Any) -> AccessToken
if not scopes:
raise ValueError("No token scope provided.")
name, key = self._credential.named_key
return _generate_sas_token(scopes[0], name, key)

class ServiceBusAzureSasCredential(object):
"""The shared access token credential used for authentication
Expand All @@ -219,7 +236,7 @@ def get_token(self, *scopes, **kwargs): # pylint:disable=unused-argument

class BaseHandler: # pylint:disable=too-many-instance-attributes
def __init__(self, fully_qualified_namespace, entity_name, credential, **kwargs):
# type: (str, str, Union[TokenCredential, AzureSasCredential], Any) -> None
# type: (str, str, Union[TokenCredential, AzureSasCredential, AzureNamedKeyCredential], Any) -> None
# If the user provided http:// or sb://, let's be polite and strip that.
self.fully_qualified_namespace = strip_protocol_from_uri(
fully_qualified_namespace.strip()
Expand All @@ -233,6 +250,8 @@ def __init__(self, fully_qualified_namespace, entity_name, credential, **kwargs)
self._mgmt_target = "{}{}".format(self._entity_path, MANAGEMENT_PATH_SUFFIX)
if isinstance(credential, AzureSasCredential):
self._credential = ServiceBusAzureSasCredential(credential)
elif isinstance(credential, AzureNamedKeyCredential):
self._credential = ServiceBusAzureNamedKeyTokenCredential(credential) # type: ignore
else:
self._credential = credential # type: ignore
self._container_id = CONTAINER_PREFIX + str(uuid.uuid4())[:8]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from ._common.constants import ServiceBusSubQueue

if TYPE_CHECKING:
from azure.core.credentials import TokenCredential, AzureSasCredential
from azure.core.credentials import TokenCredential, AzureSasCredential, AzureNamedKeyCredential

_LOGGER = logging.getLogger(__name__)

Expand All @@ -44,6 +44,7 @@ class ServiceBusClient(object):
credential objects generated by the azure-identity library and objects that implement the
`get_token(self, *scopes)` method, or alternatively, an AzureSasCredential can be provided too.
:type credential: ~azure.core.credentials.TokenCredential or ~azure.core.credentials.AzureSasCredential
or ~azure.core.credentials.AzureNamedKeyCredential
:keyword bool logging_enable: Whether to output network trace logs to the logger. Default is `False`.
:keyword transport_type: The type of transport protocol that will be used for communicating with
the Service Bus service. Default is `TransportType.Amqp` in which case port 5671 is used.
Expand Down Expand Up @@ -72,7 +73,7 @@ class ServiceBusClient(object):
"""

def __init__(self, fully_qualified_namespace, credential, **kwargs):
# type: (str, Union[TokenCredential, AzureSasCredential], Any) -> None
# type: (str, Union[TokenCredential, AzureSasCredential, AzureNamedKeyCredential], Any) -> None
# If the user provided http:// or sb://, let's be polite and strip that.
self.fully_qualified_namespace = strip_protocol_from_uri(
fully_qualified_namespace.strip()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@

if TYPE_CHECKING:
import datetime
from azure.core.credentials import TokenCredential, AzureSasCredential
from azure.core.credentials import TokenCredential, AzureSasCredential, AzureNamedKeyCredential

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -85,6 +85,7 @@ class ServiceBusReceiver(
credential objects generated by the azure-identity library and objects that implement the
`get_token(self, *scopes)` method, or alternatively, an AzureSasCredential can be provided too.
:type credential: ~azure.core.credentials.TokenCredential or ~azure.core.credentials.AzureSasCredential
or ~azure.core.credentials.AzureNamedKeyCredential
:keyword str queue_name: The path of specific Service Bus Queue the client connects to.
:keyword str topic_name: The path of specific Service Bus Topic which contains the Subscription
the client connects to.
Expand Down Expand Up @@ -120,7 +121,7 @@ class ServiceBusReceiver(
"""

def __init__(self, fully_qualified_namespace, credential, **kwargs):
# type: (str, Union[TokenCredential, AzureSasCredential], Any) -> None
# type: (str, Union[TokenCredential, AzureSasCredential, AzureNamedKeyCredential], Any) -> None
self._message_iter = None # type: Optional[Iterator[ServiceBusReceivedMessage]]
if kwargs.get("entity_name"):
super(ServiceBusReceiver, self).__init__(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@

if TYPE_CHECKING:
import datetime
from azure.core.credentials import TokenCredential, AzureSasCredential
from azure.core.credentials import TokenCredential, AzureSasCredential, AzureNamedKeyCredential

MessageTypes = Union[
Mapping[str, Any],
Expand Down Expand Up @@ -130,6 +130,7 @@ class ServiceBusSender(BaseHandler, SenderMixin):
credential objects generated by the azure-identity library and objects that implement the
`get_token(self, *scopes)` method, or alternatively, an AzureSasCredential can be provided too.
:type credential: ~azure.core.credentials.TokenCredential or ~azure.core.credentials.AzureSasCredential
or ~azure.core.credentials.AzureNamedKeyCredential
:keyword str queue_name: The path of specific Service Bus Queue the client connects to.
:keyword str topic_name: The path of specific Service Bus Topic the client connects to.
:keyword bool logging_enable: Whether to output network trace logs to the logger. Default is `False`.
Expand All @@ -143,7 +144,7 @@ class ServiceBusSender(BaseHandler, SenderMixin):
"""

def __init__(self, fully_qualified_namespace, credential, **kwargs):
# type: (str, Union[TokenCredential, AzureSasCredential], Any) -> None
# type: (str, Union[TokenCredential, AzureSasCredential, AzureNamedKeyCredential], Any) -> None
if kwargs.get("entity_name"):
super(ServiceBusSender, self).__init__(
fully_qualified_namespace=fully_qualified_namespace,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from uamqp import compat
from uamqp.message import MessageProperties

from azure.core.credentials import AccessToken, AzureSasCredential
from azure.core.credentials import AccessToken, AzureSasCredential, AzureNamedKeyCredential

from .._base_handler import _generate_sas_token, BaseHandler as BaseHandlerSync
from .._common._configuration import Configuration
Expand Down Expand Up @@ -80,6 +80,23 @@ async def get_token(
raise ValueError("No token scope provided.")
return _generate_sas_token(scopes[0], self.policy, self.key)

class ServiceBusAzureNamedKeyTokenCredentialAsync(object):
"""The named key credential used for authentication.
:param credential: The AzureNamedKeyCredential that should be used.
:type credential: ~azure.core.credentials.AzureNamedKeyCredential
"""

def __init__(self, azure_named_key_credential):
# type: (AzureNamedKeyCredential) -> None
self._credential = azure_named_key_credential
self.token_type = b"servicebus.windows.net:sastoken"

async def get_token(self, *scopes, **kwargs): # pylint:disable=unused-argument
if not scopes:
raise ValueError("No token scope provided.")
name, key = self._credential.named_key
return _generate_sas_token(scopes[0], name, key)


class ServiceBusAzureSasCredentialAsync(object):
"""The shared access token credential used for authentication
Expand All @@ -104,7 +121,7 @@ def __init__(
self,
fully_qualified_namespace: str,
entity_name: str,
credential: Union["AsyncTokenCredential", AzureSasCredential],
credential: Union["AsyncTokenCredential", AzureSasCredential, AzureNamedKeyCredential],
**kwargs: Any
) -> None:
# If the user provided http:// or sb://, let's be polite and strip that.
Expand All @@ -120,6 +137,8 @@ def __init__(
self._mgmt_target = "{}{}".format(self._entity_path, MANAGEMENT_PATH_SUFFIX)
if isinstance(credential, AzureSasCredential):
self._credential = ServiceBusAzureSasCredentialAsync(credential)
elif isinstance(credential, AzureNamedKeyCredential):
self._credential = ServiceBusAzureNamedKeyTokenCredentialAsync(credential) # type: ignore
else:
self._credential = credential # type: ignore
self._container_id = CONTAINER_PREFIX + str(uuid.uuid4())[:8]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import logging

import uamqp
from azure.core.credentials import AzureSasCredential
from azure.core.credentials import AzureSasCredential, AzureNamedKeyCredential

from .._base_handler import _parse_conn_str
from ._base_handler_async import (
Expand Down Expand Up @@ -42,6 +42,7 @@ class ServiceBusClient(object):
credential objects generated by the azure-identity library and objects that implement the
`get_token(self, *scopes)` method, or alternatively, an AzureSasCredential can be provided too.
:type credential: ~azure.core.credentials_async.AsyncTokenCredential or ~azure.core.credentials.AzureSasCredential
or ~azure.core.credentials.AzureNamedKeyCredential
:keyword bool logging_enable: Whether to output network trace logs to the logger. Default is `False`.
:keyword transport_type: The type of transport protocol that will be used for communicating with
the Service Bus service. Default is `TransportType.Amqp` in which case port 5671 is used.
Expand Down Expand Up @@ -72,7 +73,7 @@ class ServiceBusClient(object):
def __init__(
self,
fully_qualified_namespace: str,
credential: Union["AsyncTokenCredential", AzureSasCredential],
credential: Union["AsyncTokenCredential", AzureSasCredential, AzureNamedKeyCredential],
**kwargs: Any
) -> None:
# If the user provided http:// or sb://, let's be polite and strip that.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

from uamqp import ReceiveClientAsync, types, Message
from uamqp.constants import SenderSettleMode
from azure.core.credentials import AzureSasCredential
from azure.core.credentials import AzureSasCredential, AzureNamedKeyCredential

from ..exceptions import ServiceBusError
from ._servicebus_session_async import ServiceBusSession
Expand Down Expand Up @@ -82,6 +82,7 @@ class ServiceBusReceiver(collections.abc.AsyncIterator, BaseHandler, ReceiverMix
credential objects generated by the azure-identity library and objects that implement the
`get_token(self, *scopes)` method, or alternatively, an AzureSasCredential can be provided too.
:type credential: ~azure.core.credentials_async.AsyncTokenCredential or ~azure.core.credentials.AzureSasCredential
or ~azure.core.credentials.AzureNamedKeyCredential
:keyword str queue_name: The path of specific Service Bus Queue the client connects to.
:keyword str topic_name: The path of specific Service Bus Topic which contains the Subscription
the client connects to.
Expand Down Expand Up @@ -119,7 +120,7 @@ class ServiceBusReceiver(collections.abc.AsyncIterator, BaseHandler, ReceiverMix
def __init__(
self,
fully_qualified_namespace: str,
credential: Union["AsyncTokenCredential", AzureSasCredential],
credential: Union["AsyncTokenCredential", AzureSasCredential, AzureNamedKeyCredential],
**kwargs: Any
) -> None:
self._message_iter = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

import uamqp
from uamqp import SendClientAsync, types
from azure.core.credentials import AzureSasCredential
from azure.core.credentials import AzureSasCredential, AzureNamedKeyCredential

from .._common.message import ServiceBusMessage, ServiceBusMessageBatch
from .._servicebus_sender import SenderMixin
Expand Down Expand Up @@ -66,6 +66,7 @@ class ServiceBusSender(BaseHandler, SenderMixin):
credential objects generated by the azure-identity library and objects that implement the
`get_token(self, *scopes)` method, or alternatively, an AzureSasCredential can be provided too.
:type credential: ~azure.core.credentials_async.AsyncTokenCredential or ~azure.core.credentials.AzureSasCredential
or ~azure.core.credentials.AzureNamedKeyCredential
:keyword str queue_name: The path of specific Service Bus Queue the client connects to.
Only one of queue_name or topic_name can be provided.
:keyword str topic_name: The path of specific Service Bus Topic the client connects to.
Expand All @@ -83,7 +84,7 @@ class ServiceBusSender(BaseHandler, SenderMixin):
def __init__(
self,
fully_qualified_namespace: str,
credential: Union["AsyncTokenCredential", AzureSasCredential],
credential: Union["AsyncTokenCredential", AzureSasCredential, AzureNamedKeyCredential],
**kwargs: Any
) -> None:
if kwargs.get("entity_name"):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env python

# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------
"""
An example to show authentication using AzureNamedKeyCredential.
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: the file name should have _async suffix.

"""

import os
import asyncio
from azure.core.credentials import AzureNamedKeyCredential
from azure.servicebus.aio import ServiceBusClient
from azure.servicebus import ServiceBusMessage


FULLY_QUALIFIED_NAMESPACE = os.environ['SERVICE_BUS_FULLY_QUALIFIED_NAMESPACE']
QUEUE_NAME = os.environ["SERVICE_BUS_QUEUE_NAME"]
SAS_POLICY = os.environ['SERVICE_BUS_SAS_POLICY']
SERVICEBUS_SAS_KEY = os.environ['SERVICE_BUS_SAS_KEY']


credential = AzureNamedKeyCredential(SAS_POLICY, SERVICEBUS_SAS_KEY)

async def send_message():
async with ServiceBusClient(FULLY_QUALIFIED_NAMESPACE, credential) as client:
async with client.get_queue_sender(QUEUE_NAME) as sender:
await sender.send_messages([ServiceBusMessage("hello")])

loop = asyncio.get_event_loop()
start_time = time.time()
loop.run_until_complete(send_message())
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env python

# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------
"""
An example to show authentication using AzureNamedKeyCredential.
"""

import os

from azure.core.credentials import AzureNamedKeyCredential
from azure.servicebus import ServiceBusClient, ServiceBusMessage


FULLY_QUALIFIED_NAMESPACE = os.environ['SERVICE_BUS_FULLY_QUALIFIED_NAMESPACE']
QUEUE_NAME = os.environ["SERVICE_BUS_QUEUE_NAME"]
SAS_POLICY = os.environ['SERVICE_BUS_SAS_POLICY']
SERVICEBUS_SAS_KEY = os.environ['SERVICE_BUS_SAS_KEY']


credential = AzureNamedKeyCredential(SAS_POLICY, SERVICEBUS_SAS_KEY)

with ServiceBusClient(FULLY_QUALIFIED_NAMESPACE, credential) as client:
with client.get_queue_sender(QUEUE_NAME) as sender:
sender.send_messages([ServiceBusMessage("hello")])
2 changes: 1 addition & 1 deletion sdk/servicebus/azure-servicebus/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
"uamqp>=1.3.0,<2.0.0",
'azure-common~=1.1',
'msrest>=0.6.17,<2.0.0',
'azure-core<2.0.0,>=1.13.0',
'azure-core<2.0.0,>=1.14.0',
"isodate>=0.6.0",
"six>=1.11.0",
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import logging
import pytest

from azure.core.credentials import AzureSasCredential
from azure.core.credentials import AzureSasCredential, AzureNamedKeyCredential
from azure.mgmt.servicebus.models import AccessRights
from azure.servicebus.aio import ServiceBusClient, ServiceBusSender, ServiceBusReceiver
from azure.servicebus import ServiceBusMessage
Expand Down Expand Up @@ -320,3 +320,35 @@ async def test_client_azure_sas_credential_async(self,
assert len(client._handlers) == 0
async with client.get_queue_sender(servicebus_queue.name) as sender:
await sender.send_messages(ServiceBusMessage("foo"))

@pytest.mark.liveTest
@pytest.mark.live_test_only
@CachedResourceGroupPreparer()
@CachedServiceBusNamespacePreparer(name_prefix='servicebustest')
@CachedServiceBusQueuePreparer(name_prefix='servicebustest')
async def test_client_named_key_credential_async(self,
servicebus_queue,
servicebus_namespace,
servicebus_namespace_key_name,
servicebus_namespace_primary_key,
servicebus_namespace_connection_string,
**kwargs):
hostname = "{}.servicebus.windows.net".format(servicebus_namespace.name)
credential = AzureNamedKeyCredential(servicebus_namespace_key_name, servicebus_namespace_primary_key)

client = ServiceBusClient(hostname, credential)
async with client:
async with client.get_queue_sender(servicebus_queue.name) as sender:
await sender.send_messages(ServiceBusMessage("foo"))

credential.update("foo", "bar")
with pytest.raises(Exception):
async with client:
async with client.get_queue_sender(servicebus_queue.name) as sender:
await sender.send_messages(ServiceBusMessage("foo"))

# update back to the right key again
credential.update(servicebus_namespace_key_name, servicebus_namespace_primary_key)
async with client:
async with client.get_queue_sender(servicebus_queue.name) as sender:
await sender.send_messages(ServiceBusMessage("foo"))
Loading