Skip to content

Commit 95787ba

Browse files
Rakshith Bhyravabhotlaswathipil
andauthored
Add AAD support for EG (Azure#19421)
* Add AAD support * lint * tests fix * comments * unskip tests * aad tests * Update sdk/eventgrid/azure-eventgrid/README.md Co-authored-by: swathipil <[email protected]> * emdpoint * Update sdk/eventgrid/azure-eventgrid/README.md * black formatting * oauth Co-authored-by: swathipil <[email protected]>
1 parent 1e131b8 commit 95787ba

File tree

12 files changed

+150
-30
lines changed

12 files changed

+150
-30
lines changed

sdk/eventgrid/azure-eventgrid/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
# Release History
22

3-
## 4.3.1 (Unreleased)
3+
## 4.4.0 (Unreleased)
44

55
- Bumped `msrest` dependency to `0.6.21` to align with mgmt package.
66

77
### Features Added
88

9+
- `EventGridPublisherClient` now supports Azure Active Directory (AAD) for authentication.
10+
911
### Breaking Changes
1012

1113
### Key Bugs Fixed

sdk/eventgrid/azure-eventgrid/README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,34 @@ az eventgrid domain --create --location <location> --resource-group <resource-gr
3838
In order to interact with the Event Grid service, you will need to create an instance of a client.
3939
An **endpoint** and **credential** are necessary to instantiate the client object.
4040

41+
#### Using Azure Active Directory (AAD)
42+
43+
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.
44+
45+
To send events to a topic or domain with a `TokenCredential`, the authenticated identity should have the "EventGrid Data Sender" role assigned.
46+
47+
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/main/sdk/identity/azure-identity/README.md).
48+
49+
For example, you can use `DefaultAzureCredential` to construct a client which will authenticate using Azure Active Directory:
50+
51+
```Python
52+
from azure.identity import DefaultAzureCredential
53+
from azure.eventgrid import EventGridPublisherClient, EventGridEvent
54+
55+
event = EventGridEvent(
56+
data={"team": "azure-sdk"},
57+
subject="Door1",
58+
event_type="Azure.Sdk.Demo",
59+
data_version="2.0"
60+
)
61+
62+
credential = DefaultAzureCredential()
63+
endpoint = os.environ["EG_TOPIC_HOSTNAME"]
64+
client = EventGridPublisherClient(endpoint, credential)
65+
66+
client.send(event)
67+
```
68+
4169
#### Looking up the endpoint
4270
You can find the topic endpoint within the Event Grid Topic resource on the Azure portal. This will look like:
4371
`"https://<event-grid-topic-name>.<topic-location>.eventgrid.azure.net/api/events"`

sdk/eventgrid/azure-eventgrid/azure/eventgrid/_constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# Licensed under the MIT License. See License.txt in the project root for license information.
44
# --------------------------------------------------------------------------------------------
55

6+
DEFAULT_EVENTGRID_SCOPE = "https://eventgrid.azure.net/.default"
67
EVENTGRID_KEY_HEADER = "aeg-sas-key"
78
EVENTGRID_TOKEN_HEADER = "aeg-sas-token"
89
DEFAULT_API_VERSION = "2018-01-01"

sdk/eventgrid/azure-eventgrid/azure/eventgrid/_helpers.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
from msrest import Serializer
1818
from azure.core.pipeline.transport import HttpRequest
19-
from azure.core.pipeline.policies import AzureKeyCredentialPolicy
19+
from azure.core.pipeline.policies import AzureKeyCredentialPolicy, BearerTokenCredentialPolicy
2020
from azure.core.credentials import AzureKeyCredential, AzureSasCredential
2121
from ._signature_credential_policy import EventGridSasCredentialPolicy
2222
from . import _constants as constants
@@ -28,7 +28,6 @@
2828
if TYPE_CHECKING:
2929
from datetime import datetime
3030

31-
3231
def generate_sas(endpoint, shared_access_key, expiration_date_utc, **kwargs):
3332
# type: (str, str, datetime, Any) -> str
3433
"""Helper method to generate shared access signature given hostname, key, and expiration date.
@@ -70,9 +69,14 @@ def _generate_hmac(key, message):
7069
return base64.b64encode(hmac_new)
7170

7271

73-
def _get_authentication_policy(credential):
72+
def _get_authentication_policy(credential, bearer_token_policy=BearerTokenCredentialPolicy):
7473
if credential is None:
7574
raise ValueError("Parameter 'self._credential' must not be None.")
75+
if hasattr(credential, "get_token"):
76+
return bearer_token_policy(
77+
credential,
78+
constants.DEFAULT_EVENTGRID_SCOPE
79+
)
7680
if isinstance(credential, AzureKeyCredential):
7781
return AzureKeyCredentialPolicy(
7882
credential=credential, name=constants.EVENTGRID_KEY_HEADER
@@ -82,7 +86,7 @@ def _get_authentication_policy(credential):
8286
credential=credential, name=constants.EVENTGRID_TOKEN_HEADER
8387
)
8488
raise ValueError(
85-
"The provided credential should be an instance of AzureSasCredential or AzureKeyCredential"
89+
"The provided credential should be an instance of a TokenCredential, AzureSasCredential or AzureKeyCredential"
8690
)
8791

8892

sdk/eventgrid/azure-eventgrid/azure/eventgrid/_publisher_client.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,11 @@
4040

4141
if TYPE_CHECKING:
4242
# pylint: disable=unused-import,ungrouped-imports
43-
from azure.core.credentials import AzureKeyCredential, AzureSasCredential
43+
from azure.core.credentials import (
44+
AzureKeyCredential,
45+
AzureSasCredential,
46+
TokenCredential,
47+
)
4448

4549
SendType = Union[
4650
CloudEvent,
@@ -60,8 +64,9 @@ class EventGridPublisherClient(object):
6064
6165
:param str endpoint: The topic endpoint to send the events to.
6266
:param credential: The credential object used for authentication which
63-
implements SAS key authentication or SAS token authentication.
64-
:type credential: ~azure.core.credentials.AzureKeyCredential or ~azure.core.credentials.AzureSasCredential
67+
implements SAS key authentication or SAS token authentication or a TokenCredential.
68+
:type credential: ~azure.core.credentials.AzureKeyCredential or ~azure.core.credentials.AzureSasCredential or
69+
~azure.core.credentials.TokenCredential
6570
:rtype: None
6671
6772
.. admonition:: Example:
@@ -82,15 +87,15 @@ class EventGridPublisherClient(object):
8287
"""
8388

8489
def __init__(self, endpoint, credential, **kwargs):
85-
# type: (str, Union[AzureKeyCredential, AzureSasCredential], Any) -> None
90+
# type: (str, Union[AzureKeyCredential, AzureSasCredential, TokenCredential], Any) -> None
8691
self._endpoint = endpoint
8792
self._client = EventGridPublisherClientImpl(
8893
policies=EventGridPublisherClient._policies(credential, **kwargs), **kwargs
8994
)
9095

9196
@staticmethod
9297
def _policies(credential, **kwargs):
93-
# type: (Union[AzureKeyCredential, AzureSasCredential], Any) -> List[Any]
98+
# type: (Union[AzureKeyCredential, AzureSasCredential, TokenCredential], Any) -> List[Any]
9499
auth_policy = _get_authentication_policy(credential)
95100
sdk_moniker = "eventgrid/{}".format(VERSION)
96101
policies = [
@@ -183,17 +188,17 @@ def send(self, events, **kwargs):
183188
if isinstance(events[0], CloudEvent) or _is_cloud_event(events[0]):
184189
try:
185190
events = [
186-
_cloud_event_to_generated(e, **kwargs) for e in events # pylint: disable=protected-access
191+
_cloud_event_to_generated(e, **kwargs)
192+
for e in events # pylint: disable=protected-access
187193
]
188194
except AttributeError:
189195
pass # means it's a dictionary
190196
content_type = "application/cloudevents-batch+json; charset=utf-8"
191197
elif isinstance(events[0], EventGridEvent) or _is_eventgrid_event(events[0]):
192198
for event in events:
193199
_eventgrid_data_typecheck(event)
194-
self._client._send_request( # pylint: disable=protected-access
195-
_build_request(self._endpoint, content_type, events),
196-
**kwargs
200+
self._client._send_request( # pylint: disable=protected-access
201+
_build_request(self._endpoint, content_type, events), **kwargs
197202
)
198203

199204
def close(self):

sdk/eventgrid/azure-eventgrid/azure/eventgrid/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@
99
# regenerated.
1010
# --------------------------------------------------------------------------
1111

12-
VERSION = "4.3.1"
12+
VERSION = "4.4.0"

sdk/eventgrid/azure-eventgrid/azure/eventgrid/aio/_publisher_client_async.py

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
77
# --------------------------------------------------------------------------
88

9-
from typing import Any, Union, List, Dict, cast
9+
from typing import Any, Union, List, Dict, TYPE_CHECKING, cast
1010
from azure.core.credentials import AzureKeyCredential, AzureSasCredential
1111
from azure.core.tracing.decorator_async import distributed_trace_async
1212
from azure.core.messaging import CloudEvent
@@ -22,20 +22,24 @@
2222
DistributedTracingPolicy,
2323
HttpLoggingPolicy,
2424
UserAgentPolicy,
25+
AsyncBearerTokenCredentialPolicy,
2526
)
2627
from .._policies import CloudEventDistributedTracingPolicy
2728
from .._models import EventGridEvent
2829
from .._helpers import (
29-
_get_authentication_policy,
3030
_is_cloud_event,
3131
_is_eventgrid_event,
3232
_eventgrid_data_typecheck,
3333
_build_request,
3434
_cloud_event_to_generated,
35+
_get_authentication_policy,
3536
)
3637
from .._generated.aio import EventGridPublisherClient as EventGridPublisherClientAsync
3738
from .._version import VERSION
3839

40+
if TYPE_CHECKING:
41+
from azure.core.credentials_async import AsyncTokenCredential
42+
3943
SendType = Union[
4044
CloudEvent, EventGridEvent, Dict, List[CloudEvent], List[EventGridEvent], List[Dict]
4145
]
@@ -49,8 +53,9 @@ class EventGridPublisherClient:
4953
5054
:param str endpoint: The topic endpoint to send the events to.
5155
:param credential: The credential object used for authentication which implements
52-
SAS key authentication or SAS token authentication.
53-
:type credential: ~azure.core.credentials.AzureKeyCredential or ~azure.core.credentials.AzureSasCredential
56+
SAS key authentication or SAS token authentication or an AsyncTokenCredential.
57+
:type credential: ~azure.core.credentials.AzureKeyCredential or ~azure.core.credentials.AzureSasCredential or
58+
~azure.core.credentials_async.AsyncTokenCredential
5459
:rtype: None
5560
5661
.. admonition:: Example:
@@ -73,7 +78,9 @@ class EventGridPublisherClient:
7378
def __init__(
7479
self,
7580
endpoint: str,
76-
credential: Union[AzureKeyCredential, AzureSasCredential],
81+
credential: Union[
82+
"AsyncTokenCredential", AzureKeyCredential, AzureSasCredential
83+
],
7784
**kwargs: Any
7885
) -> None:
7986
self._client = EventGridPublisherClientAsync(
@@ -83,9 +90,14 @@ def __init__(
8390

8491
@staticmethod
8592
def _policies(
86-
credential: Union[AzureKeyCredential, AzureSasCredential], **kwargs: Any
93+
credential: Union[
94+
AzureKeyCredential, AzureSasCredential, "AsyncTokenCredential"
95+
],
96+
**kwargs: Any
8797
) -> List[Any]:
88-
auth_policy = _get_authentication_policy(credential)
98+
auth_policy = _get_authentication_policy(
99+
credential, AsyncBearerTokenCredentialPolicy
100+
)
89101
sdk_moniker = "eventgridpublisherclient/{}".format(VERSION)
90102
policies = [
91103
RequestIdPolicy(**kwargs),
@@ -176,17 +188,17 @@ async def send(self, events: SendType, **kwargs: Any) -> None:
176188
if isinstance(events[0], CloudEvent) or _is_cloud_event(events[0]):
177189
try:
178190
events = [
179-
_cloud_event_to_generated(e, **kwargs) for e in events # pylint: disable=protected-access
191+
_cloud_event_to_generated(e, **kwargs)
192+
for e in events # pylint: disable=protected-access
180193
]
181194
except AttributeError:
182195
pass # means it's a dictionary
183196
content_type = "application/cloudevents-batch+json; charset=utf-8"
184197
elif isinstance(events[0], EventGridEvent) or _is_eventgrid_event(events[0]):
185198
for event in events:
186199
_eventgrid_data_typecheck(event)
187-
await self._client._send_request( # pylint: disable=protected-access
188-
_build_request(self._endpoint, content_type, events),
189-
**kwargs
200+
await self._client._send_request( # pylint: disable=protected-access
201+
_build_request(self._endpoint, content_type, events), **kwargs
190202
)
191203

192204
async def __aenter__(self) -> "EventGridPublisherClient":

sdk/eventgrid/azure-eventgrid/samples/async_samples/sample_authentication_async.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,20 @@
3838
credential = AzureSasCredential(signature)
3939
client = EventGridPublisherClient(endpoint, credential)
4040
# [END client_auth_with_sas_cred_async]
41+
42+
# [START client_auth_with_token_cred_async]
43+
from azure.identity.aio import DefaultAzureCredential
44+
from azure.eventgrid.aio import EventGridPublisherClient
45+
from azure.eventgrid import EventGridEvent
46+
47+
event = EventGridEvent(
48+
data={"team": "azure-sdk"},
49+
subject="Door1",
50+
event_type="Azure.Sdk.Demo",
51+
data_version="2.0"
52+
)
53+
54+
credential = DefaultAzureCredential()
55+
endpoint = os.environ["EG_TOPIC_HOSTNAME"]
56+
client = EventGridPublisherClient(endpoint, credential)
57+
# [END client_auth_with_token_cred_async]

sdk/eventgrid/azure-eventgrid/samples/sync_samples/sample_authentication.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,12 @@
3838
credential = AzureSasCredential(signature)
3939
client = EventGridPublisherClient(endpoint, credential)
4040
# [END client_auth_with_sas_cred]
41+
42+
# [START client_auth_with_token_cred]
43+
from azure.identity import DefaultAzureCredential
44+
from azure.eventgrid import EventGridPublisherClient, EventGridEvent
45+
46+
credential = DefaultAzureCredential()
47+
endpoint = os.environ["EG_TOPIC_HOSTNAME"]
48+
client = EventGridPublisherClient(endpoint, credential)
49+
# [END client_auth_with_token_cred]

sdk/eventgrid/azure-eventgrid/tests/eventgrid_preparer.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
import os
44
from collections import namedtuple
55

6+
from azure_devtools.scenario_tests import ReplayableTest
7+
from azure.core.credentials import AccessToken
68
from azure.mgmt.eventgrid import EventGridManagementClient
79
from azure.mgmt.eventgrid.models import Topic, InputSchema, JsonInputSchemaMapping, JsonField, JsonFieldWithDefault
810
from azure_devtools.scenario_tests.exceptions import AzureTestError
911

1012
from devtools_testutils import (
11-
ResourceGroupPreparer, AzureMgmtPreparer, FakeResource
13+
ResourceGroupPreparer, AzureMgmtPreparer, FakeResource, AzureMgmtTestCase
1214
)
1315

1416
from devtools_testutils.resource_testcase import RESOURCE_GROUP_PARAM
@@ -25,6 +27,15 @@
2527
DATA_VERSION_JSON_FIELD_WITH_DEFAULT = JsonFieldWithDefault(source_field='customDataVersion', default_value='')
2628
CUSTOM_JSON_INPUT_SCHEMA_MAPPING = JsonInputSchemaMapping(id=ID_JSON_FIELD, topic=TOPIC_JSON_FIELD, event_time=EVENT_TIME_JSON_FIELD, event_type=EVENT_TYPE_JSON_FIELD_WITH_DEFAULT, subject=SUBJECT_JSON_FIELD_WITH_DEFAULT, data_version=DATA_VERSION_JSON_FIELD_WITH_DEFAULT)
2729

30+
class FakeTokenCredential(object):
31+
"""Protocol for classes able to provide OAuth tokens.
32+
:param str scopes: Lets you specify the type of access needed.
33+
"""
34+
def __init__(self):
35+
self.token = AccessToken("YOU SHALL NOT PASS", 0)
36+
37+
def get_token(self, *args):
38+
return self.token
2839

2940
class EventGridTopicPreparer(AzureMgmtPreparer):
3041
def __init__(self,
@@ -94,4 +105,5 @@ def _get_resource_group(self, **kwargs):
94105
'decorator @{} in front of this event grid topic preparer.'
95106
raise AzureTestError(template.format(ResourceGroupPreparer.__name__))
96107

108+
97109
CachedEventGridTopicPreparer = functools.partial(EventGridTopicPreparer, use_cache=True)

0 commit comments

Comments
 (0)