AuthDelegateImplementation.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  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;
  7. using System.Threading.Tasks;
  8. using Microsoft.Identity.Client;
  9. using Microsoft.Identity.Client.Extensions.Msal;
  10. using Microsoft.InformationProtection;
  11. using Serilog;
  12. namespace AipGateway.AIP
  13. {
  14. public class AuthDelegateImplementation : IAuthDelegate
  15. {
  16. private readonly ILogger _log;
  17. public int LastErrNo { get; set; }
  18. public string LastErrMsg { get; set; }
  19. private readonly AipConfig _aipConfig;
  20. private readonly IConfidentialClientApplication _confidentialApp = null;
  21. private readonly IPublicClientApplication _publicApp = null;
  22. // [Obsolete("Obsolete")] private TokenCache _tokenCache = new TokenCache();
  23. public AuthDelegateImplementation(Serilog.Core.Logger logger, AipConfig aipConfig)
  24. {
  25. _log = logger.ForContext<AuthDelegateImplementation>();
  26. _aipConfig = aipConfig;
  27. LastErrNo = 0;
  28. LastErrMsg = String.Empty;
  29. var storageProperties =
  30. new StorageCreationPropertiesBuilder(
  31. "AIPGateway.Cache",
  32. Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location))
  33. .Build();
  34. var cacheHelper = MsalCacheHelper.CreateAsync(storageProperties).GetAwaiter().GetResult();
  35. if (_aipConfig.LoginType == AipAuthLoginType.authLoginPassword)
  36. {
  37. // _confidentialApp = ConfidentialClientApplicationBuilder
  38. // .Create(_aipConfig.ClientId)
  39. // .WithClientSecret(_aipConfig.SecretValue)
  40. // // .WithAuthority($"https://login.microsoftonline.com/{_aipConfig.TenantId}")
  41. // // .WithCacheOptions(CacheOptions.EnableSharedCacheOptions)
  42. // .WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient")
  43. // .Build();
  44. // var authority = $"https://login.windows.net/{_aipConfig.TenantId}";
  45. // BrokerOptions brokerOptions = new BrokerOptions(BrokerOptions.OperatingSystems.Windows);
  46. // _confidentialApp = PublicClientApplicationBuilder
  47. // .Create(_aipConfig.ClientId)
  48. // .WithAuthority(authority)
  49. // .WithDefaultRedirectUri()
  50. // .WithBroker(brokerOptions)
  51. // .Build();
  52. ConfidentialClientApplicationOptions options = new ConfidentialClientApplicationOptions()
  53. {
  54. ClientSecret = _aipConfig.SecretValue,
  55. ClientId = _aipConfig.ClientId,
  56. TenantId = _aipConfig.TenantId,
  57. RedirectUri = "https://login.microsoftonline.com/common/oauth2/nativeclient",
  58. Instance = "https://login.microsoftonline.com/"
  59. };
  60. _confidentialApp = ConfidentialClientApplicationBuilder
  61. .CreateWithApplicationOptions(options)
  62. .WithRedirectUri(options.RedirectUri)
  63. // .WithLegacyCacheCompatibility(false)
  64. // .WithExperimentalFeatures() // for PoP
  65. // .WithCacheOptions(CacheOptions.EnableSharedCacheOptions)
  66. .Build();
  67. // IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.CreateWithApplicationOptions(options)
  68. // .WithCertificate(certificate)
  69. // .Build();
  70. //Console.WriteLine(" AppTokenCache: {0}", _confidentialApp.AppTokenCache);
  71. //Console.WriteLine("UserTokenCache: {0}", _confidentialApp.UserTokenCache);
  72. cacheHelper.RegisterCache(_confidentialApp.UserTokenCache);
  73. }
  74. else if (_aipConfig.LoginType == AipAuthLoginType.authLoginCert)
  75. {
  76. var authority = $"https://login.windows.net/{_aipConfig.TenantId}";
  77. var myCertificate = new X509Certificate2(_aipConfig.CertThumbPrint, "hanteinfo1234!");
  78. X509Certificate2 certificate = new X509Certificate2(myCertificate);
  79. _confidentialApp = ConfidentialClientApplicationBuilder.Create(_aipConfig.ClientId)
  80. .WithCertificate(certificate)
  81. .WithAuthority(new Uri(authority))
  82. .Build();
  83. cacheHelper.RegisterCache(_confidentialApp.UserTokenCache);
  84. }
  85. else
  86. {
  87. _publicApp = PublicClientApplicationBuilder.Create(_aipConfig.ClientId)
  88. .WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient")
  89. .WithAuthority(AzureCloudInstance.AzurePublic, _aipConfig.TenantId)
  90. .Build();
  91. cacheHelper.RegisterCache(_publicApp.UserTokenCache);
  92. }
  93. }
  94. public void ResetError()
  95. {
  96. LastErrNo = 0;
  97. LastErrMsg = string.Empty;
  98. }
  99. private void SetError(int errNo, string errMsg1, string errMsg2 = "No Exception Message.")
  100. {
  101. LastErrNo = errNo;
  102. LastErrMsg = errMsg1 + "\r\n" + errMsg2;
  103. _log.Error("AuthDelegateImplementation::SetError ==> {0}, {1}, {2}", errNo, errMsg1, errMsg2);
  104. }
  105. public string AcquireToken(Identity identity, string authority, string resource, string claim)
  106. {
  107. //Console.WriteLine("AuthDelegateImplementation::AcquireToken ==> LoginType: {0}", _aipConfig.LoginType);
  108. if (_aipConfig.LoginType == AipAuthLoginType.authLoginPassword)
  109. {
  110. return AcquireTokenByCertificate(identity, authority, resource, claim);
  111. }
  112. if (_aipConfig.LoginType == AipAuthLoginType.authLoginCert)
  113. {
  114. return AcquireTokenByCertificate(identity, authority, resource, claim);
  115. }
  116. return AcquireTokenById(identity, authority, resource, claim);
  117. }
  118. private string AcquireTokenByCertificate(Identity identity, string authority, string resource, string claims)
  119. {
  120. //ddddddddddddddddddddddddddddddddd: https://login.windows.net/common, https://syncservice.o365syncservice.com/
  121. //ddddddddddddddddddddddddddddddddd: https://login.windows.net/common, https://aadrm.com
  122. //Console.WriteLine("ddddddddddddddddddddddddddddddddd: {0}, {1}", authority, resource);
  123. // AuthenticationResult result;
  124. // var authorityUri = new Uri(authority);
  125. // authority = $"https://{authorityUri.Host}/{_aipConfig.TenantId}";
  126. var scopes = new[] { resource[resource.Length - 1].Equals('/') ? $"{resource}.default" : $"{resource}/.default" };
  127. try
  128. {
  129. #if false
  130. IConfidentialClientApplication app;
  131. ConfidentialClientApplicationOptions options = new ConfidentialClientApplicationOptions()
  132. {
  133. ClientSecret = _aipConfig.SecretValue,
  134. ClientId = _aipConfig.ClientId,
  135. TenantId = _aipConfig.TenantId,
  136. RedirectUri = "https://login.microsoftonline.com/common/oauth2/nativeclient",
  137. Instance = "https://login.microsoftonline.com/"
  138. };
  139. app = ConfidentialClientApplicationBuilder
  140. .CreateWithApplicationOptions(options)
  141. .WithRedirectUri(options.RedirectUri)
  142. // .WithLegacyCacheCompatibility(false)
  143. // .WithExperimentalFeatures() // for PoP
  144. // .WithCacheOptions(CacheOptions.EnableSharedCacheOptions)
  145. .Build();
  146. #endif
  147. AuthenticationResult result = null;
  148. //Console.WriteLine("B: AppTokenCache: {0}", _confidentialApp.AppTokenCache);
  149. //Console.WriteLine("B: UserTokenCache: {0}", _confidentialApp.UserTokenCache);
  150. #if false
  151. result = _confidentialApp.AcquireTokenForClient(scopes)
  152. .ExecuteAsync()
  153. .GetAwaiter()
  154. .GetResult();
  155. result = _confidentialApp.AcquireTokenForClient(scopes).ExecuteAsync().Result;
  156. #else
  157. result = _confidentialApp.AcquireTokenForClient(scopes)
  158. .ExecuteAsync(CancellationToken.None)
  159. .ConfigureAwait(false)
  160. .GetAwaiter()
  161. .GetResult();
  162. #endif
  163. //_log.Information("AcquireTokenByCertificate: {0}, {1}", identity.Email, identity.Name);
  164. // result = _confidentialApp.AcquireTokenForClient(scopes)
  165. // .WithTenantId(_aipConfig.TenantId)
  166. // .ExecuteAsync()
  167. // .GetAwaiter()
  168. // .GetResult();
  169. // result = _confidentialApp.AcquireTokenForClient(scopes).ExecuteAsync().GetAwaiter().GetResult();
  170. //Console.WriteLine("A: AppTokenCache: {0}", _confidentialApp.AppTokenCache);
  171. //Console.WriteLine("A: UserTokenCache: {0}", _confidentialApp.UserTokenCache);
  172. //Console.WriteLine("A: AccessToken: {0}", result.AccessToken);
  173. return result.AccessToken;
  174. }
  175. catch (MsalUiRequiredException ex) when (ex.Message.Contains("AADSTS70011"))
  176. {
  177. // Invalid scope. The scope has to be of the form "https://resourceurl/.default"
  178. // Mitigation: change the scope to be as expected
  179. SetError(1, "AcquireTokenByCertificate::AcquireTokenByCertificate, Scope provided is not supported.", ex.Message);
  180. }
  181. catch (Exception ex)
  182. {
  183. SetError(1, "AcquireTokenByCertificate::AcquireTokenByCertificate Failed.", ex.Message);
  184. }
  185. return null;
  186. }
  187. // private string AcquireTokenByCertificate_Temp(Identity identity, string authority, string resource, string claims)
  188. // {
  189. // AuthenticationResult result;
  190. // var authorityUri = new Uri(authority);
  191. // authority = $"https://{authorityUri.Host}/{_aipConfig.TenantId}";
  192. //
  193. // var scopes = new[] { resource[resource.Length - 1].Equals('/') ? $"{resource}.default" : $"{resource}/.default" };
  194. //
  195. // try
  196. // {
  197. // var accounts = (_confidentialApp.GetAccountsAsync()).GetAwaiter().GetResult();
  198. // result = _confidentialApp.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
  199. // .ExecuteAsync().GetAwaiter().GetResult();
  200. //
  201. // // result = _confidentialApp.AcquireTokenForClient(scopes)
  202. // // .ExecuteAsync().GetAwaiter().GetResult();
  203. //
  204. // Console.WriteLine("AuthDelegateImplementation::AcquireTokenByCertificate ==> AcquireTokenSilent Succeed.");
  205. // return result.AccessToken;
  206. // }
  207. // catch (MsalUiRequiredException)
  208. // {
  209. // Console.WriteLine("AuthDelegateImplementation::AcquireTokenByCertificate ==> AcquireTokenSilent Failed. Login AcquireTokenForClient.");
  210. // try
  211. // {
  212. // result = _confidentialApp.AcquireTokenForClient(scopes)
  213. // .ExecuteAsync().GetAwaiter().GetResult();
  214. // // result = _confidentialApp.AcquireTokenForClient(scopes)
  215. // // .WithTenantId(_aipConfig.TenantId)
  216. // // .ExecuteAsync()
  217. // // .GetAwaiter()
  218. // // .GetResult();
  219. // // result = _confidentialApp.AcquireTokenForClient(scopes).ExecuteAsync().GetAwaiter().GetResult();
  220. // }
  221. // catch (MsalServiceException ex) when (ex.Message.Contains("AADSTS70011"))
  222. // {
  223. // // Invalid scope. The scope has to be of the form "https://resourceurl/.default"
  224. // // Mitigation: change the scope to be as expected
  225. // SetError(1, "AcquireTokenByPassword.Scope provided is not supported.", ex.Message);
  226. // return null;
  227. // }
  228. // }
  229. //
  230. // return result.AccessToken;
  231. // }
  232. private string AcquireTokenById(Identity identity, string authority, string resource, string claims)
  233. {
  234. //Console.WriteLine("AcquireTokenById::AcquireTokenByPassword");
  235. AuthenticationResult result;
  236. var authorityUri = new Uri(authority);
  237. authority = $"https://{authorityUri.Host}/{_aipConfig.TenantId}";
  238. // var app = PublicClientApplicationBuilder.Create(_aipConfig.ClientId)
  239. // .WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient")
  240. // .WithAuthority(AzureCloudInstance.AzurePublic, _aipConfig.TenantId)
  241. // .Build();
  242. var accounts = (_publicApp.GetAccountsAsync()).GetAwaiter().GetResult();
  243. // Append .default to the resource passed in to AcquireToken().
  244. var scopes = new[] { resource[resource.Length - 1].Equals('/') ? $"{resource}.default" : $"{resource}/.default" };
  245. try
  246. {
  247. result = _publicApp.AcquireTokenSilent(scopes, accounts.FirstOrDefault()).ExecuteAsync().Result;
  248. //Console.WriteLine("AuthDelegateImplementation::AcquireTokenById ==> AcquireTokenSilent Succeed.");
  249. return result.AccessToken;
  250. }
  251. catch (MsalUiRequiredException)
  252. {
  253. //Console.WriteLine("AuthDelegateImplementation::AcquireTokenById ==> AcquireTokenSilent Failed. Login AcquireTokenForClient.");
  254. result = _publicApp.AcquireTokenInteractive(scopes)
  255. .WithAccount(accounts.FirstOrDefault())
  256. .WithPrompt(Prompt.SelectAccount)
  257. .ExecuteAsync()
  258. .ConfigureAwait(false)
  259. .GetAwaiter()
  260. .GetResult();
  261. }
  262. catch (Exception ex)
  263. {
  264. SetError(1, "AcquireTokenById.Login Failed.", ex.Message);
  265. return null;
  266. }
  267. return result.AccessToken;
  268. }
  269. public async Task<AuthenticationResult> AcquireTokenAsync(string authority, string resource, string claims)
  270. {
  271. AuthenticationResult result = null;
  272. if (authority.ToLower().Contains("common"))
  273. {
  274. var authorityUri = new Uri(authority);
  275. authority = String.Format("https://{0}/{1}", authorityUri.Host, _aipConfig.TenantId);
  276. }
  277. var app = PublicClientApplicationBuilder.Create(_aipConfig.ClientId)
  278. .WithAuthority(authority)
  279. .WithDefaultRedirectUri()
  280. .Build();
  281. var accounts = (app.GetAccountsAsync()).GetAwaiter().GetResult();
  282. // Append .default to the resource passed in to AcquireToken().
  283. string[] scopes = { resource[resource.Length - 1].Equals('/') ? $"{resource}.default" : $"{resource}/.default" };
  284. try
  285. {
  286. result = await app.AcquireTokenSilent(new[] { "https://aadrm.com/user_impersonation" }, accounts.FirstOrDefault()).ExecuteAsync();
  287. // result = await _app.AcquireTokenSilent(scopes,
  288. // accounts.FirstOrDefault())
  289. // .ExecuteAsync();
  290. }
  291. catch (MsalUiRequiredException)
  292. {
  293. result = app.AcquireTokenInteractive(scopes)
  294. .WithAccount(accounts.FirstOrDefault())
  295. .WithPrompt(Prompt.SelectAccount)
  296. .ExecuteAsync()
  297. .ConfigureAwait(false)
  298. .GetAwaiter()
  299. .GetResult();
  300. }
  301. // Return the token. The token is sent to the resource.
  302. return result;
  303. }
  304. // public Identity GetUserIdentity()
  305. // {
  306. // try
  307. // {
  308. // string resource = "https://graph.microsoft.com/";
  309. //
  310. // AuthenticationContext authContext = new AuthenticationContext("https://login.windows.net/common", tokenCache);
  311. // var result = authContext.AcquireTokenAsync(resource, appInfo.ApplicationId, new Uri(redirectUri), new PlatformParameters(PromptBehavior.Always)).Result;
  312. // return new Identity(result.UserInfo.DisplayableId);
  313. //
  314. // }
  315. // catch (Exception ex)
  316. // {
  317. // throw ex;
  318. // }
  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. }