Skip to content

Commit

Permalink
Add EventGrid AAD authentication (#22735)
Browse files Browse the repository at this point in the history
  • Loading branch information
YijunXieMS authored Jul 19, 2021
1 parent 7c9ac27 commit e14704d
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 9 deletions.
3 changes: 2 additions & 1 deletion sdk/eventgrid/azure-messaging-eventgrid/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Release History

## 4.5.0-beta.1 (Unreleased)

### Features Added
- Added `EventGridPublisherClientBuilder#credential(TokenCredential credential)` to support Azure Active Directory authentication.

## 4.4.0 (2021-06-09)
### New Features
Expand Down
32 changes: 29 additions & 3 deletions sdk/eventgrid/azure-messaging-eventgrid/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ az eventgrid domain create --location <location> --resource-group <your-resource

### Authenticate the Client

In order to send events, we need an endpoint to send to and some authentication for the endpoint, either as a
key credential or a shared access signature.
In order to send events, we need an endpoint to send to and some authentication for the endpoint. The authentication can be
a key credential, a shared access signature, or Azure Active Directory token authentication.
The endpoint and key can both be obtained through [Azure Portal][portal] or [Azure CLI][cli].

#### Endpoint
Expand All @@ -75,6 +75,11 @@ using the following command in [Azure CLI][cli]. Anyone of the keys listed will
az eventgrid topic key list --name <your-resource-name> --resource-group <your-resource-group-name>
```

#### Azure Active Directory (AAD) Token authentication
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.

#### Creating the Client

##### Using endpoint and access key to create the client
Expand Down Expand Up @@ -138,7 +143,7 @@ limited time, you can use it to create the publisher client:
Sync client:
<!-- embedme ./src/samples/java/com/azure/messaging/eventgrid/samples/ReadmeSamples.java#L102-L105 -->
```java
EventGridPublisherClient<CloudEvent> eventGridEventClient = new EventGridPublisherClientBuilder()
EventGridPublisherClient<CloudEvent> cloudEventClient = new EventGridPublisherClientBuilder()
.endpoint("<endpont of your event grid topic/domain that accepts CloudEvent schema>")
.credential(new AzureSasCredential("<sas token that can access the endpoint>"))
.buildCloudEventPublisherClient();
Expand All @@ -152,6 +157,27 @@ EventGridPublisherAsyncClient<CloudEvent> cloudEventAsyncClient = new EventGridP
.buildCloudEventPublisherAsyncClient();
```

##### Using endpoint and Azure Active Directory (AAD) token credential to create the client
To use the AAD token credential, include `azure-identity` artifact as a dependency. Refer to
[azure-identity README](https://docs.microsoft.com/java/api/overview/azure/identity-readme) for details.

Sync client:
<!-- embedme ./src/samples/java/com/azure/messaging/eventgrid/samples/ReadmeSamples.java#L231-L234 -->
```java
EventGridPublisherClient<CloudEvent> cloudEventClient = new EventGridPublisherClientBuilder()
.endpoint("<endpoint of your event grid topic/domain that accepts CloudEvent schema>")
.credential(new DefaultAzureCredentialBuilder().build())
.buildCloudEventPublisherClient();
```
Async client:
<!-- embedme ./src/samples/java/com/azure/messaging/eventgrid/samples/ReadmeSamples.java#L238-L241 -->
```java
EventGridPublisherAsyncClient<CloudEvent> cloudEventClient = new EventGridPublisherClientBuilder()
.endpoint("<endpoint of your event grid topic/domain that accepts CloudEvent schema>")
.credential(new DefaultAzureCredentialBuilder().build())
.buildCloudEventPublisherAsyncClient();
```

#### Create a SAS token for other people to send events for a limited period of time
If you'd like to give permission to other people to publish events to your Event Grid Topic or Domain for some time, you can create
a SAS (**Shared Access Signature**) for them so they can create an `EventGridPublisherClient` like the above to use `AzureSasCredential`
Expand Down
6 changes: 6 additions & 0 deletions sdk/eventgrid/azure-messaging-eventgrid/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@
<version>12.10.0</version> <!-- {x-version-update;com.azure:azure-storage-queue;dependency} -->
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
<version>1.3.3</version> <!-- {x-version-update;com.azure:azure-identity;dependency} -->
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.azure.core.annotation.ServiceClientBuilder;
import com.azure.core.credential.AzureKeyCredential;
import com.azure.core.credential.AzureSasCredential;
import com.azure.core.credential.TokenCredential;
import com.azure.core.http.HttpClient;
import com.azure.core.http.HttpHeader;
import com.azure.core.http.HttpHeaders;
Expand All @@ -14,6 +15,7 @@
import com.azure.core.http.policy.AddDatePolicy;
import com.azure.core.http.policy.AddHeadersPolicy;
import com.azure.core.http.policy.AzureKeyCredentialPolicy;
import com.azure.core.http.policy.BearerTokenAuthenticationPolicy;
import com.azure.core.http.policy.HttpLogOptions;
import com.azure.core.http.policy.HttpLoggingPolicy;
import com.azure.core.http.policy.HttpPipelinePolicy;
Expand Down Expand Up @@ -53,6 +55,7 @@ public final class EventGridPublisherClientBuilder {
private static final String EVENTGRID_PROPERTIES = "azure-messaging-eventgrid.properties";
private static final String NAME = "name";
private static final String VERSION = "version";
private static final String DEFAULT_EVENTGRID_SCOPE = "https://eventgrid.azure.net/.default";

private final String clientName;

Expand All @@ -70,6 +73,8 @@ public final class EventGridPublisherClientBuilder {

private AzureSasCredential sasToken;

private TokenCredential tokenCredential;

private EventGridServiceVersion serviceVersion;

private String endpoint;
Expand Down Expand Up @@ -131,14 +136,29 @@ private <T> EventGridPublisherAsyncClient<T> buildAsyncClient(Class<T> eventClas

httpPipelinePolicies.add(new AddDatePolicy());

// Using token before key if both are set
final int credentialCount = (sasToken != null ? 1 : 0) + (keyCredential != null ? 1 : 0)
+ (tokenCredential != null ? 1 : 0);
if (credentialCount > 1) {
throw logger.logExceptionAsError(
new IllegalStateException("More than 1 credentials are set while building a client. "
+ "You should set one and only one credential of type 'TokenCredential', 'AzureSasCredential', "
+ "or 'AzureKeyCredential'."));
} else if (credentialCount == 0) {
throw logger.logExceptionAsError(
new IllegalStateException("Missing credential information while building a client."
+ "You should set one and only one credential of type 'TokenCredential', 'AzureSasCredential', "
+ "or 'AzureKeyCredential'."));
}
if (sasToken != null) {
httpPipelinePolicies.add((context, next) -> {
context.getHttpRequest().getHeaders().set(AEG_SAS_TOKEN, sasToken.getSignature());
return next.process();
});
} else {
} else if (keyCredential != null) {
httpPipelinePolicies.add(new AzureKeyCredentialPolicy(AEG_SAS_KEY, keyCredential));
} else {
httpPipelinePolicies.add(new BearerTokenAuthenticationPolicy(this.tokenCredential,
DEFAULT_EVENTGRID_SCOPE));
}

httpPipelinePolicies.addAll(policies);
Expand Down Expand Up @@ -238,7 +258,7 @@ public EventGridPublisherClientBuilder credential(AzureKeyCredential credential)

/**
* Set the domain or topic authentication using an already obtained Shared Access Signature token.
* @param credential the token credential to use.
* @param credential the sas credential to use.
*
* @return the builder itself.
*/
Expand All @@ -247,6 +267,19 @@ public EventGridPublisherClientBuilder credential(AzureSasCredential credential)
return this;
}

/**
* Set the domain or topic authentication using Azure Activity Directory authentication.
* Refer to <a href="https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/identity/azure-identity">azure-identity</a>
*
* @param credential the token credential to use.
*
* @return the builder itself.
*/
public EventGridPublisherClientBuilder credential(TokenCredential credential) {
this.tokenCredential = credential;
return this;
}

/**
* Set the domain or topic endpoint. This is the address to publish events to.
* It must be the full url of the endpoint instead of just the hostname.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.azure.core.util.BinaryData;
import com.azure.core.util.serializer.TypeReference;
import com.azure.core.models.CloudEvent;
import com.azure.identity.DefaultAzureCredentialBuilder;
import com.azure.messaging.eventgrid.EventGridEvent;
import com.azure.messaging.eventgrid.EventGridPublisherAsyncClient;
import com.azure.messaging.eventgrid.EventGridPublisherClient;
Expand Down Expand Up @@ -99,8 +100,8 @@ public void createCustomEventPublisherAsyncClient() {
}

public void createPublisherClientWithSas() {
EventGridPublisherClient<CloudEvent> eventGridEventClient = new EventGridPublisherClientBuilder()
.endpoint("<endpont of your event grid topic/domain that accepts CloudEvent schema>")
EventGridPublisherClient<CloudEvent> cloudEventClient = new EventGridPublisherClientBuilder()
.endpoint("<endpoint of your event grid topic/domain that accepts CloudEvent schema>")
.credential(new AzureSasCredential("<sas token that can access the endpoint>"))
.buildCloudEventPublisherClient();
}
Expand Down Expand Up @@ -224,4 +225,18 @@ public void systemEventDifferentEventData() {
}
}
}

public void createPublisherClientWithTokenCredential() {
EventGridPublisherClient<CloudEvent> cloudEventClient = new EventGridPublisherClientBuilder()
.endpoint("<endpoint of your event grid topic/domain that accepts CloudEvent schema>")
.credential(new DefaultAzureCredentialBuilder().build())
.buildCloudEventPublisherClient();
}

public void createPublisherAsyncClientWithTokenCredential() {
EventGridPublisherAsyncClient<CloudEvent> cloudEventClient = new EventGridPublisherClientBuilder()
.endpoint("<endpoint of your event grid topic/domain that accepts CloudEvent schema>")
.credential(new DefaultAzureCredentialBuilder().build())
.buildCloudEventPublisherAsyncClient();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package com.azure.messaging.eventgrid;

import com.azure.core.credential.AzureKeyCredential;
import com.azure.identity.DefaultAzureCredentialBuilder;
import org.junit.jupiter.api.Test;

import java.io.UnsupportedEncodingException;
Expand All @@ -15,6 +16,7 @@
import java.time.format.DateTimeFormatter;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class EventGridPublisherClientOnlyTests {
Expand Down Expand Up @@ -49,4 +51,30 @@ public void testGenerateSas() throws UnsupportedEncodingException {

assertTrue(sasToken1.contains(unsignedSas));
}

@Test
public void createPublisherClientWithNoCredentials() {
final String fullEndpoint = String.format("%s?%s=%s", DUMMY_ENDPOINT, "api-version",
EventGridServiceVersion.getLatest().getVersion());

assertThrows(IllegalStateException.class, () -> {
new EventGridPublisherClientBuilder()
.endpoint(fullEndpoint)
.buildCloudEventPublisherClient();
});
}

@Test
public void createPublisherClientWithTwoCredentials() {
final String fullEndpoint = String.format("%s?%s=%s", DUMMY_ENDPOINT, "api-version",
EventGridServiceVersion.getLatest().getVersion());

assertThrows(IllegalStateException.class, () -> {
new EventGridPublisherClientBuilder()
.endpoint(fullEndpoint)
.credential(new AzureKeyCredential("FakeCredential"))
.credential(new DefaultAzureCredentialBuilder().build())
.buildCloudEventPublisherClient();
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import com.azure.core.util.Context;
import com.azure.core.models.CloudEvent;
import com.azure.core.util.serializer.JacksonAdapter;
import com.azure.identity.DefaultAzureCredential;
import com.azure.identity.DefaultAzureCredentialBuilder;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
Expand Down Expand Up @@ -160,6 +162,32 @@ public void publishWithSasToken() {
.verifyComplete();
}

@Test
@Disabled
public void publishWithTokenCredential() {
DefaultAzureCredential defaultCredential = new DefaultAzureCredentialBuilder().build();
EventGridPublisherAsyncClient<CloudEvent> egClient = builder
.credential(defaultCredential)
.endpoint(getEndpoint(CLOUD_ENDPOINT))
.buildCloudEventPublisherAsyncClient();

List<CloudEvent> events = new ArrayList<>();
events.add(new CloudEvent("/microsoft/testEvent", "Microsoft.MockPublisher.TestEvent",
BinaryData.fromObject(new HashMap<String, String>() {
{
put("Field1", "Value1");
put("Field2", "Value2");
put("Field3", "Value3");
}
}), CloudEventDataFormat.JSON, "application/json")
.setSubject("Test")
.setTime(OffsetDateTime.now()));

StepVerifier.create(egClient.sendEventsWithResponse(events, Context.NONE))
.expectNextMatches(voidResponse -> voidResponse.getStatusCode() == 200)
.verifyComplete();
}

@Test
public void publishCloudEvents() {
EventGridPublisherAsyncClient<CloudEvent> egClient = builder
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"networkCallRecords" : [ {
"Method" : "POST",
"Uri" : "https://REDACTED.westus2-1.eventgrid-int.azure.net/api/events?api-version=2018-01-01",
"Headers" : {
"User-Agent" : "azsdk-java-UnknownName/UnknownVersion (15.0.2; Windows 10; 10.0)",
"x-ms-client-request-id" : "d9d08daa-f908-4338-ab35-ad6042dc5b9c",
"Content-Type" : "application/cloudevents-batch+json; charset=utf-8"
},
"Response" : {
"content-length" : "0",
"Strict-Transport-Security" : "max-age=31536000; includeSubDomains",
"Server" : "Microsoft-HTTPAPI/2.0",
"api-supported-versions" : "2018-01-01",
"retry-after" : "0",
"StatusCode" : "200",
"x-ms-request-id" : "83028bf1-abe4-49ee-a244-ad5e66335d25",
"Date" : "Fri, 25 Jun 2021 21:19:01 GMT"
},
"Exception" : null
} ],
"variables" : [ ]
}

0 comments on commit e14704d

Please sign in to comment.