From 6bdd71325db493f50ac9b9bcfcb1224dc4116707 Mon Sep 17 00:00:00 2001 From: David Burg Date: Wed, 6 May 2026 22:39:08 -0700 Subject: [PATCH 1/2] Update DI to use shared credential with AzureCliCredential for local dev - Move ManagedIdentityClientId from per-connector to shared ConnectorOptions - Add UseAzureCliCredential option for local development - Register TokenCredential as singleton in DI (AzureCliCredential for local, ManagedIdentityCredential for Azure) - Simplify client registration to pass credential explicitly - Remove per-connector ManagedIdentityClientId properties Companion to Azure/Connectors-NET-SDK#112 --- .../Configuration/ConnectorOptions.cs | 132 ++++++++++++++++++ DirectConnector/Program.cs | 105 ++++++-------- 2 files changed, 178 insertions(+), 59 deletions(-) diff --git a/DirectConnector/Configuration/ConnectorOptions.cs b/DirectConnector/Configuration/ConnectorOptions.cs index 4e57886..ccfe8e3 100644 --- a/DirectConnector/Configuration/ConnectorOptions.cs +++ b/DirectConnector/Configuration/ConnectorOptions.cs @@ -17,6 +17,18 @@ public class ConnectorOptions /// public const string SectionName = "Connectors"; + /// + /// When true, use for local development. + /// Set to true in local.settings.json for local dev; leave false (default) in Azure. + /// + public bool UseAzureCliCredential { get; set; } + + /// + /// Managed identity client ID for user-assigned identity. + /// Leave unset (null or empty) for system-assigned managed identity (default). + /// + public string? ManagedIdentityClientId { get; set; } + /// /// Office365 connector options. /// @@ -78,6 +90,126 @@ public class ConnectorOptions public AzureLogAnalyticsOptions AzureLogAnalytics { get; set; } = new AzureLogAnalyticsOptions(); } +/// +/// Configuration options for the Microsoft Teams connector. +/// +public class TeamsOptions +{ + /// + /// The API connection runtime URL for Microsoft Teams. + /// + [Required(ErrorMessage = "Teams ConnectionRuntimeUrl is required.")] + public string ConnectionRuntimeUrl { get; set; } = string.Empty; +} + +/// +/// Configuration options for the Office365 connector. +/// +public class Office365Options +{ + /// + /// The API connection runtime URL for Office365. + /// + [Required(ErrorMessage = "Office365 ConnectionRuntimeUrl is required.")] + public string ConnectionRuntimeUrl { get; set; } = string.Empty; +} + +/// +/// Configuration options for the SharePoint Online connector. +/// +public class SharePointOptions +{ + /// + /// The API connection runtime URL for SharePoint Online. + /// + [Required(ErrorMessage = "SharePoint ConnectionRuntimeUrl is required.")] + public string ConnectionRuntimeUrl { get; set; } = string.Empty; +} + +/// +/// Configuration options for the OneDrive for Business connector. +/// +public class OneDriveOptions +{ + /// + /// The API connection runtime URL for OneDrive for Business. + /// + [Required(ErrorMessage = "OneDrive ConnectionRuntimeUrl is required.")] + public string ConnectionRuntimeUrl { get; set; } = string.Empty; +} + +/// +/// Configuration options for the MS Graph Groups and Users connector. +/// +public class MsGraphOptions +{ + /// + /// The API connection runtime URL for MS Graph Groups and Users. + /// + [Required(ErrorMessage = "Connectors:MsGraph:ConnectionRuntimeUrl is required.")] + public string ConnectionRuntimeUrl { get; set; } = string.Empty; +} + +/// +/// Configuration options for the Azure Blob Storage connector. +/// +public class AzureBlobOptions +{ + /// + /// The API connection runtime URL for Azure Blob Storage. + /// + [Required(ErrorMessage = "Connectors:AzureBlob:ConnectionRuntimeUrl is required.")] + public string ConnectionRuntimeUrl { get; set; } = string.Empty; +} + +/// +/// Configuration options for the SMTP connector. +/// +public class SmtpOptions +{ + /// + /// The API connection runtime URL for SMTP. + /// + [Required(ErrorMessage = "Connectors:Smtp:ConnectionRuntimeUrl is required.")] + public string ConnectionRuntimeUrl { get; set; } = string.Empty; +} + +/// +/// Configuration options for the IBM MQ connector. +/// +public class MqOptions +{ + /// + /// The API connection runtime URL for IBM MQ. + /// + [Required(ErrorMessage = "Connectors:Mq:ConnectionRuntimeUrl is required.")] + public string ConnectionRuntimeUrl { get; set; } = string.Empty; +} + +/// +/// Configuration options for the Office 365 Users connector. +/// +public class Office365UsersOptions +{ + /// + /// The API connection runtime URL for Office 365 Users. + /// + [Required(ErrorMessage = "Connectors:Office365Users:ConnectionRuntimeUrl is required.")] + public string ConnectionRuntimeUrl { get; set; } = string.Empty; +} + +/// +/// Configuration options for the Azure Log Analytics connector. +/// +public class AzureLogAnalyticsOptions +{ + /// + /// The API connection runtime URL for Azure Log Analytics. + /// + [Required(ErrorMessage = "Connectors:AzureLogAnalytics:ConnectionRuntimeUrl is required.")] + public string ConnectionRuntimeUrl { get; set; } = string.Empty; +} + /// /// Configuration options for the Microsoft Teams connector. /// diff --git a/DirectConnector/Program.cs b/DirectConnector/Program.cs index e88984c..0888389 100644 --- a/DirectConnector/Program.cs +++ b/DirectConnector/Program.cs @@ -2,6 +2,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ +using Azure.Core; +using Azure.Identity; using DirectConnector.Configuration; using Microsoft.Azure.Connectors.DirectClient.Azureblob; using Microsoft.Azure.Connectors.DirectClient.Azureloganalytics; @@ -35,126 +37,111 @@ services.AddHttpClient(); + // NOTE: Resolve the credential once for all connector clients. + // In Azure: defaults to ManagedIdentityCredential (system-assigned). + // For user-assigned MSI: set ManagedIdentityClientId in configuration. + // For local dev: set UseAzureCliCredential=true in local.settings.json. + services.AddSingleton(serviceProvider => + { + var options = serviceProvider.GetRequiredService>().Value; + + if (options.UseAzureCliCredential) + { + return new AzureCliCredential(); + } + + if (!string.IsNullOrEmpty(options.ManagedIdentityClientId)) + { + return new ManagedIdentityCredential( + ManagedIdentityId.FromUserAssignedClientId(options.ManagedIdentityClientId)); + } + + return new ManagedIdentityCredential(ManagedIdentityId.SystemAssigned); + }); + // NOTE: Register generated connector clients as singletons. - // The factory overload lets DI own the instance lifetime and call Dispose, - // which exercises the ownership-based disposal pattern: the client will - // dispose its internally-created HttpClient and DefaultAzureCredential. + // The factory overload lets DI own the instance lifetime and call Dispose. + // All clients share the same credential resolved above. // NOTE: Validation of ConnectionRuntimeUrl is handled by // [Required] attribute + ValidateOnStart() at host initialization. services.AddSingleton(serviceProvider => { var options = serviceProvider.GetRequiredService>().Value; + var credential = serviceProvider.GetRequiredService(); - // NOTE: When ManagedIdentityClientId is null, use the default constructor so - // the client relies on DefaultAzureCredential. When ManagedIdentityClientId is non-null - // (empty string = system-assigned MSI, non-empty = user-assigned MSI), use the MSI constructor. - return options.Office365.ManagedIdentityClientId != null - ? new Office365Client( - options.Office365.ConnectionRuntimeUrl, - options.Office365.ManagedIdentityClientId) - : new Office365Client(options.Office365.ConnectionRuntimeUrl); + return new Office365Client(options.Office365.ConnectionRuntimeUrl, credential); }); services.AddSingleton(serviceProvider => { var options = serviceProvider.GetRequiredService>().Value; + var credential = serviceProvider.GetRequiredService(); - // NOTE: When ManagedIdentityClientId is null, use the default constructor so - // the client relies on DefaultAzureCredential. When ManagedIdentityClientId is non-null - // (empty string = system-assigned MSI, non-empty = user-assigned MSI), use the MSI constructor. - return options.SharePoint.ManagedIdentityClientId != null - ? new SharepointonlineClient( - options.SharePoint.ConnectionRuntimeUrl, - options.SharePoint.ManagedIdentityClientId) - : new SharepointonlineClient(options.SharePoint.ConnectionRuntimeUrl); + return new SharepointonlineClient(options.SharePoint.ConnectionRuntimeUrl, credential); }); services.AddSingleton(serviceProvider => { var options = serviceProvider.GetRequiredService>().Value; + var credential = serviceProvider.GetRequiredService(); - return options.Teams.ManagedIdentityClientId != null - ? new TeamsClient( - options.Teams.ConnectionRuntimeUrl, - options.Teams.ManagedIdentityClientId) - : new TeamsClient(options.Teams.ConnectionRuntimeUrl); + return new TeamsClient(options.Teams.ConnectionRuntimeUrl, credential); }); services.AddSingleton(serviceProvider => { var options = serviceProvider.GetRequiredService>().Value; + var credential = serviceProvider.GetRequiredService(); - return options.OneDrive.ManagedIdentityClientId != null - ? new OnedriveforbusinessClient( - options.OneDrive.ConnectionRuntimeUrl, - options.OneDrive.ManagedIdentityClientId) - : new OnedriveforbusinessClient(options.OneDrive.ConnectionRuntimeUrl); + return new OnedriveforbusinessClient(options.OneDrive.ConnectionRuntimeUrl, credential); }); services.AddSingleton(serviceProvider => { var options = serviceProvider.GetRequiredService>().Value; + var credential = serviceProvider.GetRequiredService(); - return options.MsGraph.ManagedIdentityClientId != null - ? new MsgraphgroupsanduserClient( - options.MsGraph.ConnectionRuntimeUrl, - options.MsGraph.ManagedIdentityClientId) - : new MsgraphgroupsanduserClient(options.MsGraph.ConnectionRuntimeUrl); + return new MsgraphgroupsanduserClient(options.MsGraph.ConnectionRuntimeUrl, credential); }); services.AddSingleton(serviceProvider => { var options = serviceProvider.GetRequiredService>().Value; + var credential = serviceProvider.GetRequiredService(); - return options.AzureBlob.ManagedIdentityClientId != null - ? new AzureblobClient( - options.AzureBlob.ConnectionRuntimeUrl, - options.AzureBlob.ManagedIdentityClientId) - : new AzureblobClient(options.AzureBlob.ConnectionRuntimeUrl); + return new AzureblobClient(options.AzureBlob.ConnectionRuntimeUrl, credential); }); services.AddSingleton(serviceProvider => { var options = serviceProvider.GetRequiredService>().Value; + var credential = serviceProvider.GetRequiredService(); - return options.Smtp.ManagedIdentityClientId != null - ? new SmtpClient( - options.Smtp.ConnectionRuntimeUrl, - options.Smtp.ManagedIdentityClientId) - : new SmtpClient(options.Smtp.ConnectionRuntimeUrl); + return new SmtpClient(options.Smtp.ConnectionRuntimeUrl, credential); }); services.AddSingleton(serviceProvider => { var options = serviceProvider.GetRequiredService>().Value; + var credential = serviceProvider.GetRequiredService(); - return options.Mq.ManagedIdentityClientId != null - ? new MqClient( - options.Mq.ConnectionRuntimeUrl, - options.Mq.ManagedIdentityClientId) - : new MqClient(options.Mq.ConnectionRuntimeUrl); + return new MqClient(options.Mq.ConnectionRuntimeUrl, credential); }); services.AddSingleton(serviceProvider => { var options = serviceProvider.GetRequiredService>().Value; + var credential = serviceProvider.GetRequiredService(); - return options.Office365Users.ManagedIdentityClientId != null - ? new Office365usersClient( - options.Office365Users.ConnectionRuntimeUrl, - options.Office365Users.ManagedIdentityClientId) - : new Office365usersClient(options.Office365Users.ConnectionRuntimeUrl); + return new Office365usersClient(options.Office365Users.ConnectionRuntimeUrl, credential); }); services.AddSingleton(serviceProvider => { var options = serviceProvider.GetRequiredService>().Value; + var credential = serviceProvider.GetRequiredService(); - return options.AzureLogAnalytics.ManagedIdentityClientId != null - ? new AzureloganalyticsClient( - options.AzureLogAnalytics.ConnectionRuntimeUrl, - options.AzureLogAnalytics.ManagedIdentityClientId) - : new AzureloganalyticsClient(options.AzureLogAnalytics.ConnectionRuntimeUrl); + return new AzureloganalyticsClient(options.AzureLogAnalytics.ConnectionRuntimeUrl, credential); }); }) .Build(); From 965cf34a5d3b5902e487d7f3af7ef2fc43e00596 Mon Sep 17 00:00:00 2001 From: David Burg Date: Thu, 7 May 2026 01:10:04 -0700 Subject: [PATCH 2/2] Remove duplicate connector option type definitions Co-authored-by: Dobby --- .../Configuration/ConnectorOptions.cs | 190 ------------------ 1 file changed, 190 deletions(-) diff --git a/DirectConnector/Configuration/ConnectorOptions.cs b/DirectConnector/Configuration/ConnectorOptions.cs index ccfe8e3..337793d 100644 --- a/DirectConnector/Configuration/ConnectorOptions.cs +++ b/DirectConnector/Configuration/ConnectorOptions.cs @@ -209,193 +209,3 @@ public class AzureLogAnalyticsOptions [Required(ErrorMessage = "Connectors:AzureLogAnalytics:ConnectionRuntimeUrl is required.")] public string ConnectionRuntimeUrl { get; set; } = string.Empty; } - -/// -/// Configuration options for the Microsoft Teams connector. -/// -public class TeamsOptions -{ - /// - /// The API connection runtime URL for Microsoft Teams. - /// - [Required(ErrorMessage = "Teams ConnectionRuntimeUrl is required.")] - public string ConnectionRuntimeUrl { get; set; } = string.Empty; - - /// - /// Managed identity client ID for user-assigned identity. - /// Set to empty string for system-assigned managed identity. - /// Leave unset (null) to use the DefaultAzureCredential chain (CLI, env vars, etc.). - /// - public string? ManagedIdentityClientId { get; set; } -} - -/// -/// Configuration options for the Office365 connector. -/// -public class Office365Options -{ - /// - /// The API connection runtime URL for Office365. - /// - [Required(ErrorMessage = "Office365 ConnectionRuntimeUrl is required.")] - public string ConnectionRuntimeUrl { get; set; } = string.Empty; - - /// - /// Managed identity client ID for user-assigned identity. - /// Set to empty string for system-assigned managed identity. - /// Leave unset (null) to use the DefaultAzureCredential chain (CLI, env vars, etc.). - /// - public string? ManagedIdentityClientId { get; set; } -} - -/// -/// Configuration options for the SharePoint Online connector. -/// -public class SharePointOptions -{ - /// - /// The API connection runtime URL for SharePoint Online. - /// - [Required(ErrorMessage = "SharePoint ConnectionRuntimeUrl is required.")] - public string ConnectionRuntimeUrl { get; set; } = string.Empty; - - /// - /// Managed identity client ID for user-assigned identity. - /// Set to empty string for system-assigned managed identity. - /// Leave unset (null) to use the DefaultAzureCredential chain (CLI, env vars, etc.). - /// - public string? ManagedIdentityClientId { get; set; } -} - -/// -/// Configuration options for the OneDrive for Business connector. -/// -public class OneDriveOptions -{ - /// - /// The API connection runtime URL for OneDrive for Business. - /// - [Required(ErrorMessage = "OneDrive ConnectionRuntimeUrl is required.")] - public string ConnectionRuntimeUrl { get; set; } = string.Empty; - - /// - /// Managed identity client ID for user-assigned identity. - /// Set to empty string for system-assigned managed identity. - /// Leave unset (null) to use the DefaultAzureCredential chain (CLI, env vars, etc.). - /// - public string? ManagedIdentityClientId { get; set; } -} - -/// -/// Configuration options for the MS Graph Groups and Users connector. -/// -public class MsGraphOptions -{ - /// - /// The API connection runtime URL for MS Graph Groups and Users. - /// - [Required(ErrorMessage = "Connectors:MsGraph:ConnectionRuntimeUrl is required.")] - public string ConnectionRuntimeUrl { get; set; } = string.Empty; - - /// - /// Managed identity client ID for user-assigned identity. - /// Set to empty string for system-assigned managed identity. - /// Leave unset (null) to use the DefaultAzureCredential chain (CLI, env vars, etc.). - /// - public string? ManagedIdentityClientId { get; set; } -} - -/// -/// Configuration options for the Azure Blob Storage connector. -/// -public class AzureBlobOptions -{ - /// - /// The API connection runtime URL for Azure Blob Storage. - /// - [Required(ErrorMessage = "Connectors:AzureBlob:ConnectionRuntimeUrl is required.")] - public string ConnectionRuntimeUrl { get; set; } = string.Empty; - - /// - /// Managed identity client ID for user-assigned identity. - /// Set to empty string for system-assigned managed identity. - /// Leave unset (null) to use the DefaultAzureCredential chain (CLI, env vars, etc.). - /// - public string? ManagedIdentityClientId { get; set; } -} - -/// -/// Configuration options for the SMTP connector. -/// -public class SmtpOptions -{ - /// - /// The API connection runtime URL for SMTP. - /// - [Required(ErrorMessage = "Connectors:Smtp:ConnectionRuntimeUrl is required.")] - public string ConnectionRuntimeUrl { get; set; } = string.Empty; - - /// - /// Managed identity client ID for user-assigned identity. - /// Set to empty string for system-assigned managed identity. - /// Leave unset (null) to use the DefaultAzureCredential chain (CLI, env vars, etc.). - /// - public string? ManagedIdentityClientId { get; set; } -} - -/// -/// Configuration options for the IBM MQ connector. -/// -public class MqOptions -{ - /// - /// The API connection runtime URL for IBM MQ. - /// - [Required(ErrorMessage = "Connectors:Mq:ConnectionRuntimeUrl is required.")] - public string ConnectionRuntimeUrl { get; set; } = string.Empty; - - /// - /// Managed identity client ID for user-assigned identity. - /// Set to empty string for system-assigned managed identity. - /// Leave unset (null) to use the DefaultAzureCredential chain (CLI, env vars, etc.). - /// - public string? ManagedIdentityClientId { get; set; } -} - -/// -/// Configuration options for the Office 365 Users connector. -/// -public class Office365UsersOptions -{ - /// - /// The API connection runtime URL for Office 365 Users. - /// - [Required(ErrorMessage = "Connectors:Office365Users:ConnectionRuntimeUrl is required.")] - public string ConnectionRuntimeUrl { get; set; } = string.Empty; - - /// - /// Managed identity client ID for user-assigned identity. - /// Set to empty string for system-assigned managed identity. - /// Leave unset (null) to use the DefaultAzureCredential chain (CLI, env vars, etc.). - /// - public string? ManagedIdentityClientId { get; set; } -} - -/// -/// Configuration options for the Azure Log Analytics connector. -/// -public class AzureLogAnalyticsOptions -{ - /// - /// The API connection runtime URL for Azure Log Analytics. - /// - [Required(ErrorMessage = "Connectors:AzureLogAnalytics:ConnectionRuntimeUrl is required.")] - public string ConnectionRuntimeUrl { get; set; } = string.Empty; - - /// - /// Managed identity client ID for user-assigned identity. - /// Set to empty string for system-assigned managed identity. - /// Leave unset (null) to use the DefaultAzureCredential chain (CLI, env vars, etc.). - /// - public string? ManagedIdentityClientId { get; set; } -}