Skip to content

Commit

Permalink
Add AAD support
Browse files Browse the repository at this point in the history
  • Loading branch information
rakshith91 committed Jun 23, 2021
1 parent 18b3999 commit 1cc83d9
Show file tree
Hide file tree
Showing 12 changed files with 153 additions and 16 deletions.
4 changes: 3 additions & 1 deletion sdk/eventgrid/azure-eventgrid/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# Release History

## 4.3.1 (Unreleased)
## 4.4.0 (Unreleased)

### Features Added

- `EventGridPublisherClient` now supports Azure Active Directory (AAD) for authentication.

### Breaking Changes

### Key Bugs Fixed
Expand Down
28 changes: 28 additions & 0 deletions sdk/eventgrid/azure-eventgrid/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,34 @@ az eventgrid domain --create --location <location> --resource-group <resource-gr
In order to interact with the Event Grid service, you will need to create an instance of a client.
An **endpoint** and **credential** are necessary to instantiate the client object.

#### Using Azure Active Directory (AAD)

Azure Event Grid provides integration with Azure Active Directory (Azure AD) for identity-based authentication of requests. With Azure AD, you can use role-based access control (RBAC) to grant access to your Azure Event Grid resources to users, groups, or applications.

To send events to a topic or domain with a `TokenCredential`, the authenticated identity should have the "EventGrid Data Sender" role assigned.

With the `azure-identity` package, you can seamlessly authorize requests in both development and production environments. To learn more about Azure Active Directory, see the [`azure-identity` README](https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/identity/azure-identity/README.md).

For example, use can use `DefaultAzureCredential` to construct a client which will authenticate using Azure Active Directory:

```Python
from azure.identity import DefaultAzureCredential
from azure.eventgrid import EventGridPublisherClient, EventGridEvent

event = EventGridEvent(
data={"team": "azure-sdk"},
subject="Door1",
event_type="Azure.Sdk.Demo",
data_version="2.0"
)

credential = DefaultAzureCredential()
endpoint = os.environ["EG_TOPIC_HOSTNAME"]
client = EventGridPublisherClient(endpoint, credential)

client.send(event)
```

#### Looking up the endpoint
You can find the topic endpoint within the Event Grid Topic resource on the Azure portal. This will look like:
`"https://<event-grid-topic-name>.<topic-location>.eventgrid.azure.net/api/events"`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

DEFAULT_EVENTGRID_SCOPE = "https://eventgrid.azure.net/.default"
EVENTGRID_KEY_HEADER = "aeg-sas-key"
EVENTGRID_TOKEN_HEADER = "aeg-sas-token"
DEFAULT_API_VERSION = "2018-01-01"
Expand Down
11 changes: 8 additions & 3 deletions sdk/eventgrid/azure-eventgrid/azure/eventgrid/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

from msrest import Serializer
from azure.core.pipeline.transport import HttpRequest
from azure.core.pipeline.policies import AzureKeyCredentialPolicy
from azure.core.pipeline.policies import AzureKeyCredentialPolicy, BearerTokenCredentialPolicy
from azure.core.credentials import AzureKeyCredential, AzureSasCredential
from ._signature_credential_policy import EventGridSasCredentialPolicy
from . import _constants as constants
Expand All @@ -27,7 +27,7 @@

if TYPE_CHECKING:
from datetime import datetime

from azure.core.credentials import TokenCredential

def generate_sas(endpoint, shared_access_key, expiration_date_utc, **kwargs):
# type: (str, str, datetime, Any) -> str
Expand Down Expand Up @@ -73,6 +73,11 @@ def _generate_hmac(key, message):
def _get_authentication_policy(credential):
if credential is None:
raise ValueError("Parameter 'self._credential' must not be None.")
if isinstance(credential, TokenCredential):
return BearerTokenCredentialPolicy(
credential,
scopes=constants.DEFAULT_EVENTGRID_SCOPE
)
if isinstance(credential, AzureKeyCredential):
return AzureKeyCredentialPolicy(
credential=credential, name=constants.EVENTGRID_KEY_HEADER
Expand All @@ -82,7 +87,7 @@ def _get_authentication_policy(credential):
credential=credential, name=constants.EVENTGRID_TOKEN_HEADER
)
raise ValueError(
"The provided credential should be an instance of AzureSasCredential or AzureKeyCredential"
"The provided credential should be an instance of a TokenCredential, AzureSasCredential or AzureKeyCredential"
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@

if TYPE_CHECKING:
# pylint: disable=unused-import,ungrouped-imports
from azure.core.credentials import AzureKeyCredential, AzureSasCredential
from azure.core.credentials import AzureKeyCredential, AzureSasCredential, TokenCredential

SendType = Union[
CloudEvent,
Expand All @@ -60,8 +60,9 @@ class EventGridPublisherClient(object):
:param str endpoint: The topic endpoint to send the events to.
:param credential: The credential object used for authentication which
implements SAS key authentication or SAS token authentication.
:type credential: ~azure.core.credentials.AzureKeyCredential or ~azure.core.credentials.AzureSasCredential
implements SAS key authentication or SAS token authentication or a TokenCredential.
:type credential: ~azure.core.credentials.AzureKeyCredential or ~azure.core.credentials.AzureSasCredential or
~azure.core.credentials.TokenCredential
:rtype: None
.. admonition:: Example:
Expand All @@ -82,7 +83,7 @@ class EventGridPublisherClient(object):
"""

def __init__(self, endpoint, credential, **kwargs):
# type: (str, Union[AzureKeyCredential, AzureSasCredential], Any) -> None
# type: (str, Union[AzureKeyCredential, AzureSasCredential, TokenCredential], Any) -> None
self._endpoint = endpoint
self._client = EventGridPublisherClientImpl(
policies=EventGridPublisherClient._policies(credential, **kwargs), **kwargs
Expand Down
2 changes: 1 addition & 1 deletion sdk/eventgrid/azure-eventgrid/azure/eventgrid/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@
# regenerated.
# --------------------------------------------------------------------------

VERSION = "4.3.1"
VERSION = "4.4.0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------
from typing import TYPE_CHECKING
from azure.core.pipeline.policies import AzureKeyCredentialPolicy, AsyncBearerTokenCredentialPolicy
from azure.core.credentials import AzureKeyCredential, AzureSasCredential

from .. import _constants as constants
from .._signature_credential_policy import EventGridSasCredentialPolicy

if TYPE_CHECKING:
from azure.core.credentials_async import AsyncTokenCredential


def _get_authentication_policy_async(credential):
if credential is None:
raise ValueError("Parameter 'self._credential' must not be None.")
if isinstance(credential, AsyncTokenCredential):
return AsyncBearerTokenCredentialPolicy(
credential,
scopes=constants.DEFAULT_EVENTGRID_SCOPE
)
if isinstance(credential, AzureKeyCredential):
return AzureKeyCredentialPolicy(
credential=credential, name=constants.EVENTGRID_KEY_HEADER
)
if isinstance(credential, AzureSasCredential):
return EventGridSasCredentialPolicy(
credential=credential, name=constants.EVENTGRID_TOKEN_HEADER
)
raise ValueError(
"The provided credential should be an instance of a TokenCredential, AzureSasCredential or AzureKeyCredential"
)
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
# --------------------------------------------------------------------------

from typing import Any, Union, List, Dict, cast
from typing import Any, Union, List, Dict, TYPE_CHECKING, cast
from azure.core.credentials import AzureKeyCredential, AzureSasCredential
from azure.core.tracing.decorator_async import distributed_trace_async
from azure.core.messaging import CloudEvent
Expand All @@ -23,10 +23,10 @@
HttpLoggingPolicy,
UserAgentPolicy,
)
from ._helpers_async import _get_authentication_policy_async
from .._policies import CloudEventDistributedTracingPolicy
from .._models import EventGridEvent
from .._helpers import (
_get_authentication_policy,
_is_cloud_event,
_is_eventgrid_event,
_eventgrid_data_typecheck,
Expand All @@ -36,6 +36,9 @@
from .._generated.aio import EventGridPublisherClient as EventGridPublisherClientAsync
from .._version import VERSION

if TYPE_CHECKING:
from azure.core.credentials_async import AsyncTokenCredential

SendType = Union[
CloudEvent, EventGridEvent, Dict, List[CloudEvent], List[EventGridEvent], List[Dict]
]
Expand All @@ -49,8 +52,9 @@ class EventGridPublisherClient:
:param str endpoint: The topic endpoint to send the events to.
:param credential: The credential object used for authentication which implements
SAS key authentication or SAS token authentication.
:type credential: ~azure.core.credentials.AzureKeyCredential or ~azure.core.credentials.AzureSasCredential
SAS key authentication or SAS token authentication or an AsyncTokenCredential.
:type credential: ~azure.core.credentials.AzureKeyCredential or ~azure.core.credentials.AzureSasCredential or
~azure.core.credentials_async.AsyncTokenCredential
:rtype: None
.. admonition:: Example:
Expand All @@ -73,7 +77,7 @@ class EventGridPublisherClient:
def __init__(
self,
endpoint: str,
credential: Union[AzureKeyCredential, AzureSasCredential],
credential: Union[AzureKeyCredential, AzureSasCredential, AsyncTokenCredential],
**kwargs: Any
) -> None:
self._client = EventGridPublisherClientAsync(
Expand All @@ -85,7 +89,7 @@ def __init__(
def _policies(
credential: Union[AzureKeyCredential, AzureSasCredential], **kwargs: Any
) -> List[Any]:
auth_policy = _get_authentication_policy(credential)
auth_policy = _get_authentication_policy_async(credential)
sdk_moniker = "eventgridpublisherclient/{}".format(VERSION)
policies = [
RequestIdPolicy(**kwargs),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,20 @@
credential = AzureSasCredential(signature)
client = EventGridPublisherClient(endpoint, credential)
# [END client_auth_with_sas_cred_async]

# [START client_auth_with_token_cred_async]
from azure.identity.aio import DefaultAzureCredential
from azure.eventgrid.aio import EventGridPublisherClient
from azure.eventgrid import EventGridEvent

event = EventGridEvent(
data={"team": "azure-sdk"},
subject="Door1",
event_type="Azure.Sdk.Demo",
data_version="2.0"
)

credential = DefaultAzureCredential()
endpoint = os.environ["EG_TOPIC_HOSTNAME"]
client = EventGridPublisherClient(endpoint, credential)
# [END client_auth_with_token_cred_async]
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,19 @@
credential = AzureSasCredential(signature)
client = EventGridPublisherClient(endpoint, credential)
# [END client_auth_with_sas_cred]

# [START client_auth_with_token_cred]
from azure.identity import DefaultAzureCredential
from azure.eventgrid import EventGridPublisherClient, EventGridEvent

event = EventGridEvent(
data={"team": "azure-sdk"},
subject="Door1",
event_type="Azure.Sdk.Demo",
data_version="2.0"
)

credential = DefaultAzureCredential()
endpoint = os.environ["EG_TOPIC_HOSTNAME"]
client = EventGridPublisherClient(endpoint, credential)
# [END client_auth_with_token_cred]
14 changes: 14 additions & 0 deletions sdk/eventgrid/azure-eventgrid/tests/test_eg_publisher_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import sys
import os
import json
from azure.identity import DefaultAzureCredential
import pytest
import uuid
from datetime import datetime, timedelta
Expand Down Expand Up @@ -345,3 +346,16 @@ def test_send_throws_with_bad_credential(self):
bad_credential = "I am a bad credential"
with pytest.raises(ValueError, match="The provided credential should be an instance of AzureSasCredential or AzureKeyCredential"):
client = EventGridPublisherClient("eventgrid_endpoint", bad_credential)

@CachedResourceGroupPreparer(name_prefix='eventgridtest')
@CachedEventGridTopicPreparer(name_prefix='eventgridtest')
def test_send_signature_credential(self, resource_group, eventgrid_topic, eventgrid_topic_primary_key, eventgrid_topic_endpoint):
credential = DefaultAzureCredential()
client = EventGridPublisherClient(eventgrid_topic_endpoint, credential)
eg_event = EventGridEvent(
subject="sample",
data={"sample": "eventgridevent"},
event_type="Sample.EventGrid.Event",
data_version="2.0"
)
client.send(eg_event)
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import sys
import os
import json
from azure.identity.aio import DefaultAzureCredential
import pytest
from datetime import timedelta
from msrest.serialization import UTC
Expand Down Expand Up @@ -328,4 +329,18 @@ async def test_send_and_close_async_session(self, resource_group, eventgrid_topi
@pytest.mark.asyncio
def test_send_NONE_credential_async(self, resource_group, eventgrid_topic, eventgrid_topic_primary_key, eventgrid_topic_endpoint):
with pytest.raises(ValueError, match="Parameter 'self._credential' must not be None."):
client = EventGridPublisherClient(eventgrid_topic_endpoint, None)
client = EventGridPublisherClient(eventgrid_topic_endpoint, None)

@CachedResourceGroupPreparer(name_prefix='eventgridtest')
@CachedEventGridTopicPreparer(name_prefix='eventgridtest')
@pytest.mark.asyncio
async def test_send_signature_credential(self, resource_group, eventgrid_topic, eventgrid_topic_primary_key, eventgrid_topic_endpoint):
credential = DefaultAzureCredential()
client = EventGridPublisherClient(eventgrid_topic_endpoint, credential)
eg_event = EventGridEvent(
subject="sample",
data={"sample": "eventgridevent"},
event_type="Sample.EventGrid.Event",
data_version="2.0"
)
await client.send(eg_event)

0 comments on commit 1cc83d9

Please sign in to comment.