Skip to content

Commit 967af1a

Browse files
authored
Add token credential auth support (#17308)
1 parent 3812cd6 commit 967af1a

20 files changed

+576
-395
lines changed

sdk/eventhub/Microsoft.Azure.WebJobs.Extensions.EventHubs/Microsoft.Azure.WebJobs.Extensions.EventHubs.sln

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Azure.WebJobs.Ext
1111
EndProject
1212
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Messaging.EventHubs", "..\Azure.Messaging.EventHubs\src\Azure.Messaging.EventHubs.csproj", "{B51ECD35-11DA-46D2-89D7-9DE3888CF896}"
1313
EndProject
14+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Extensions.Azure", "..\..\extensions\Microsoft.Extensions.Azure\src\Microsoft.Extensions.Azure.csproj", "{E772769A-7CE1-4FBC-A084-C0936EB2C766}"
15+
EndProject
1416
Global
1517
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1618
Debug|Any CPU = Debug|Any CPU
@@ -72,5 +74,17 @@ Global
7274
{B51ECD35-11DA-46D2-89D7-9DE3888CF896}.Release|x64.Build.0 = Release|Any CPU
7375
{B51ECD35-11DA-46D2-89D7-9DE3888CF896}.Release|x86.ActiveCfg = Release|Any CPU
7476
{B51ECD35-11DA-46D2-89D7-9DE3888CF896}.Release|x86.Build.0 = Release|Any CPU
77+
{E772769A-7CE1-4FBC-A084-C0936EB2C766}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
78+
{E772769A-7CE1-4FBC-A084-C0936EB2C766}.Debug|Any CPU.Build.0 = Debug|Any CPU
79+
{E772769A-7CE1-4FBC-A084-C0936EB2C766}.Debug|x64.ActiveCfg = Debug|Any CPU
80+
{E772769A-7CE1-4FBC-A084-C0936EB2C766}.Debug|x64.Build.0 = Debug|Any CPU
81+
{E772769A-7CE1-4FBC-A084-C0936EB2C766}.Debug|x86.ActiveCfg = Debug|Any CPU
82+
{E772769A-7CE1-4FBC-A084-C0936EB2C766}.Debug|x86.Build.0 = Debug|Any CPU
83+
{E772769A-7CE1-4FBC-A084-C0936EB2C766}.Release|Any CPU.ActiveCfg = Release|Any CPU
84+
{E772769A-7CE1-4FBC-A084-C0936EB2C766}.Release|Any CPU.Build.0 = Release|Any CPU
85+
{E772769A-7CE1-4FBC-A084-C0936EB2C766}.Release|x64.ActiveCfg = Release|Any CPU
86+
{E772769A-7CE1-4FBC-A084-C0936EB2C766}.Release|x64.Build.0 = Release|Any CPU
87+
{E772769A-7CE1-4FBC-A084-C0936EB2C766}.Release|x86.ActiveCfg = Release|Any CPU
88+
{E772769A-7CE1-4FBC-A084-C0936EB2C766}.Release|x86.Build.0 = Release|Any CPU
7589
EndGlobalSection
7690
EndGlobal

sdk/eventhub/Microsoft.Azure.WebJobs.Extensions.EventHubs/api/Microsoft.Azure.WebJobs.Extensions.EventHubs.netstandard2.0.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,18 @@ namespace Microsoft.Azure.WebJobs
55
public sealed partial class EventHubAttribute : System.Attribute
66
{
77
public EventHubAttribute(string eventHubName) { }
8-
[Microsoft.Azure.WebJobs.Description.ConnectionStringAttribute]
9-
public string Connection { get { throw null; } set { } }
108
[Microsoft.Azure.WebJobs.Description.AutoResolveAttribute]
9+
public string Connection { get { throw null; } set { } }
1110
public string EventHubName { get { throw null; } }
1211
}
1312
[Microsoft.Azure.WebJobs.Description.BindingAttribute]
1413
[System.AttributeUsageAttribute(System.AttributeTargets.Parameter)]
1514
public sealed partial class EventHubTriggerAttribute : System.Attribute
1615
{
1716
public EventHubTriggerAttribute(string eventHubName) { }
17+
[Microsoft.Azure.WebJobs.Description.AutoResolveAttribute]
1818
public string Connection { get { throw null; } set { } }
19+
[Microsoft.Azure.WebJobs.Description.AutoResolveAttribute]
1920
public string ConsumerGroup { get { throw null; } set { } }
2021
public string EventHubName { get { throw null; } }
2122
}
@@ -30,8 +31,6 @@ public EventHubOptions() { }
3031
public bool InvokeProcessorAfterReceiveTimeout { get { throw null; } set { } }
3132
public string LeaseContainerName { get { throw null; } set { } }
3233
public int MaxBatchSize { get { throw null; } set { } }
33-
public void AddEventHubProducerClient(Azure.Messaging.EventHubs.Producer.EventHubProducerClient client) { }
34-
public void AddEventHubProducerClient(string eventHubName, Azure.Messaging.EventHubs.Producer.EventHubProducerClient client) { }
3534
public void AddReceiver(string eventHubName, string receiverConnectionString) { }
3635
public void AddReceiver(string eventHubName, string receiverConnectionString, string storageConnectionString) { }
3736
public void AddSender(string eventHubName, string sendConnectionString) { }
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Collections.Concurrent;
6+
using Azure.Core;
7+
using Azure.Messaging.EventHubs.Consumer;
8+
using Azure.Messaging.EventHubs.Producer;
9+
using Azure.Storage.Blobs;
10+
using Microsoft.Azure.WebJobs.EventHubs.Processor;
11+
using Microsoft.Azure.WebJobs.Extensions.Clients.Shared;
12+
using Microsoft.Azure.WebJobs.Host;
13+
using Microsoft.Extensions.Azure;
14+
using Microsoft.Extensions.Configuration;
15+
using Microsoft.Extensions.Options;
16+
17+
namespace Microsoft.Azure.WebJobs.EventHubs
18+
{
19+
internal class EventHubClientFactory
20+
{
21+
private readonly IConfiguration _configuration;
22+
private readonly AzureComponentFactory _componentFactory;
23+
private readonly EventHubOptions _options;
24+
private readonly INameResolver _nameResolver;
25+
private readonly ConcurrentDictionary<string, EventHubProducerClient> _producerCache;
26+
private readonly ConcurrentDictionary<string, IEventHubConsumerClient> _consumerCache = new ();
27+
28+
public EventHubClientFactory(
29+
IConfiguration configuration,
30+
AzureComponentFactory componentFactory,
31+
IOptions<EventHubOptions> options,
32+
INameResolver nameResolver)
33+
{
34+
_configuration = configuration;
35+
_componentFactory = componentFactory;
36+
_options = options.Value;
37+
_nameResolver = nameResolver;
38+
_producerCache = new ConcurrentDictionary<string, EventHubProducerClient>(_options.RegisteredProducers);
39+
}
40+
41+
internal EventHubProducerClient GetEventHubProducerClient(string eventHubName, string connection)
42+
{
43+
eventHubName = _nameResolver.ResolveWholeString(eventHubName);
44+
45+
return _producerCache.GetOrAdd(eventHubName, key =>
46+
{
47+
if (!string.IsNullOrWhiteSpace(connection))
48+
{
49+
var info = ResolveConnectionInformation(connection);
50+
51+
if (info.FullyQualifiedEndpoint != null &&
52+
info.TokenCredential != null)
53+
{
54+
return new EventHubProducerClient(info.FullyQualifiedEndpoint, eventHubName, info.TokenCredential);
55+
}
56+
57+
return new EventHubProducerClient(NormalizeConnectionString(info.ConnectionString, eventHubName));
58+
}
59+
60+
throw new InvalidOperationException("No event hub sender named " + eventHubName);
61+
});
62+
}
63+
64+
internal EventProcessorHost GetEventProcessorHost(string eventHubName, string connection, string consumerGroup)
65+
{
66+
eventHubName = _nameResolver.ResolveWholeString(eventHubName);
67+
consumerGroup ??= EventHubConsumerClient.DefaultConsumerGroupName;
68+
69+
if (_options.RegisteredConsumerCredentials.TryGetValue(eventHubName, out var creds))
70+
{
71+
return new EventProcessorHost(consumerGroup: consumerGroup,
72+
connectionString: creds.EventHubConnectionString,
73+
eventHubName: eventHubName,
74+
options: _options.EventProcessorOptions,
75+
eventBatchMaximumCount: _options.MaxBatchSize,
76+
invokeProcessorAfterReceiveTimeout: _options.InvokeProcessorAfterReceiveTimeout,
77+
exceptionHandler: _options.ExceptionHandler);
78+
}
79+
else if (!string.IsNullOrEmpty(connection))
80+
{
81+
var info = ResolveConnectionInformation(connection);
82+
83+
if (info.FullyQualifiedEndpoint != null &&
84+
info.TokenCredential != null)
85+
{
86+
return new EventProcessorHost(consumerGroup: consumerGroup,
87+
fullyQualifiedNamespace: info.FullyQualifiedEndpoint,
88+
eventHubName: eventHubName,
89+
credential: info.TokenCredential,
90+
options: _options.EventProcessorOptions,
91+
eventBatchMaximumCount: _options.MaxBatchSize,
92+
invokeProcessorAfterReceiveTimeout: _options.InvokeProcessorAfterReceiveTimeout,
93+
exceptionHandler: _options.ExceptionHandler);
94+
}
95+
96+
return new EventProcessorHost(consumerGroup: consumerGroup,
97+
connectionString: NormalizeConnectionString(info.ConnectionString, eventHubName),
98+
eventHubName: eventHubName,
99+
options: _options.EventProcessorOptions,
100+
eventBatchMaximumCount: _options.MaxBatchSize,
101+
invokeProcessorAfterReceiveTimeout: _options.InvokeProcessorAfterReceiveTimeout,
102+
exceptionHandler: _options.ExceptionHandler);
103+
}
104+
105+
throw new InvalidOperationException("No event hub receiver named " + eventHubName);
106+
}
107+
108+
internal IEventHubConsumerClient GetEventHubConsumerClient(string eventHubName, string connection, string consumerGroup)
109+
{
110+
eventHubName = _nameResolver.ResolveWholeString(eventHubName);
111+
consumerGroup ??= EventHubConsumerClient.DefaultConsumerGroupName;
112+
113+
return _consumerCache.GetOrAdd(eventHubName, name =>
114+
{
115+
EventHubConsumerClient client = null;
116+
if (_options.RegisteredConsumerCredentials.TryGetValue(eventHubName, out var creds))
117+
{
118+
client = new EventHubConsumerClient(consumerGroup, creds.EventHubConnectionString, eventHubName);
119+
}
120+
else if (!string.IsNullOrEmpty(connection))
121+
{
122+
var info = ResolveConnectionInformation(connection);
123+
124+
if (info.FullyQualifiedEndpoint != null &&
125+
info.TokenCredential != null)
126+
{
127+
client = new EventHubConsumerClient(consumerGroup, info.FullyQualifiedEndpoint, eventHubName, info.TokenCredential);
128+
}
129+
else
130+
{
131+
client = new EventHubConsumerClient(consumerGroup, NormalizeConnectionString(info.ConnectionString, eventHubName));
132+
}
133+
}
134+
135+
if (client != null)
136+
{
137+
return new EventHubConsumerClientImpl(client);
138+
}
139+
140+
throw new InvalidOperationException("No event hub receiver named " + eventHubName);
141+
});
142+
}
143+
144+
internal BlobContainerClient GetCheckpointStoreClient(string eventHubName)
145+
{
146+
string storageConnectionString = null;
147+
if (_options.RegisteredConsumerCredentials.TryGetValue(eventHubName, out var creds))
148+
{
149+
storageConnectionString = creds.StorageConnectionString;
150+
}
151+
152+
// Fall back to default if not explicitly registered
153+
return new BlobContainerClient(storageConnectionString ?? _configuration.GetWebJobsConnectionString(ConnectionStringNames.Storage), _options.LeaseContainerName);
154+
}
155+
156+
internal static string NormalizeConnectionString(string originalConnectionString, string eventHubName)
157+
{
158+
var connectionString = ConnectionString.Parse(originalConnectionString);
159+
160+
if (!connectionString.ContainsSegmentKey("EntityPath"))
161+
{
162+
connectionString.Add("EntityPath", eventHubName);
163+
}
164+
165+
return connectionString.ToString();
166+
}
167+
168+
private EventHubsConnectionInformation ResolveConnectionInformation(string connection)
169+
{
170+
IConfigurationSection connectionSection = _configuration.GetWebJobsConnectionStringSection(connection);
171+
if (!connectionSection.Exists())
172+
{
173+
// Not found
174+
throw new InvalidOperationException($"EventHub account connection string '{connection}' does not exist." +
175+
$"Make sure that it is a defined App Setting.");
176+
}
177+
178+
if (!string.IsNullOrWhiteSpace(connectionSection.Value))
179+
{
180+
return new EventHubsConnectionInformation(connectionSection.Value);
181+
}
182+
183+
var fullyQualifiedNamespace = connectionSection["fullyQualifiedNamespace"];
184+
if (string.IsNullOrWhiteSpace(fullyQualifiedNamespace))
185+
{
186+
// Not found
187+
throw new InvalidOperationException($"Connection should have an 'fullyQualifiedNamespace' property or be a string representing a connection string.");
188+
}
189+
190+
var credential = _componentFactory.CreateTokenCredential(connectionSection);
191+
192+
return new EventHubsConnectionInformation(fullyQualifiedNamespace, credential);
193+
}
194+
195+
private record EventHubsConnectionInformation
196+
{
197+
public EventHubsConnectionInformation(string connectionString)
198+
{
199+
ConnectionString = connectionString;
200+
}
201+
202+
public EventHubsConnectionInformation(string fullyQualifiedEndpoint, TokenCredential tokenCredential)
203+
{
204+
FullyQualifiedEndpoint = fullyQualifiedEndpoint;
205+
TokenCredential = tokenCredential;
206+
}
207+
208+
public string ConnectionString { get; }
209+
public string FullyQualifiedEndpoint { get; }
210+
public TokenCredential TokenCredential { get; }
211+
}
212+
}
213+
}

sdk/eventhub/Microsoft.Azure.WebJobs.Extensions.EventHubs/src/Config/EventHubExtensionConfigProvider.cs

Lines changed: 13 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,24 @@ namespace Microsoft.Azure.WebJobs.EventHubs
2222
[Extension("EventHubs", configurationSection: "EventHubs")]
2323
internal class EventHubExtensionConfigProvider : IExtensionConfigProvider
2424
{
25-
private IConfiguration _config;
2625
private readonly IOptions<EventHubOptions> _options;
2726
private readonly ILoggerFactory _loggerFactory;
2827
private readonly IConverterManager _converterManager;
29-
private readonly INameResolver _nameResolver;
3028
private readonly IWebJobsExtensionConfiguration<EventHubExtensionConfigProvider> _configuration;
31-
32-
public EventHubExtensionConfigProvider(IConfiguration config, IOptions<EventHubOptions> options, ILoggerFactory loggerFactory,
33-
IConverterManager converterManager, INameResolver nameResolver, IWebJobsExtensionConfiguration<EventHubExtensionConfigProvider> configuration)
29+
private readonly EventHubClientFactory _clientFactory;
30+
31+
public EventHubExtensionConfigProvider(
32+
IOptions<EventHubOptions> options,
33+
ILoggerFactory loggerFactory,
34+
IConverterManager converterManager,
35+
IWebJobsExtensionConfiguration<EventHubExtensionConfigProvider> configuration,
36+
EventHubClientFactory clientFactory)
3437
{
35-
_config = config;
3638
_options = options;
3739
_loggerFactory = loggerFactory;
3840
_converterManager = converterManager;
39-
_nameResolver = nameResolver;
4041
_configuration = configuration;
42+
_clientFactory = clientFactory;
4143
}
4244

4345
internal Action<ExceptionReceivedEventArgs> ExceptionHandler { get; set; }
@@ -54,7 +56,7 @@ public void Initialize(ExtensionConfigContext context)
5456
throw new ArgumentNullException(nameof(context));
5557
}
5658

57-
_options.Value.SetExceptionHandler(ExceptionReceivedHandler);
59+
_options.Value.ExceptionHandler = ExceptionReceivedHandler;
5860
_configuration.ConfigurationSection.Bind(_options);
5961

6062
context
@@ -65,7 +67,7 @@ public void Initialize(ExtensionConfigContext context)
6567
.AddOpenConverter<OpenType.Poco, EventData>(ConvertPocoToEventData);
6668

6769
// register our trigger binding provider
68-
var triggerBindingProvider = new EventHubTriggerAttributeBindingProvider(_config, _nameResolver, _converterManager, _options, _loggerFactory);
70+
var triggerBindingProvider = new EventHubTriggerAttributeBindingProvider(_converterManager, _options, _loggerFactory, _clientFactory);
6971
context.AddBindingRule<EventHubTriggerAttribute>()
7072
.BindToTrigger(triggerBindingProvider);
7173

@@ -74,10 +76,7 @@ public void Initialize(ExtensionConfigContext context)
7476
.BindToCollector(BuildFromAttribute);
7577

7678
context.AddBindingRule<EventHubAttribute>()
77-
.BindToInput(attribute =>
78-
{
79-
return _options.Value.GetEventHubProducerClient(attribute.EventHubName, attribute.Connection);
80-
});
79+
.BindToInput(attribute => _clientFactory.GetEventHubProducerClient(attribute.EventHubName, attribute.Connection));
8180

8281
ExceptionHandler = (e =>
8382
{
@@ -93,26 +92,9 @@ internal static void LogExceptionReceivedEvent(ExceptionReceivedEventArgs e, ILo
9392
Utility.LogException(e.Exception, message, logger);
9493
}
9594

96-
private static LogLevel GetLogLevel(Exception ex)
97-
{
98-
var ehex = ex as EventHubsException;
99-
if (!(ex is OperationCanceledException) && (ehex == null || !ehex.IsTransient))
100-
{
101-
// any non-transient exceptions or unknown exception types
102-
// we want to log as errors
103-
return LogLevel.Error;
104-
}
105-
else
106-
{
107-
// transient messaging errors we log as info so we have a record
108-
// of them, but we don't treat them as actual errors
109-
return LogLevel.Information;
110-
}
111-
}
112-
11395
private IAsyncCollector<EventData> BuildFromAttribute(EventHubAttribute attribute)
11496
{
115-
EventHubProducerClient client = _options.Value.GetEventHubProducerClient(attribute.EventHubName, attribute.Connection);
97+
EventHubProducerClient client = _clientFactory.GetEventHubProducerClient(attribute.EventHubName, attribute.Connection);
11698
return new EventHubAsyncCollector(new EventHubProducerClientImpl(client, _loggerFactory));
11799
}
118100

0 commit comments

Comments
 (0)