Skip to content

Commit

Permalink
Merge pull request #2524 from microsoft/ccastro/cert-auth
Browse files Browse the repository at this point in the history
Certificate authentication: Enable cert auth and start the shift away from ICredentialProvider.
  • Loading branch information
carlosscastro authored Sep 18, 2019
2 parents b7a5e42 + 5413a9e commit d078226
Show file tree
Hide file tree
Showing 13 changed files with 613 additions and 201 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
-----BEGIN CERTIFICATE-----
MIIIvjCCBqagAwIBAgITIAAJ5+kZen6NQMkNHwAAAAnn6TANBgkqhkiG9w0BAQsF
ADCBizELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT
B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEVMBMGA1UE
CxMMTWljcm9zb2Z0IElUMR4wHAYDVQQDExVNaWNyb3NvZnQgSVQgVExTIENBIDIw
HhcNMTkwODIzMTgyNTA1WhcNMjEwODIzMTgyNTA1WjAlMSMwIQYDVQQDExp0ZXN0
LmF1dGguYm90ZnJhbWV3b3JrLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBANZJFZHzfW8L9R0Iqd3DydCDHGhELNgr7ijO9nI/swl1v4rBlKWV24ML
O0zxRmQhddpqSoWPFeF+MDo/OLv+FLwTexUC+ajf7iMiX7G+oG1PS9W4IU1B9tRR
8w4Dn7rkAhrRZUZOkdyE1Y/mTogDslJuQUWtVndrwFO+QA4Yu21KvZo/bq9CBBDE
CCWPWsZ27yVEqCKnx7+IKzmYUi1ukB9qWReahcLCckPoL5vQjNjNRIKWdSO36Mck
fRks92mXI5DgKJcJoSN7k6DfauSTytvrnlMd99WSieQXJ8zT/rUKd2Vi48ugjNWp
Dn/C529rlwgPGCL5DtXwevB/ByYi8rECAwEAAaOCBH4wggR6MIIB9AYKKwYBBAHW
eQIEAgSCAeQEggHgAd4AdgB9PvL4j/+IVWgkwsDKnlKJeSvFDngJfy5ql2iZfiLw
1wAAAWy/wsRqAAAEAwBHMEUCIEyrS3y+oHZMXGD5w6Z4pq7WDFYYEKoBUzHq4wAH
sqy4AiEAk3lGjhbE90OW5JNZWYp72UULgAG3cwPueBGbtJrv8wEAdQBc3EOS/uar
RUSxXprUVuYQN/vV+kfcoXOUsl7m9scOygAAAWy/wsTiAAAEAwBGMEQCICYW5kwo
K5ddS+z435yg1r2zXrLPrMP2/b7i+L7co8xmAiATm5PJmS7OTC+gDNfGNtXXNB/K
qA2fBiuJeRRqsGWTeQB1AESUZS6w7s6vxEAH2Kj+KMDa5oK+2MsxtT/TM5a1toGo
AAABbL/Cw/EAAAQDAEYwRAIgdh73y6qInn82ZQk28zH+ddJKojS1NwrkARveu0je
UpwCIHUKb0a7JgUWlmOstrO5oRwGd512hfVkUItxMUqHhqvtAHYA7ku9t3XOYLrh
Qmkfq+GeZqMPfl+wctiDAMR7iXqo/csAAAFsv8LEawAABAMARzBFAiEA59w8fd9g
0MslZsKHRpMmaJiwEgZzfxxpTQ12TK7izBACIDx5+pVPWry1hRTI1EcJJxl23v7G
bpv1/1oHO5QhNDeQMCcGCSsGAQQBgjcVCgQaMBgwCgYIKwYBBQUHAwIwCgYIKwYB
BQUHAwEwPgYJKwYBBAGCNxUHBDEwLwYnKwYBBAGCNxUIh9qGdYPu2QGCyYUbgbWe
YYX062CBXYTS30KC55N6AgFkAgEdMIGFBggrBgEFBQcBAQR5MHcwUQYIKwYBBQUH
MAKGRWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvbXNjb3JwL01pY3Jvc29m
dCUyMElUJTIwVExTJTIwQ0ElMjAyLmNydDAiBggrBgEFBQcwAYYWaHR0cDovL29j
c3AubXNvY3NwLmNvbTAdBgNVHQ4EFgQUubs1xOZtHFIMNVz6xisEQO1FT+AwCwYD
VR0PBAQDAgSwMCUGA1UdEQQeMByCGnRlc3QuYXV0aC5ib3RmcmFtZXdvcmsuY29t
MIGsBgNVHR8EgaQwgaEwgZ6ggZuggZiGS2h0dHA6Ly9tc2NybC5taWNyb3NvZnQu
Y29tL3BraS9tc2NvcnAvY3JsL01pY3Jvc29mdCUyMElUJTIwVExTJTIwQ0ElMjAy
LmNybIZJaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9tc2NvcnAvY3JsL01p
Y3Jvc29mdCUyMElUJTIwVExTJTIwQ0ElMjAyLmNybDBNBgNVHSAERjBEMEIGCSsG
AQQBgjcqATA1MDMGCCsGAQUFBwIBFidodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20v
cGtpL21zY29ycC9jcHMwHwYDVR0jBBgwFoAUkZ47RGw9V5xCdyo010/RzEqXLNow
HQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMA0GCSqGSIb3DQEBCwUAA4IC
AQBFStzhi1ihTC2jgbjqOL60rBTXhAoo8ieSRoHBg4vNeGG5oiH26/P/6A9Z3uYc
Ld+I0o0t4AcbTRREITQijcWj/Zw4i4kNAdf4cPNQZcqKWpnBcWBmuvisVOpsKEna
Xj7v/UFcHJGNvF2RHLd5s3lNn1xKfT3FWWkBmk6aFjXjlX3vWlulzpZlZUPE0XjM
irKHwpqPun+xhTPItv1+1rgMfbv4U+biZCl0a6WbadbJ0Ro/s46sSlFHwNlZwOnB
v8wwLW5m4+6ta1Z2gdL+kuLsIxaH2dJaL+vWHSjnPtyAKuizuI7Yezr1dYeNfE4h
pK6qY/39q/Tbge8Z4/nQtkCuepQmTKih7kayK8INlmEpDS6fCPivxS/GARit/nIG
P68aZ4rE7Lsq14nRNeY7B4rr8M6koKQmU/42D8R6uFnviM7BFH6Xb8zm973430BU
nEKB0BQiLmVWsrl62VPcAf8jmT6D5ewy/x57z0M0Sp42OG0rdhOTwcwiJZ9H9K11
Z7APnOfpADJ9U7IotsWtJK+skHftlXd9e8/2kRZJVXAEAOVX0RMh9w/p6GvVsPrp
XfKMurEUf4sjaydo0w0NqL+tvt6uyCm0CvgWY+K/MHQeg2xAPqxANhJqR3G2cF1W
+g72MY37XiT9CDvreyfuGLozcEFqoFpRayzLzyn0Rhi1oQ==
-----END CERTIFICATE-----
Binary file not shown.
62 changes: 59 additions & 3 deletions libraries/Microsoft.Bot.Builder/BotFrameworkAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,12 @@ public class BotFrameworkAdapter : BotAdapter, IAdapterIntegration, IUserTokenPr

private static readonly HttpClient _defaultHttpClient = new HttpClient();
private readonly ICredentialProvider _credentialProvider;
private readonly AppCredentials _appCredentials;
private readonly IChannelProvider _channelProvider;
private readonly HttpClient _httpClient;
private readonly RetryPolicy _connectorClientRetryPolicy;
private readonly ILogger _logger;
private readonly ConcurrentDictionary<string, MicrosoftAppCredentials> _appCredentialMap = new ConcurrentDictionary<string, MicrosoftAppCredentials>();
private readonly ConcurrentDictionary<string, AppCredentials> _appCredentialMap = new ConcurrentDictionary<string, AppCredentials>();
private readonly AuthenticationConfiguration _authConfiguration;

// There is a significant boost in throughput if we reuse a connectorClient
Expand Down Expand Up @@ -135,6 +136,54 @@ public BotFrameworkAdapter(
ConnectorClient.AddDefaultRequestHeaders(_httpClient);
}

/// <summary>
/// Initializes a new instance of the <see cref="BotFrameworkAdapter"/> class,
/// using a credential provider.
/// </summary>
/// <param name="credentials">The credentials to be used for token acquisition.</param>
/// <param name="authConfig">The authentication configuration.</param>
/// <param name="channelProvider">The channel provider.</param>
/// <param name="connectorClientRetryPolicy">Retry policy for retrying HTTP operations.</param>
/// <param name="customHttpClient">The HTTP client.</param>
/// <param name="middleware">The middleware to initially add to the adapter.</param>
/// <param name="logger">The ILogger implementation this adapter should use.</param>
/// <exception cref="ArgumentNullException">
/// <paramref name="credentialProvider"/> is <c>null</c>.</exception>
/// <remarks>Use a <see cref="MiddlewareSet"/> object to add multiple middleware
/// components in the constructor. Use the <see cref="Use(IMiddleware)"/> method to
/// add additional middleware to the adapter after construction.
/// </remarks>
public BotFrameworkAdapter(
AppCredentials credentials,
AuthenticationConfiguration authConfig,
IChannelProvider channelProvider = null,
RetryPolicy connectorClientRetryPolicy = null,
HttpClient customHttpClient = null,
IMiddleware middleware = null,
ILogger logger = null)
{
_appCredentials = credentials ?? throw new ArgumentNullException(nameof(credentials));
_credentialProvider = new SimpleCredentialProvider(credentials.MicrosoftAppId, string.Empty);
_channelProvider = channelProvider;
_httpClient = customHttpClient ?? _defaultHttpClient;
_connectorClientRetryPolicy = connectorClientRetryPolicy;
_logger = logger ?? NullLogger.Instance;
_authConfiguration = authConfig ?? throw new ArgumentNullException(nameof(authConfig));

if (middleware != null)
{
Use(middleware);
}

// Relocate the tenantId field used by MS Teams to a new location (from channelData to conversation)
// This will only occur on activities from teams that include tenant info in channelData but NOT in conversation,
// thus should be future friendly. However, once the transition is complete. we can remove this.
Use(new TenantIdWorkaroundForTeamsMiddleware());

// DefaultRequestHeaders are not thread safe so set them up here because this adapter should be a singleton.
ConnectorClient.AddDefaultRequestHeaders(_httpClient);
}

/// <summary>
/// Sends a proactive message from the bot to a conversation.
/// </summary>
Expand Down Expand Up @@ -915,7 +964,7 @@ private async Task<IConnectorClient> CreateConnectorClientAsync(string serviceUr
/// <param name="serviceUrl">The service URL.</param>
/// <param name="appCredentials">The application credentials for the bot.</param>
/// <returns>Connector client instance.</returns>
private IConnectorClient CreateConnectorClient(string serviceUrl, MicrosoftAppCredentials appCredentials = null)
private IConnectorClient CreateConnectorClient(string serviceUrl, AppCredentials appCredentials = null)
{
string clientKey = $"{serviceUrl}{appCredentials?.MicrosoftAppId ?? string.Empty}";

Expand Down Expand Up @@ -950,7 +999,7 @@ private IConnectorClient CreateConnectorClient(string serviceUrl, MicrosoftAppCr
/// <param name="appId">The application identifier (AAD Id for the bot).</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>App credentials.</returns>
private async Task<MicrosoftAppCredentials> GetAppCredentialsAsync(string appId, CancellationToken cancellationToken)
private async Task<AppCredentials> GetAppCredentialsAsync(string appId, CancellationToken cancellationToken)
{
if (appId == null)
{
Expand All @@ -962,6 +1011,13 @@ private async Task<MicrosoftAppCredentials> GetAppCredentialsAsync(string appId,
return appCredentials;
}

// If app credentials were provided, use them as they are the preferred choice moving forward
if (_appCredentials != null)
{
_appCredentialMap[appId] = appCredentials;
return appCredentials;
}

// NOTE: we can't do async operations inside of a AddOrUpdate, so we split access pattern
string appPassword = await _credentialProvider.GetAppPasswordAsync(appId).ConfigureAwait(false);
appCredentials = (_channelProvider != null && _channelProvider.IsGovernment()) ?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ public BotFrameworkOptions()
/// <value>The credential provider.</value>
public ICredentialProvider CredentialProvider { get; set; } = new SimpleCredentialProvider();

/// <summary>
/// Gets or sets an <see cref="AppCredentials"/> that should be used to store and retrieve the
/// credentials used during authentication with the Bot Framework Service.
/// </summary>
/// <value>The credential provider.</value>
public AppCredentials AppCredentials { get; set; }

/// <summary>
/// Gets or sets an <see cref="IChannelProvider"/> that should be used to provide configuration for
/// how to validate authentication tokens received from the Bot Framework Service.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ public class AdalAuthenticator
private static volatile RetryParams currentRetryPolicy;

// Our ADAL context. Acquires tokens and manages token caching for us.
private readonly AuthenticationContext authContext;
private AuthenticationContext authContext;

private readonly ClientCredential clientCredential;
private readonly ClientAssertionCertificate clientCertificate;
private readonly OAuthConfiguration authConfig;
private readonly ILogger logger;

Expand All @@ -49,15 +50,16 @@ public AdalAuthenticator(ClientCredential clientCredential, OAuthConfiguration c
this.clientCredential = clientCredential ?? throw new ArgumentNullException(nameof(clientCredential));
this.logger = logger;

if (customHttpClient != null)
{
var httpClientFactory = new ConstantHttpClientFactory(customHttpClient);
this.authContext = new AuthenticationContext(configurationOAuth.Authority, true, new TokenCache(), httpClientFactory);
}
else
{
this.authContext = new AuthenticationContext(configurationOAuth.Authority);
}
Initialize(configurationOAuth, customHttpClient);
}

public AdalAuthenticator(ClientAssertionCertificate clientCertificate, OAuthConfiguration configurationOAuth, HttpClient customHttpClient = null, ILogger logger = null)
{
this.authConfig = configurationOAuth ?? throw new ArgumentNullException(nameof(configurationOAuth));
this.clientCertificate = clientCertificate ?? throw new ArgumentNullException(nameof(clientCertificate));
this.logger = logger;

Initialize(configurationOAuth, customHttpClient);
}

public async Task<AuthenticationResult> GetTokenAsync(bool forceRefresh = false)
Expand Down Expand Up @@ -100,7 +102,19 @@ private async Task<AuthenticationResult> AcquireTokenAsync(bool forceRefresh = f
// Given that this is a ClientCredential scenario, it will use the cache without the
// need to call AcquireTokenSilentAsync (which is only for user credentials).
// Scenario details: https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/Client-credential-flows#it-uses-the-application-token-cache
var res = await authContext.AcquireTokenAsync(authConfig.Scope, this.clientCredential).ConfigureAwait(false);
AuthenticationResult authResult = null;

// Password based auth
if (clientCredential != null)
{
authResult = await authContext.AcquireTokenAsync(authConfig.Scope, this.clientCredential).ConfigureAwait(false);
}

// Certificate based auth
else if (clientCertificate != null)
{
authResult = await authContext.AcquireTokenAsync(authConfig.Scope, clientCertificate).ConfigureAwait(false);
}

// This means we acquired a valid token successfully. We can make our retry policy null.
// Note that the retry policy is set under the semaphore so no additional synchronization is needed.
Expand All @@ -109,7 +123,7 @@ private async Task<AuthenticationResult> AcquireTokenAsync(bool forceRefresh = f
currentRetryPolicy = null;
}

return res;
return authResult;
}
else
{
Expand Down Expand Up @@ -222,5 +236,23 @@ private RetryParams ComputeAdalRetry(Exception ex)

return RetryParams.DefaultBackOff(0);
}

private void Initialize(OAuthConfiguration configurationOAuth, HttpClient customHttpClient)
{
if (customHttpClient != null)
{
var httpClientFactory = new ConstantHttpClientFactory(customHttpClient);
this.authContext = new AuthenticationContext(configurationOAuth.Authority, true, new TokenCache(), httpClientFactory);
}
else
{
this.authContext = new AuthenticationContext(configurationOAuth.Authority);
}
}

private bool UseCertificate()
{
return this.clientCertificate != null;
}
}
}
Loading

0 comments on commit d078226

Please sign in to comment.