AuthDelegateImplementation.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. using System;
  2. using System.IO;
  3. using System.Linq;
  4. using System.Reflection;
  5. using System.Security.Cryptography.X509Certificates;
  6. using System.Threading.Tasks;
  7. using Microsoft.Identity.Client;
  8. using Microsoft.Identity.Client.Extensions.Msal;
  9. using Microsoft.InformationProtection;
  10. namespace AipGateway.AIP
  11. {
  12. public class AuthDelegateImplementation : IAuthDelegate
  13. {
  14. public int LastErrNo { get; set; }
  15. public string LastErrMsg { get; set; }
  16. private readonly AipConfig _aipConfig;
  17. private readonly IConfidentialClientApplication _confidentialApp = null;
  18. private readonly IPublicClientApplication _publicApp = null;
  19. public AuthDelegateImplementation(AipConfig aipConfig)
  20. {
  21. _aipConfig = aipConfig;
  22. LastErrNo = 0;
  23. LastErrMsg = String.Empty;
  24. var storageProperties =
  25. new StorageCreationPropertiesBuilder(
  26. "AIPGateway.Cache",
  27. Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location))
  28. .Build();
  29. var cacheHelper = MsalCacheHelper.CreateAsync(storageProperties).GetAwaiter().GetResult();
  30. if (_aipConfig.LoginType == AipAuthLoginType.authLoginPassword)
  31. {
  32. _confidentialApp = ConfidentialClientApplicationBuilder.Create(_aipConfig.ClientId)
  33. .WithClientSecret(_aipConfig.SecretValue)
  34. // .WithAuthority($"https://login.microsoftonline.com/{_aipConfig.TenantId}")
  35. // .WithCacheOptions(CacheOptions.EnableSharedCacheOptions)
  36. .WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient")
  37. .Build();
  38. cacheHelper.RegisterCache(_confidentialApp.UserTokenCache);
  39. }
  40. else if (_aipConfig.LoginType == AipAuthLoginType.authLoginCert)
  41. {
  42. var authority = $"https://login.windows.net/{_aipConfig.TenantId}";
  43. // var certificate = Utilities.ReadCertificateFromStore(_aipConfig.CertThumbPrint);
  44. var myCertificate = X509Certificate2.CreateFromCertFile(_aipConfig.CertThumbPrint);
  45. X509Certificate2 certificate = new X509Certificate2(myCertificate);
  46. _confidentialApp = ConfidentialClientApplicationBuilder.Create(_aipConfig.ClientId)
  47. .WithCertificate(certificate)
  48. .WithAuthority(new Uri(authority))
  49. .Build();
  50. cacheHelper.RegisterCache(_confidentialApp.UserTokenCache);
  51. }
  52. else
  53. {
  54. _publicApp = PublicClientApplicationBuilder.Create(_aipConfig.ClientId)
  55. .WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient")
  56. .WithAuthority(AzureCloudInstance.AzurePublic, _aipConfig.TenantId)
  57. .Build();
  58. cacheHelper.RegisterCache(_publicApp.UserTokenCache);
  59. }
  60. }
  61. public void ResetError()
  62. {
  63. LastErrNo = 0;
  64. LastErrMsg = string.Empty;
  65. }
  66. private void SetError(int errNo, string errMsg1, string errMsg2 = "No Exception Message.")
  67. {
  68. LastErrNo = errNo;
  69. LastErrMsg = errMsg1 + "\r\n" + errMsg2;
  70. Console.WriteLine("AuthDelegateImplementation::SetError ==> {0}, {1}, {2}", errNo, errMsg1, errMsg2);
  71. }
  72. public string AcquireToken(Identity identity, string authority, string resource, string claim)
  73. {
  74. Console.WriteLine("AuthDelegateImplementation::AcquireToken ==> LoginType: {0}", _aipConfig.LoginType);
  75. // var app = PublicClientApplicationBuilder.Create(_aipConfig.ClientId)
  76. // .WithAuthority(authority)
  77. // .Build();
  78. //
  79. // var scopes = new[] { resource[resource.Length - 1].Equals('/') ? $"{resource}.default" : $"{resource}/.default" };
  80. // try
  81. // {
  82. // var accounts = app.GetAccountsAsync().GetAwaiter().GetResult();
  83. // var result = app.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
  84. // .ExecuteAsync().Result;
  85. //
  86. // Console.WriteLine("AuthDelegateImplementation::AcquireToken ==> AcquireTokenSilent Succeed.");
  87. // return result.AccessToken;
  88. // }
  89. // catch (MsalUiRequiredException)
  90. // {
  91. // Console.WriteLine("AuthDelegateImplementation::AcquireToken ==> AcquireTokenSilent Failed.");
  92. // }
  93. if (_aipConfig.LoginType == AipAuthLoginType.authLoginPassword)
  94. {
  95. return AcquireTokenByPassword(identity, authority, resource, claim);
  96. }
  97. if (_aipConfig.LoginType == AipAuthLoginType.authLoginCert)
  98. {
  99. return AcquireTokenByCert(identity, authority, resource, claim);
  100. }
  101. return AcquireTokenById(identity, authority, resource, claim);
  102. }
  103. private string AcquireTokenByPassword(Identity identity, string authority, string resource, string claims)
  104. {
  105. Console.WriteLine("AuthDelegateImplementation::AcquireTokenByPassword");
  106. AuthenticationResult result;
  107. var authorityUri = new Uri(authority);
  108. authority = $"https://{authorityUri.Host}/{_aipConfig.TenantId}";
  109. // var app = ConfidentialClientApplicationBuilder.Create(_aipConfig.ClientId)
  110. // .WithClientSecret(_aipConfig.SecretValue)
  111. // // .WithAuthority($"https://login.microsoftonline.com/{_aipConfig.TenantId}")
  112. // // .WithCacheOptions(CacheOptions.EnableSharedCacheOptions)
  113. // .WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient")
  114. // .Build();
  115. // Create confidential client using client secret.
  116. // var app = ConfidentialClientApplicationBuilder.Create(_aipConfig.ClientId)
  117. // .WithRedirectUri(resource)
  118. // .WithAuthority(authority)
  119. // .WithClientSecret(_aipConfig.SecretValue)
  120. // .Build();
  121. var scopes = new[] { resource[resource.Length - 1].Equals('/') ? $"{resource}.default" : $"{resource}/.default" };
  122. //app.AddInMemoryTokenCache();
  123. try
  124. {
  125. var accounts = (_confidentialApp.GetAccountsAsync()).GetAwaiter().GetResult();
  126. result = _confidentialApp.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
  127. .ExecuteAsync().GetAwaiter().GetResult();
  128. Console.WriteLine("AuthDelegateImplementation::AcquireTokenByPassword ==> AcquireTokenSilent Succeed.");
  129. return result.AccessToken;
  130. }
  131. catch (MsalUiRequiredException)
  132. {
  133. Console.WriteLine("AuthDelegateImplementation::AcquireTokenByPassword ==> AcquireTokenSilent Failed.");
  134. try
  135. {
  136. result = _confidentialApp.AcquireTokenForClient(scopes)
  137. .WithTenantId(_aipConfig.TenantId)
  138. .ExecuteAsync()
  139. .GetAwaiter()
  140. .GetResult();
  141. }
  142. catch (MsalServiceException ex) when (ex.Message.Contains("AADSTS70011"))
  143. {
  144. // Invalid scope. The scope has to be of the form "https://resourceurl/.default"
  145. // Mitigation: change the scope to be as expected
  146. SetError(1, "AcquireTokenByPassword.Scope provided is not supported.", ex.Message);
  147. return null;
  148. }
  149. }
  150. return result.AccessToken;
  151. }
  152. private string AcquireTokenByCert(Identity identity, string authority, string resource, string claims)
  153. {
  154. Console.WriteLine("AuthDelegateImplementation::AcquireTokenByPassword");
  155. AuthenticationResult result;
  156. var authorityUri = new Uri(authority);
  157. authority = $"https://{authorityUri.Host}/{_aipConfig.TenantId}";
  158. // Read cert from local machine
  159. // var certificate = Utilities.ReadCertificateFromStore(_aipConfig.CertThumbPrint);
  160. var myCertificate = X509Certificate2.CreateFromCertFile(_aipConfig.CertThumbPrint);
  161. X509Certificate2 certificate = new X509Certificate2(myCertificate);
  162. // Use cert to build ClientAssertionCertificate
  163. // var app = ConfidentialClientApplicationBuilder.Create(_aipConfig.ClientId)
  164. // .WithCertificate(certificate)
  165. // .WithAuthority(new Uri(authority))
  166. // .Build();
  167. // var app = ConfidentialClientApplicationBuilder.Create(_aipConfig.ClientId)
  168. // .WithCertificate(certificate)
  169. // .WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient")
  170. // .Build();
  171. // Append .default to the resource passed in to AcquireToken().
  172. var scopes = new[] { resource[resource.Length - 1].Equals('/') ? $"{resource}.default" : $"{resource}/.default" };
  173. try
  174. {
  175. result = _confidentialApp.AcquireTokenForClient(scopes).ExecuteAsync().Result;
  176. }
  177. catch (MsalServiceException ex) when (ex.Message.Contains("AADSTS70011"))
  178. {
  179. // Invalid scope. The scope has to be of the form "https://resourceurl/.default"
  180. // Mitigation: change the scope to be as expected
  181. SetError(1, "AcquireTokenByCert.Scope provided is not supported.", ex.Message);
  182. return null;
  183. }
  184. return result.AccessToken;
  185. }
  186. private string AcquireTokenById(Identity identity, string authority, string resource, string claims)
  187. {
  188. Console.WriteLine("AcquireTokenById::AcquireTokenByPassword");
  189. AuthenticationResult result;
  190. var authorityUri = new Uri(authority);
  191. authority = $"https://{authorityUri.Host}/{_aipConfig.TenantId}";
  192. // var app = PublicClientApplicationBuilder.Create(_aipConfig.ClientId)
  193. // .WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient")
  194. // .WithAuthority(AzureCloudInstance.AzurePublic, _aipConfig.TenantId)
  195. // .Build();
  196. var accounts = (_publicApp.GetAccountsAsync()).GetAwaiter().GetResult();
  197. // Append .default to the resource passed in to AcquireToken().
  198. var scopes = new[] { resource[resource.Length - 1].Equals('/') ? $"{resource}.default" : $"{resource}/.default" };
  199. try
  200. {
  201. result = _publicApp.AcquireTokenInteractive(scopes)
  202. .WithAccount(accounts.FirstOrDefault())
  203. .WithPrompt(Prompt.SelectAccount)
  204. .ExecuteAsync()
  205. .ConfigureAwait(false)
  206. .GetAwaiter()
  207. .GetResult();
  208. }
  209. catch (Exception ex)
  210. {
  211. SetError(1, "AcquireTokenById.Login Failed.", ex.Message);
  212. return null;
  213. }
  214. return result.AccessToken;
  215. }
  216. // public X509Certificate2 LoadCertificate(string certificateName)
  217. // {
  218. // string vaultUrl = "";
  219. // string clientId = _aipConfig.ClientId;
  220. // string tenantId = _aipConfig.TenantId;
  221. // string secret = "";
  222. //
  223. // Console.WriteLine($"Loading certificate '{certificateName}' from Azure Key Vault");
  224. //
  225. // var credentials = new ClientSecretCredential(tenantId: tenantId, clientId: clientId, clientSecret: secret);
  226. // var certClient = new CertificateClient(new Uri(vaultUrl), credentials);
  227. // var secretClient = new SecretClient(new Uri(vaultUrl), credentials);
  228. //
  229. // var cert = GetCertificateAsync(certClient, secretClient, certificateName);
  230. //
  231. // Console.WriteLine("Certificate loaded");
  232. // return cert;
  233. // }
  234. // private static X509Certificate2 GetCertificateAsync(CertificateClient certificateClient,
  235. // SecretClient secretClient,
  236. // string certificateName)
  237. // {
  238. //
  239. // KeyVaultCertificateWithPolicy certificate = certificateClient.GetCertificate(certificateName);
  240. //
  241. // // Return a certificate with only the public key if the private key is not exportable.
  242. // if (certificate.Policy?.Exportable != true)
  243. // {
  244. // return new X509Certificate2(certificate.Cer);
  245. // }
  246. //
  247. // // Parse the secret ID and version to retrieve the private key.
  248. // string[] segments = certificate.SecretId.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries);
  249. // if (segments.Length != 3)
  250. // {
  251. // throw new InvalidOperationException($"Number of segments is incorrect: {segments.Length}, URI: {certificate.SecretId}");
  252. // }
  253. //
  254. // string secretName = segments[1];
  255. // string secretVersion = segments[2];
  256. //
  257. // KeyVaultSecret secret = secretClient.GetSecret(secretName, secretVersion);
  258. //
  259. // // For PEM, you'll need to extract the base64-encoded message body.
  260. // // .NET 5.0 preview introduces the System.Security.Cryptography.PemEncoding class to make this easier.
  261. // if ("application/x-pkcs12".Equals(secret.Properties.ContentType, StringComparison.InvariantCultureIgnoreCase))
  262. // {
  263. // byte[] pfx = Convert.FromBase64String(secret.Value);
  264. // return new X509Certificate2(pfx);
  265. // }
  266. //
  267. // throw new NotSupportedException($"Only PKCS#12 is supported. Found Content-Type: {secret.Properties.ContentType}");
  268. // }
  269. // }
  270. private X509Certificate2 ReadCertificateFromStore(string thumbprint)
  271. {
  272. X509Certificate2 cert = null;
  273. X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
  274. store.Open(OpenFlags.ReadOnly);
  275. X509Certificate2Collection certCollection = store.Certificates;
  276. // Find unexpired certificates.
  277. X509Certificate2Collection currentCerts = certCollection.Find(X509FindType.FindByTimeValid, DateTime.Now, false);
  278. // From the collection of unexpired certificates, find the ones with the correct name.
  279. X509Certificate2Collection signingCert = currentCerts.Find(X509FindType.FindByThumbprint, thumbprint, false);
  280. // Return the first certificate in the collection, has the right name and is current.
  281. cert = signingCert.OfType<X509Certificate2>().OrderByDescending(c => c.NotBefore).FirstOrDefault();
  282. store.Close();
  283. return cert;
  284. }
  285. public async Task<AuthenticationResult> AcquireTokenAsync(string authority, string resource, string claims)
  286. {
  287. AuthenticationResult result = null;
  288. if (authority.ToLower().Contains("common"))
  289. {
  290. var authorityUri = new Uri(authority);
  291. authority = String.Format("https://{0}/{1}", authorityUri.Host, _aipConfig.TenantId);
  292. }
  293. var app = PublicClientApplicationBuilder.Create(_aipConfig.ClientId)
  294. .WithAuthority(authority)
  295. .WithDefaultRedirectUri()
  296. .Build();
  297. var accounts = (app.GetAccountsAsync()).GetAwaiter().GetResult();
  298. // Append .default to the resource passed in to AcquireToken().
  299. string[] scopes = { resource[resource.Length - 1].Equals('/') ? $"{resource}.default" : $"{resource}/.default" };
  300. try
  301. {
  302. result = await app.AcquireTokenSilent(new[] { "https://aadrm.com/user_impersonation" }, accounts.FirstOrDefault()).ExecuteAsync();
  303. // result = await _app.AcquireTokenSilent(scopes,
  304. // accounts.FirstOrDefault())
  305. // .ExecuteAsync();
  306. }
  307. catch (MsalUiRequiredException)
  308. {
  309. result = app.AcquireTokenInteractive(scopes)
  310. .WithAccount(accounts.FirstOrDefault())
  311. .WithPrompt(Prompt.SelectAccount)
  312. .ExecuteAsync()
  313. .ConfigureAwait(false)
  314. .GetAwaiter()
  315. .GetResult();
  316. }
  317. // Return the token. The token is sent to the resource.
  318. return result;
  319. }
  320. // private async Task<string> GetAccessTokenOnBehalfOfUser(string authority, string resource)
  321. // {
  322. // IConfidentialClientApplication app;
  323. //
  324. // if (false)
  325. // {
  326. // // Read X509 cert from local store and build ClientAssertionCertificate.
  327. // X509Certificate2 cert = Utilities.ReadCertificateFromStore(_aipConfig.CertThumbPrint);
  328. //
  329. // // Create confidential client using certificate.
  330. // app = ConfidentialClientApplicationBuilder.Create(_aipConfig.ClientId)
  331. // .WithRedirectUri(resource)
  332. // .WithAuthority(authority)
  333. // .WithCertificate(cert)
  334. // .Build();
  335. // }
  336. //
  337. // else
  338. // {
  339. // // Create confidential client using client secret.
  340. // app = ConfidentialClientApplicationBuilder.Create(_aipConfig.ClientId)
  341. // .WithRedirectUri(resource)
  342. // .WithAuthority(authority)
  343. // .WithClientSecret(_aipConfig.SecretValue)
  344. // .Build();
  345. // }
  346. //
  347. // // Store user access token of authenticated user.
  348. // var ci = (ClaimsIdentity)_claimsPrincipal.Identity;
  349. // string userAccessToken = (string)ci.BootstrapContext;
  350. //
  351. //
  352. // // Generate a user assertion with the UPN and access token.
  353. // UserAssertion userAssertion = new UserAssertion(userAccessToken, "urn:ietf:params:oauth:grant-type:jwt-bearer");
  354. //
  355. // // Append .default to the resource passed in to AcquireToken().
  356. // List<string> scopes = new List<string>() { resource[resource.Length - 1].Equals('/') ? $"{resource}.default" : $"{resource}/.default" };
  357. //
  358. // AuthenticationResult result = await app.AcquireTokenOnBehalfOf(scopes, userAssertion)
  359. // .ExecuteAsync();
  360. //
  361. // // Return the token to the API caller
  362. // return (result.AccessToken);
  363. // }
  364. }
  365. }