-
Notifications
You must be signed in to change notification settings - Fork 226
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
V1 Delta Roster Sample Update using Microsoft Communications SDK 1.2.…
…0.9212 (#684) * Add the Sample for V1 Delta Roster * Namespace changes for V1 nuget * Deployment issue fix --------- Co-authored-by: Samarth Asthana <[email protected]>
- Loading branch information
1 parent
f657ea3
commit e7f61c2
Showing
46 changed files
with
4,900 additions
and
0 deletions.
There are no files selected for viewing
245 changes: 245 additions & 0 deletions
245
Samples/Common/Sample.Common.V1/Authentication/AuthenticationProvider.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,245 @@ | ||
// <copyright file="AuthenticationProvider.cs" company="Microsoft Corporation"> | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT license. | ||
// </copyright> | ||
|
||
// THIS CODE HAS NOT BEEN TESTED RIGOROUSLY.USING THIS CODE IN PRODUCTION ENVIRONMENT IS STRICTLY NOT RECOMMENDED. | ||
// THIS SAMPLE IS PURELY FOR DEMONSTRATION PURPOSES ONLY. | ||
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. | ||
namespace Sample.Common.Authentication | ||
{ | ||
using System; | ||
using System.IdentityModel.Tokens.Jwt; | ||
using System.Net.Http; | ||
using System.Net.Http.Headers; | ||
using System.Security.Claims; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.Graph.Communications.Client.Authentication; | ||
using Microsoft.Graph.Communications.Common; | ||
using Microsoft.Graph.Communications.Common.Telemetry; | ||
using Microsoft.Identity.Client; | ||
using Microsoft.IdentityModel.Protocols; | ||
using Microsoft.IdentityModel.Protocols.OpenIdConnect; | ||
using Microsoft.IdentityModel.Tokens; | ||
|
||
/// <summary> | ||
/// The authentication provider for this bot instance. | ||
/// </summary> | ||
/// <seealso cref="IRequestAuthenticationProvider" /> | ||
public class AuthenticationProvider : ObjectRoot, IRequestAuthenticationProvider | ||
{ | ||
/// <summary> | ||
/// The application name. | ||
/// </summary> | ||
private readonly string appName; | ||
|
||
/// <summary> | ||
/// The application identifier. | ||
/// </summary> | ||
private readonly string appId; | ||
|
||
/// <summary> | ||
/// The application secret. | ||
/// </summary> | ||
private readonly string appSecret; | ||
|
||
/// <summary> | ||
/// The open ID configuration refresh interval. | ||
/// </summary> | ||
private readonly TimeSpan openIdConfigRefreshInterval = TimeSpan.FromHours(2); | ||
|
||
/// <summary> | ||
/// The previous update timestamp for OpenIdConfig. | ||
/// </summary> | ||
private DateTime prevOpenIdConfigUpdateTimestamp = DateTime.MinValue; | ||
|
||
/// <summary> | ||
/// The open identifier configuration. | ||
/// </summary> | ||
private OpenIdConnectConfiguration openIdConfiguration; | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="AuthenticationProvider" /> class. | ||
/// </summary> | ||
/// <param name="appName">The application name.</param> | ||
/// <param name="appId">The application identifier.</param> | ||
/// <param name="appSecret">The application secret.</param> | ||
/// <param name="logger">The logger.</param> | ||
public AuthenticationProvider(string appName, string appId, string appSecret, IGraphLogger logger) | ||
: base(logger.NotNull(nameof(logger)).CreateShim(nameof(AuthenticationProvider))) | ||
{ | ||
this.appName = appName.NotNullOrWhitespace(nameof(appName)); | ||
this.appId = appId.NotNullOrWhitespace(nameof(appId)); | ||
this.appSecret = appSecret.NotNullOrWhitespace(nameof(appSecret)); | ||
} | ||
|
||
/// <summary> | ||
/// Authenticates the specified request message. | ||
/// This method will be called any time there is an outbound request. | ||
/// In this case we are using the Microsoft.IdentityModel.Clients.ActiveDirectory library | ||
/// to stamp the outbound http request with the OAuth 2.0 token using an AAD application id | ||
/// and application secret. Alternatively, this method can support certificate validation. | ||
/// Note that this is only for demonstration purpose. Since acquiring token can be a costly operation, | ||
/// it is recomended to acquire token before hand and cache the token, instead of acquiring it for every incoming request. | ||
/// </summary> | ||
/// <param name="request">The request.</param> | ||
/// <param name="tenant">The tenant.</param> | ||
/// <returns> | ||
/// The <see cref="Task" />. | ||
/// </returns> | ||
public async Task AuthenticateOutboundRequestAsync(HttpRequestMessage request, string tenant) | ||
{ | ||
const string schema = "Bearer"; | ||
const string replaceString = "{tenant}"; | ||
const string oauthV2TokenLink = "https://login.microsoftonline.com/{tenant}"; | ||
const string resource = "https://graph.microsoft.com/beta"; | ||
|
||
// If no tenant was specified, we craft the token link using the common tenant. | ||
// https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-protocols#endpoints | ||
tenant = string.IsNullOrWhiteSpace(tenant) ? "common" : tenant; | ||
var tokenLink = oauthV2TokenLink.Replace(replaceString, tenant); | ||
var scopes = new string[] { $"{resource}/.default" }; | ||
|
||
this.GraphLogger.Info("AuthenticationProvider: Generating OAuth token."); | ||
var app = ConfidentialClientApplicationBuilder.Create(this.appId) | ||
.WithAuthority(tokenLink) | ||
.WithClientSecret(this.appSecret) | ||
.Build(); | ||
|
||
AuthenticationResult result; | ||
try | ||
{ | ||
result = await this.AcquireTokenWithRetryAsync(app, scopes, 3).ConfigureAwait(false); | ||
} | ||
catch (Exception ex) | ||
{ | ||
this.GraphLogger.Error(ex, $"Failed to generate token for client: {this.appId}"); | ||
throw; | ||
} | ||
|
||
this.GraphLogger.Info($"AuthenticationProvider: Generated OAuth token. Expires in {result.ExpiresOn.Subtract(DateTimeOffset.UtcNow).TotalMinutes} minutes."); | ||
|
||
request.Headers.Authorization = new AuthenticationHeaderValue(schema, result.AccessToken); | ||
} | ||
|
||
/// <summary> | ||
/// Validates the request asynchronously. | ||
/// This method will be called any time we have an incoming request. | ||
/// Returning invalid result will trigger a Forbidden response. | ||
/// </summary> | ||
/// <param name="request">The request.</param> | ||
/// <returns> | ||
/// The <see cref="RequestValidationResult" /> structure. | ||
/// </returns> | ||
public async Task<RequestValidationResult> ValidateInboundRequestAsync(HttpRequestMessage request) | ||
{ | ||
var token = request?.Headers?.Authorization?.Parameter; | ||
if (string.IsNullOrWhiteSpace(token)) | ||
{ | ||
return new RequestValidationResult { IsValid = false }; | ||
} | ||
|
||
// Currently the service does not sign outbound request using AAD, instead it is signed | ||
// with a private certificate. In order for us to be able to ensure the certificate is | ||
// valid we need to download the corresponding public keys from a trusted source. | ||
const string authDomain = "https://api.aps.skype.com/v1/.well-known/OpenIdConfiguration"; | ||
if (this.openIdConfiguration == null || DateTime.Now > this.prevOpenIdConfigUpdateTimestamp.Add(this.openIdConfigRefreshInterval)) | ||
{ | ||
this.GraphLogger.Info("Updating OpenID configuration"); | ||
|
||
// Download the OIDC configuration which contains the JWKS | ||
IConfigurationManager<OpenIdConnectConfiguration> configurationManager = | ||
new ConfigurationManager<OpenIdConnectConfiguration>( | ||
authDomain, | ||
new OpenIdConnectConfigurationRetriever()); | ||
this.openIdConfiguration = await configurationManager.GetConfigurationAsync(CancellationToken.None).ConfigureAwait(false); | ||
|
||
this.prevOpenIdConfigUpdateTimestamp = DateTime.Now; | ||
} | ||
|
||
// The incoming token should be issued by graph. | ||
var authIssuers = new[] | ||
{ | ||
"https://graph.microsoft.com", | ||
"https://api.botframework.com", | ||
}; | ||
|
||
// Configure the TokenValidationParameters. | ||
// Aet the Issuer(s) and Audience(s) to validate and | ||
// assign the SigningKeys which were downloaded from AuthDomain. | ||
TokenValidationParameters validationParameters = new TokenValidationParameters | ||
{ | ||
ValidIssuers = authIssuers, | ||
ValidAudience = this.appId, | ||
IssuerSigningKeys = this.openIdConfiguration.SigningKeys, | ||
}; | ||
|
||
ClaimsPrincipal claimsPrincipal; | ||
try | ||
{ | ||
// Now validate the token. If the token is not valid for any reason, an exception will be thrown by the method | ||
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler(); | ||
claimsPrincipal = handler.ValidateToken(token, validationParameters, out _); | ||
} | ||
|
||
// Token expired... should somehow return 401 (Unauthorized) | ||
// catch (SecurityTokenExpiredException ex) | ||
// Tampered token | ||
// catch (SecurityTokenInvalidSignatureException ex) | ||
// Some other validation error | ||
// catch (SecurityTokenValidationException ex) | ||
catch (Exception ex) | ||
{ | ||
// Some other error | ||
this.GraphLogger.Error(ex, $"Failed to validate token for client: {this.appId}."); | ||
return new RequestValidationResult() { IsValid = false }; | ||
} | ||
|
||
const string ClaimType = "http://schemas.microsoft.com/identity/claims/tenantid"; | ||
var tenantClaim = claimsPrincipal.FindFirst(claim => claim.Type.Equals(ClaimType, StringComparison.Ordinal)); | ||
|
||
if (string.IsNullOrEmpty(tenantClaim?.Value)) | ||
{ | ||
// No tenant claim given to us. reject the request. | ||
return new RequestValidationResult { IsValid = false }; | ||
} | ||
|
||
request.Properties.Add(HttpConstants.HeaderNames.Tenant, tenantClaim.Value); | ||
return new RequestValidationResult { IsValid = true, TenantId = tenantClaim.Value }; | ||
} | ||
|
||
/// <summary> | ||
/// Acquires the token and retries if failure occurs. | ||
/// </summary> | ||
/// <param name="app">The application.</param> | ||
/// <param name="scopes">The resources.</param> | ||
/// <param name="attempts">The attempts.</param> | ||
/// <returns> | ||
/// The <see cref="AuthenticationResult" />. | ||
/// </returns> | ||
private async Task<AuthenticationResult> AcquireTokenWithRetryAsync(IConfidentialClientApplication app, string[] scopes, int attempts) | ||
{ | ||
while (true) | ||
{ | ||
attempts--; | ||
|
||
try | ||
{ | ||
return await app.AcquireTokenForClient(scopes) | ||
.ExecuteAsync() | ||
.ConfigureAwait(false); | ||
} | ||
catch (Exception) | ||
{ | ||
if (attempts < 1) | ||
{ | ||
throw; | ||
} | ||
} | ||
|
||
await Task.Delay(1000).ConfigureAwait(false); | ||
} | ||
} | ||
} | ||
} |
58 changes: 58 additions & 0 deletions
58
Samples/Common/Sample.Common.V1/Authentication/AuthenticationWrapper.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
// <copyright file="AuthenticationWrapper.cs" company="Microsoft Corporation"> | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT license. | ||
// </copyright> | ||
|
||
namespace Sample.Common.Authentication | ||
{ | ||
using System.Collections.Generic; | ||
using System.Net.Http; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.Graph.Communications.Client.Authentication; | ||
using Microsoft.Graph.Communications.Common; | ||
using Microsoft.Graph.Models; | ||
using Microsoft.Kiota.Abstractions; | ||
using Microsoft.Kiota.Abstractions.Authentication; | ||
|
||
/// <summary> | ||
/// A wrapper for the <see cref="IRequestAuthenticationProvider"/> | ||
/// that maps to the <see cref="IAuthenticationProvider"/>. | ||
/// </summary> | ||
/// <seealso cref="IRequestAuthenticationProvider" /> | ||
/// <seealso cref="IAuthenticationProvider" /> | ||
public class AuthenticationWrapper : IRequestAuthenticationProvider, IAuthenticationProvider | ||
{ | ||
private readonly IRequestAuthenticationProvider authenticationProvider; | ||
private readonly string tenant; | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="AuthenticationWrapper"/> class. | ||
/// </summary> | ||
/// <param name="authenticationProvider">The authentication provider.</param> | ||
/// <param name="tenant">The tenant.</param> | ||
public AuthenticationWrapper(IRequestAuthenticationProvider authenticationProvider, string tenant = null) | ||
{ | ||
this.authenticationProvider = authenticationProvider.NotNull(nameof(authenticationProvider)); | ||
this.tenant = tenant; | ||
} | ||
|
||
/// <inheritdoc /> | ||
public Task AuthenticateOutboundRequestAsync(HttpRequestMessage request, string tenant) | ||
{ | ||
return this.authenticationProvider.AuthenticateOutboundRequestAsync(request, tenant); | ||
} | ||
|
||
/// <inheritdoc /> | ||
public Task<RequestValidationResult> ValidateInboundRequestAsync(HttpRequestMessage request) | ||
{ | ||
return this.authenticationProvider.ValidateInboundRequestAsync(request); | ||
} | ||
|
||
/// <inheritdoc /> | ||
public Task AuthenticateRequestAsync(RequestInformation request, Dictionary<string, object> additionalAuthenticationContext = null, CancellationToken cancellationToken = default) | ||
{ | ||
throw new System.NotImplementedException(); | ||
} | ||
} | ||
} |
Oops, something went wrong.