using System; using System.IO; using System.Linq; using System.Reflection; using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; using Microsoft.Identity.Client; using Microsoft.Identity.Client.Extensions.Msal; using Microsoft.InformationProtection; using Serilog; namespace AipGateway.AIP { public class AuthDelegateImplementation : IAuthDelegate { private readonly ILogger _log; public int LastErrNo { get; set; } public string LastErrMsg { get; set; } private readonly AipConfig _aipConfig; private readonly IConfidentialClientApplication _confidentialApp = null; private readonly IPublicClientApplication _publicApp = null; // [Obsolete("Obsolete")] private TokenCache _tokenCache = new TokenCache(); public AuthDelegateImplementation(Serilog.Core.Logger logger, AipConfig aipConfig) { _log = logger.ForContext(); _aipConfig = aipConfig; LastErrNo = 0; LastErrMsg = String.Empty; var storageProperties = new StorageCreationPropertiesBuilder( "AIPGateway.Cache", Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)) .Build(); var cacheHelper = MsalCacheHelper.CreateAsync(storageProperties).GetAwaiter().GetResult(); if (_aipConfig.LoginType == AipAuthLoginType.authLoginPassword) { // _confidentialApp = ConfidentialClientApplicationBuilder // .Create(_aipConfig.ClientId) // .WithClientSecret(_aipConfig.SecretValue) // // .WithAuthority($"https://login.microsoftonline.com/{_aipConfig.TenantId}") // // .WithCacheOptions(CacheOptions.EnableSharedCacheOptions) // .WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient") // .Build(); // var authority = $"https://login.windows.net/{_aipConfig.TenantId}"; // BrokerOptions brokerOptions = new BrokerOptions(BrokerOptions.OperatingSystems.Windows); // _confidentialApp = PublicClientApplicationBuilder // .Create(_aipConfig.ClientId) // .WithAuthority(authority) // .WithDefaultRedirectUri() // .WithBroker(brokerOptions) // .Build(); ConfidentialClientApplicationOptions options = new ConfidentialClientApplicationOptions() { ClientSecret = _aipConfig.SecretValue, ClientId = _aipConfig.ClientId, TenantId = _aipConfig.TenantId, RedirectUri = "https://login.microsoftonline.com/common/oauth2/nativeclient", Instance = "https://login.microsoftonline.com/" }; _confidentialApp = ConfidentialClientApplicationBuilder .CreateWithApplicationOptions(options) .WithRedirectUri(options.RedirectUri) // .WithLegacyCacheCompatibility(false) // .WithExperimentalFeatures() // for PoP // .WithCacheOptions(CacheOptions.EnableSharedCacheOptions) .Build(); // IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.CreateWithApplicationOptions(options) // .WithCertificate(certificate) // .Build(); //Console.WriteLine(" AppTokenCache: {0}", _confidentialApp.AppTokenCache); //Console.WriteLine("UserTokenCache: {0}", _confidentialApp.UserTokenCache); cacheHelper.RegisterCache(_confidentialApp.UserTokenCache); } else if (_aipConfig.LoginType == AipAuthLoginType.authLoginCert) { var authority = $"https://login.windows.net/{_aipConfig.TenantId}"; var myCertificate = new X509Certificate2(_aipConfig.CertThumbPrint, "hanteinfo1234!"); X509Certificate2 certificate = new X509Certificate2(myCertificate); _confidentialApp = ConfidentialClientApplicationBuilder.Create(_aipConfig.ClientId) .WithCertificate(certificate) .WithAuthority(new Uri(authority)) .Build(); cacheHelper.RegisterCache(_confidentialApp.UserTokenCache); } else { _publicApp = PublicClientApplicationBuilder.Create(_aipConfig.ClientId) .WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient") .WithAuthority(AzureCloudInstance.AzurePublic, _aipConfig.TenantId) .Build(); cacheHelper.RegisterCache(_publicApp.UserTokenCache); } } public void ResetError() { LastErrNo = 0; LastErrMsg = string.Empty; } private void SetError(int errNo, string errMsg1, string errMsg2 = "No Exception Message.") { LastErrNo = errNo; LastErrMsg = errMsg1 + "\r\n" + errMsg2; _log.Error("AuthDelegateImplementation::SetError ==> {0}, {1}, {2}", errNo, errMsg1, errMsg2); } public string AcquireToken(Identity identity, string authority, string resource, string claim) { //Console.WriteLine("AuthDelegateImplementation::AcquireToken ==> LoginType: {0}", _aipConfig.LoginType); if (_aipConfig.LoginType == AipAuthLoginType.authLoginPassword) { return AcquireTokenByCertificate(identity, authority, resource, claim); } if (_aipConfig.LoginType == AipAuthLoginType.authLoginCert) { return AcquireTokenByCertificate(identity, authority, resource, claim); } return AcquireTokenById(identity, authority, resource, claim); } private string AcquireTokenByCertificate(Identity identity, string authority, string resource, string claims) { //ddddddddddddddddddddddddddddddddd: https://login.windows.net/common, https://syncservice.o365syncservice.com/ //ddddddddddddddddddddddddddddddddd: https://login.windows.net/common, https://aadrm.com //Console.WriteLine("ddddddddddddddddddddddddddddddddd: {0}, {1}", authority, resource); // AuthenticationResult result; // var authorityUri = new Uri(authority); // authority = $"https://{authorityUri.Host}/{_aipConfig.TenantId}"; var scopes = new[] { resource[resource.Length - 1].Equals('/') ? $"{resource}.default" : $"{resource}/.default" }; try { #if false IConfidentialClientApplication app; ConfidentialClientApplicationOptions options = new ConfidentialClientApplicationOptions() { ClientSecret = _aipConfig.SecretValue, ClientId = _aipConfig.ClientId, TenantId = _aipConfig.TenantId, RedirectUri = "https://login.microsoftonline.com/common/oauth2/nativeclient", Instance = "https://login.microsoftonline.com/" }; app = ConfidentialClientApplicationBuilder .CreateWithApplicationOptions(options) .WithRedirectUri(options.RedirectUri) // .WithLegacyCacheCompatibility(false) // .WithExperimentalFeatures() // for PoP // .WithCacheOptions(CacheOptions.EnableSharedCacheOptions) .Build(); #endif AuthenticationResult result = null; //Console.WriteLine("B: AppTokenCache: {0}", _confidentialApp.AppTokenCache); //Console.WriteLine("B: UserTokenCache: {0}", _confidentialApp.UserTokenCache); #if false result = _confidentialApp.AcquireTokenForClient(scopes) .ExecuteAsync() .GetAwaiter() .GetResult(); result = _confidentialApp.AcquireTokenForClient(scopes).ExecuteAsync().Result; #else result = _confidentialApp.AcquireTokenForClient(scopes) .ExecuteAsync(CancellationToken.None) .ConfigureAwait(false) .GetAwaiter() .GetResult(); #endif //_log.Information("AcquireTokenByCertificate: {0}, {1}", identity.Email, identity.Name); // result = _confidentialApp.AcquireTokenForClient(scopes) // .WithTenantId(_aipConfig.TenantId) // .ExecuteAsync() // .GetAwaiter() // .GetResult(); // result = _confidentialApp.AcquireTokenForClient(scopes).ExecuteAsync().GetAwaiter().GetResult(); //Console.WriteLine("A: AppTokenCache: {0}", _confidentialApp.AppTokenCache); //Console.WriteLine("A: UserTokenCache: {0}", _confidentialApp.UserTokenCache); //Console.WriteLine("A: AccessToken: {0}", result.AccessToken); return result.AccessToken; } catch (MsalUiRequiredException ex) when (ex.Message.Contains("AADSTS70011")) { // Invalid scope. The scope has to be of the form "https://resourceurl/.default" // Mitigation: change the scope to be as expected SetError(1, "AcquireTokenByCertificate::AcquireTokenByCertificate, Scope provided is not supported.", ex.Message); } catch (Exception ex) { SetError(1, "AcquireTokenByCertificate::AcquireTokenByCertificate Failed.", ex.Message); } return null; } // private string AcquireTokenByCertificate_Temp(Identity identity, string authority, string resource, string claims) // { // AuthenticationResult result; // var authorityUri = new Uri(authority); // authority = $"https://{authorityUri.Host}/{_aipConfig.TenantId}"; // // var scopes = new[] { resource[resource.Length - 1].Equals('/') ? $"{resource}.default" : $"{resource}/.default" }; // // try // { // var accounts = (_confidentialApp.GetAccountsAsync()).GetAwaiter().GetResult(); // result = _confidentialApp.AcquireTokenSilent(scopes, accounts.FirstOrDefault()) // .ExecuteAsync().GetAwaiter().GetResult(); // // // result = _confidentialApp.AcquireTokenForClient(scopes) // // .ExecuteAsync().GetAwaiter().GetResult(); // // Console.WriteLine("AuthDelegateImplementation::AcquireTokenByCertificate ==> AcquireTokenSilent Succeed."); // return result.AccessToken; // } // catch (MsalUiRequiredException) // { // Console.WriteLine("AuthDelegateImplementation::AcquireTokenByCertificate ==> AcquireTokenSilent Failed. Login AcquireTokenForClient."); // try // { // result = _confidentialApp.AcquireTokenForClient(scopes) // .ExecuteAsync().GetAwaiter().GetResult(); // // result = _confidentialApp.AcquireTokenForClient(scopes) // // .WithTenantId(_aipConfig.TenantId) // // .ExecuteAsync() // // .GetAwaiter() // // .GetResult(); // // result = _confidentialApp.AcquireTokenForClient(scopes).ExecuteAsync().GetAwaiter().GetResult(); // } // catch (MsalServiceException ex) when (ex.Message.Contains("AADSTS70011")) // { // // Invalid scope. The scope has to be of the form "https://resourceurl/.default" // // Mitigation: change the scope to be as expected // SetError(1, "AcquireTokenByPassword.Scope provided is not supported.", ex.Message); // return null; // } // } // // return result.AccessToken; // } private string AcquireTokenById(Identity identity, string authority, string resource, string claims) { //Console.WriteLine("AcquireTokenById::AcquireTokenByPassword"); AuthenticationResult result; var authorityUri = new Uri(authority); authority = $"https://{authorityUri.Host}/{_aipConfig.TenantId}"; // var app = PublicClientApplicationBuilder.Create(_aipConfig.ClientId) // .WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient") // .WithAuthority(AzureCloudInstance.AzurePublic, _aipConfig.TenantId) // .Build(); var accounts = (_publicApp.GetAccountsAsync()).GetAwaiter().GetResult(); // Append .default to the resource passed in to AcquireToken(). var scopes = new[] { resource[resource.Length - 1].Equals('/') ? $"{resource}.default" : $"{resource}/.default" }; try { result = _publicApp.AcquireTokenSilent(scopes, accounts.FirstOrDefault()).ExecuteAsync().Result; //Console.WriteLine("AuthDelegateImplementation::AcquireTokenById ==> AcquireTokenSilent Succeed."); return result.AccessToken; } catch (MsalUiRequiredException) { //Console.WriteLine("AuthDelegateImplementation::AcquireTokenById ==> AcquireTokenSilent Failed. Login AcquireTokenForClient."); result = _publicApp.AcquireTokenInteractive(scopes) .WithAccount(accounts.FirstOrDefault()) .WithPrompt(Prompt.SelectAccount) .ExecuteAsync() .ConfigureAwait(false) .GetAwaiter() .GetResult(); } catch (Exception ex) { SetError(1, "AcquireTokenById.Login Failed.", ex.Message); return null; } return result.AccessToken; } public async Task AcquireTokenAsync(string authority, string resource, string claims) { AuthenticationResult result = null; if (authority.ToLower().Contains("common")) { var authorityUri = new Uri(authority); authority = String.Format("https://{0}/{1}", authorityUri.Host, _aipConfig.TenantId); } var app = PublicClientApplicationBuilder.Create(_aipConfig.ClientId) .WithAuthority(authority) .WithDefaultRedirectUri() .Build(); var accounts = (app.GetAccountsAsync()).GetAwaiter().GetResult(); // Append .default to the resource passed in to AcquireToken(). string[] scopes = { resource[resource.Length - 1].Equals('/') ? $"{resource}.default" : $"{resource}/.default" }; try { result = await app.AcquireTokenSilent(new[] { "https://aadrm.com/user_impersonation" }, accounts.FirstOrDefault()).ExecuteAsync(); // result = await _app.AcquireTokenSilent(scopes, // accounts.FirstOrDefault()) // .ExecuteAsync(); } catch (MsalUiRequiredException) { result = app.AcquireTokenInteractive(scopes) .WithAccount(accounts.FirstOrDefault()) .WithPrompt(Prompt.SelectAccount) .ExecuteAsync() .ConfigureAwait(false) .GetAwaiter() .GetResult(); } // Return the token. The token is sent to the resource. return result; } // public Identity GetUserIdentity() // { // try // { // string resource = "https://graph.microsoft.com/"; // // AuthenticationContext authContext = new AuthenticationContext("https://login.windows.net/common", tokenCache); // var result = authContext.AcquireTokenAsync(resource, appInfo.ApplicationId, new Uri(redirectUri), new PlatformParameters(PromptBehavior.Always)).Result; // return new Identity(result.UserInfo.DisplayableId); // // } // catch (Exception ex) // { // throw ex; // } // } // private async Task GetAccessTokenOnBehalfOfUser(string authority, string resource) // { // IConfidentialClientApplication app; // // if (false) // { // // Read X509 cert from local store and build ClientAssertionCertificate. // X509Certificate2 cert = Utilities.ReadCertificateFromStore(_aipConfig.CertThumbPrint); // // // Create confidential client using certificate. // app = ConfidentialClientApplicationBuilder.Create(_aipConfig.ClientId) // .WithRedirectUri(resource) // .WithAuthority(authority) // .WithCertificate(cert) // .Build(); // } // // else // { // // Create confidential client using client secret. // app = ConfidentialClientApplicationBuilder.Create(_aipConfig.ClientId) // .WithRedirectUri(resource) // .WithAuthority(authority) // .WithClientSecret(_aipConfig.SecretValue) // .Build(); // } // // // Store user access token of authenticated user. // var ci = (ClaimsIdentity)_claimsPrincipal.Identity; // string userAccessToken = (string)ci.BootstrapContext; // // // // Generate a user assertion with the UPN and access token. // UserAssertion userAssertion = new UserAssertion(userAccessToken, "urn:ietf:params:oauth:grant-type:jwt-bearer"); // // // Append .default to the resource passed in to AcquireToken(). // List scopes = new List() { resource[resource.Length - 1].Equals('/') ? $"{resource}.default" : $"{resource}/.default" }; // // AuthenticationResult result = await app.AcquireTokenOnBehalfOf(scopes, userAssertion) // .ExecuteAsync(); // // // Return the token to the API caller // return (result.AccessToken); // } } }