PageRenderTime 36ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/mcs/class/referencesource/System.ServiceModel/System/ServiceModel/Dispatcher/SecurityImpersonationBehavior.cs

https://github.com/pruiz/mono
C# | 557 lines | 476 code | 59 blank | 22 comment | 101 complexity | 1d07d038d45f4a6c0b4b4a3a808976a4 MD5 | raw file
Possible License(s): LGPL-2.0, MPL-2.0-no-copyleft-exception, CC-BY-SA-3.0, GPL-2.0
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) Microsoft Corporation. All rights reserved.
  3. //-----------------------------------------------------------------------------
  4. namespace System.ServiceModel.Dispatcher
  5. {
  6. using System;
  7. using System.Collections.Generic;
  8. using System.ComponentModel;
  9. using System.Diagnostics;
  10. using System.IdentityModel.Claims;
  11. using System.IdentityModel.Policy;
  12. using System.IdentityModel.Tokens;
  13. using System.Runtime;
  14. using System.Runtime.CompilerServices;
  15. using System.Runtime.InteropServices;
  16. using System.Security;
  17. using System.Security.Principal;
  18. using System.ServiceModel;
  19. using System.ServiceModel.Activation;
  20. using System.ServiceModel.Description;
  21. using System.ServiceModel.Diagnostics;
  22. using System.ServiceModel.Security;
  23. using System.ServiceModel.Security.Tokens;
  24. using System.Text;
  25. using System.Threading;
  26. using ClaimsIdentity = System.Security.Claims.ClaimsIdentity;
  27. using ClaimsPrincipal = System.Security.Claims.ClaimsPrincipal;
  28. using EXTENDED_NAME_FORMAT = System.ServiceModel.ComIntegration.EXTENDED_NAME_FORMAT;
  29. using SafeCloseHandle = System.IdentityModel.SafeCloseHandle;
  30. using SafeNativeMethods = System.ServiceModel.ComIntegration.SafeNativeMethods;
  31. using Win32Error = System.ServiceModel.ComIntegration.Win32Error;
  32. internal sealed class SecurityImpersonationBehavior
  33. {
  34. PrincipalPermissionMode principalPermissionMode;
  35. object roleProvider;
  36. bool impersonateCallerForAllOperations;
  37. Dictionary<string, string> domainNameMap;
  38. Random random;
  39. const int maxDomainNameMapSize = 5;
  40. static WindowsPrincipal anonymousWindowsPrincipal;
  41. AuditLevel auditLevel = ServiceSecurityAuditBehavior.defaultMessageAuthenticationAuditLevel;
  42. AuditLogLocation auditLogLocation = ServiceSecurityAuditBehavior.defaultAuditLogLocation;
  43. bool suppressAuditFailure = ServiceSecurityAuditBehavior.defaultSuppressAuditFailure;
  44. SecurityImpersonationBehavior(DispatchRuntime dispatch)
  45. {
  46. this.principalPermissionMode = dispatch.PrincipalPermissionMode;
  47. this.impersonateCallerForAllOperations = dispatch.ImpersonateCallerForAllOperations;
  48. this.auditLevel = dispatch.MessageAuthenticationAuditLevel;
  49. this.auditLogLocation = dispatch.SecurityAuditLogLocation;
  50. this.suppressAuditFailure = dispatch.SuppressAuditFailure;
  51. if (dispatch.IsRoleProviderSet)
  52. {
  53. ApplyRoleProvider(dispatch);
  54. }
  55. this.domainNameMap = new Dictionary<string, string>(maxDomainNameMapSize, StringComparer.OrdinalIgnoreCase);
  56. }
  57. public static SecurityImpersonationBehavior CreateIfNecessary(DispatchRuntime dispatch)
  58. {
  59. if (IsSecurityBehaviorNeeded(dispatch))
  60. {
  61. return new SecurityImpersonationBehavior(dispatch);
  62. }
  63. else
  64. {
  65. return null;
  66. }
  67. }
  68. static WindowsPrincipal AnonymousWindowsPrincipal
  69. {
  70. get
  71. {
  72. if (anonymousWindowsPrincipal == null)
  73. anonymousWindowsPrincipal = new WindowsPrincipal(WindowsIdentity.GetAnonymous());
  74. return anonymousWindowsPrincipal;
  75. }
  76. }
  77. [MethodImpl(MethodImplOptions.NoInlining)]
  78. void ApplyRoleProvider(DispatchRuntime dispatch)
  79. {
  80. this.roleProvider = dispatch.RoleProvider;
  81. }
  82. static bool IsSecurityBehaviorNeeded(DispatchRuntime dispatch)
  83. {
  84. if (AspNetEnvironment.Current.RequiresImpersonation)
  85. {
  86. return true;
  87. }
  88. if (dispatch.PrincipalPermissionMode != PrincipalPermissionMode.None)
  89. {
  90. return true;
  91. }
  92. // Impersonation behavior is required if
  93. // 1) Contract requires it or
  94. // 2) Contract allows it and config requires it
  95. for (int i = 0; i < dispatch.Operations.Count; i++)
  96. {
  97. DispatchOperation operation = dispatch.Operations[i];
  98. if (operation.Impersonation == ImpersonationOption.Required)
  99. {
  100. return true;
  101. }
  102. else if (operation.Impersonation == ImpersonationOption.NotAllowed)
  103. {
  104. // a validation rule enforces that config cannot require impersonation in this case
  105. return false;
  106. }
  107. }
  108. // contract allows impersonation. Return true if config requires it.
  109. return dispatch.ImpersonateCallerForAllOperations;
  110. }
  111. [MethodImpl(MethodImplOptions.NoInlining)]
  112. IPrincipal SetCurrentThreadPrincipal(ServiceSecurityContext securityContext, out bool isThreadPrincipalSet)
  113. {
  114. IPrincipal result = null;
  115. IPrincipal principal = null;
  116. ClaimsPrincipal claimsPrincipal = OperationContext.Current.ClaimsPrincipal;
  117. if (this.principalPermissionMode == PrincipalPermissionMode.UseWindowsGroups)
  118. {
  119. principal = ( claimsPrincipal is WindowsPrincipal ) ? claimsPrincipal : GetWindowsPrincipal( securityContext );
  120. }
  121. else if (this.principalPermissionMode == PrincipalPermissionMode.UseAspNetRoles)
  122. {
  123. principal = new RoleProviderPrincipal(this.roleProvider, securityContext);
  124. }
  125. else if (this.principalPermissionMode == PrincipalPermissionMode.Custom)
  126. {
  127. principal = GetCustomPrincipal(securityContext);
  128. }
  129. else if (this.principalPermissionMode == PrincipalPermissionMode.Always)
  130. {
  131. principal = claimsPrincipal ?? new ClaimsPrincipal( new ClaimsIdentity() );
  132. }
  133. if (principal != null)
  134. {
  135. result = Thread.CurrentPrincipal;
  136. Thread.CurrentPrincipal = principal;
  137. isThreadPrincipalSet = true;
  138. }
  139. else
  140. {
  141. isThreadPrincipalSet = false;
  142. }
  143. return result;
  144. }
  145. [MethodImpl(MethodImplOptions.NoInlining)]
  146. static IPrincipal GetCustomPrincipal(ServiceSecurityContext securityContext)
  147. {
  148. object customPrincipal;
  149. if (securityContext.AuthorizationContext.Properties.TryGetValue(SecurityUtils.Principal, out customPrincipal) && customPrincipal is IPrincipal)
  150. return (IPrincipal)customPrincipal;
  151. else
  152. throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.NoPrincipalSpecifiedInAuthorizationContext)));
  153. }
  154. internal bool IsSecurityContextImpersonationRequired(ref MessageRpc rpc)
  155. {
  156. return ((rpc.Operation.Impersonation == ImpersonationOption.Required)
  157. || ((rpc.Operation.Impersonation == ImpersonationOption.Allowed) && this.impersonateCallerForAllOperations));
  158. }
  159. internal bool IsImpersonationEnabledOnCurrentOperation(ref MessageRpc rpc)
  160. {
  161. return this.IsSecurityContextImpersonationRequired(ref rpc) ||
  162. AspNetEnvironment.Current.RequiresImpersonation ||
  163. this.principalPermissionMode != PrincipalPermissionMode.None;
  164. }
  165. [Fx.Tag.SecurityNote(Critical = "Calls SecurityCritical method StartImpersonation2."
  166. + "Caller must ensure that this method is called at an appropriate time and that impersonationContext out param is Dispose()'d correctly.")]
  167. [SecurityCritical]
  168. public void StartImpersonation(ref MessageRpc rpc, out IDisposable impersonationContext, out IPrincipal originalPrincipal, out bool isThreadPrincipalSet)
  169. {
  170. impersonationContext = null;
  171. originalPrincipal = null;
  172. isThreadPrincipalSet = false;
  173. ServiceSecurityContext securityContext;
  174. bool setThreadPrincipal = this.principalPermissionMode != PrincipalPermissionMode.None;
  175. bool isSecurityContextImpersonationOn = IsSecurityContextImpersonationRequired(ref rpc);
  176. if (setThreadPrincipal || isSecurityContextImpersonationOn)
  177. securityContext = GetAndCacheSecurityContext(ref rpc);
  178. else
  179. securityContext = null;
  180. if (setThreadPrincipal && securityContext != null)
  181. originalPrincipal = this.SetCurrentThreadPrincipal(securityContext, out isThreadPrincipalSet);
  182. if (isSecurityContextImpersonationOn || AspNetEnvironment.Current.RequiresImpersonation)
  183. {
  184. impersonationContext = StartImpersonation2(ref rpc, securityContext, isSecurityContextImpersonationOn);
  185. }
  186. }
  187. [Fx.Tag.SecurityNote(Critical = "Calls SecurityCritical method HostedImpersonationContext.Impersonate."
  188. + "Caller must ensure that this method is called at an appropriate time and that result is Dispose()'d correctly.")]
  189. [SecurityCritical]
  190. IDisposable StartImpersonation2(ref MessageRpc rpc, ServiceSecurityContext securityContext, bool isSecurityContextImpersonationOn)
  191. {
  192. IDisposable impersonationContext = null;
  193. try
  194. {
  195. if (isSecurityContextImpersonationOn)
  196. {
  197. if (securityContext == null)
  198. throw TraceUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.SFxSecurityContextPropertyMissingFromRequestMessage)), rpc.Request);
  199. WindowsIdentity impersonationToken = securityContext.WindowsIdentity;
  200. if (impersonationToken.User != null)
  201. {
  202. impersonationContext = impersonationToken.Impersonate();
  203. }
  204. else if (securityContext.PrimaryIdentity is WindowsSidIdentity)
  205. {
  206. WindowsSidIdentity sidIdentity = (WindowsSidIdentity)securityContext.PrimaryIdentity;
  207. if (sidIdentity.SecurityIdentifier.IsWellKnown(WellKnownSidType.AnonymousSid))
  208. {
  209. impersonationContext = new WindowsAnonymousIdentity().Impersonate();
  210. }
  211. else
  212. {
  213. string fullyQualifiedDomainName = GetUpnFromDownlevelName(sidIdentity.Name);
  214. using (WindowsIdentity windowsIdentity = new WindowsIdentity(fullyQualifiedDomainName, SecurityUtils.AuthTypeKerberos))
  215. {
  216. impersonationContext = windowsIdentity.Impersonate();
  217. }
  218. }
  219. }
  220. else
  221. throw TraceUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.SecurityContextDoesNotAllowImpersonation, rpc.Operation.Action)), rpc.Request);
  222. }
  223. else if (AspNetEnvironment.Current.RequiresImpersonation)
  224. {
  225. if (rpc.HostingProperty != null)
  226. {
  227. impersonationContext = rpc.HostingProperty.Impersonate();
  228. }
  229. }
  230. SecurityTraceRecordHelper.TraceImpersonationSucceeded(rpc.EventTraceActivity, rpc.Operation);
  231. // update the impersonation succeed audit
  232. if (AuditLevel.Success == (this.auditLevel & AuditLevel.Success))
  233. {
  234. SecurityAuditHelper.WriteImpersonationSuccessEvent(this.auditLogLocation,
  235. this.suppressAuditFailure, rpc.Operation.Name, SecurityUtils.GetIdentityNamesFromContext(securityContext.AuthorizationContext));
  236. }
  237. }
  238. catch (Exception ex)
  239. {
  240. if (Fx.IsFatal(ex))
  241. {
  242. throw;
  243. }
  244. SecurityTraceRecordHelper.TraceImpersonationFailed(rpc.EventTraceActivity, rpc.Operation, ex);
  245. //
  246. // Update the impersonation failure audit
  247. // Copy SecurityAuthorizationBehavior.Audit level to here!!!
  248. //
  249. if (AuditLevel.Failure == (this.auditLevel & AuditLevel.Failure))
  250. {
  251. try
  252. {
  253. string primaryIdentity;
  254. if (securityContext != null)
  255. primaryIdentity = SecurityUtils.GetIdentityNamesFromContext(securityContext.AuthorizationContext);
  256. else
  257. primaryIdentity = SecurityUtils.AnonymousIdentity.Name;
  258. SecurityAuditHelper.WriteImpersonationFailureEvent(this.auditLogLocation,
  259. this.suppressAuditFailure, rpc.Operation.Name, primaryIdentity, ex);
  260. }
  261. #pragma warning suppress 56500
  262. catch (Exception auditException)
  263. {
  264. if (Fx.IsFatal(auditException))
  265. throw;
  266. DiagnosticUtility.TraceHandledException(auditException, TraceEventType.Error);
  267. }
  268. }
  269. throw;
  270. }
  271. return impersonationContext;
  272. }
  273. public void StopImpersonation(ref MessageRpc rpc, IDisposable impersonationContext, IPrincipal originalPrincipal, bool isThreadPrincipalSet)
  274. {
  275. try
  276. {
  277. if (IsSecurityContextImpersonationRequired(ref rpc) || AspNetEnvironment.Current.RequiresImpersonation)
  278. {
  279. if (impersonationContext != null)
  280. {
  281. impersonationContext.Dispose();
  282. }
  283. }
  284. if (isThreadPrincipalSet)
  285. {
  286. Thread.CurrentPrincipal = originalPrincipal;
  287. }
  288. }
  289. #pragma warning suppress 56500 // covered by FxCOP
  290. catch
  291. {
  292. string message = null;
  293. try
  294. {
  295. message = SR.GetString(SR.SFxRevertImpersonationFailed0);
  296. }
  297. finally
  298. {
  299. DiagnosticUtility.FailFast(message);
  300. }
  301. }
  302. }
  303. IPrincipal GetWindowsPrincipal(ServiceSecurityContext securityContext)
  304. {
  305. WindowsIdentity wid = securityContext.WindowsIdentity;
  306. if (!wid.IsAnonymous)
  307. return new WindowsPrincipal(wid);
  308. WindowsSidIdentity wsid = securityContext.PrimaryIdentity as WindowsSidIdentity;
  309. if (wsid != null)
  310. return new WindowsSidPrincipal(wsid, securityContext);
  311. return AnonymousWindowsPrincipal;
  312. }
  313. ServiceSecurityContext GetAndCacheSecurityContext(ref MessageRpc rpc)
  314. {
  315. ServiceSecurityContext securityContext = rpc.SecurityContext;
  316. if (!rpc.HasSecurityContext)
  317. {
  318. SecurityMessageProperty securityContextProperty = rpc.Request.Properties.Security;
  319. if (securityContextProperty == null)
  320. securityContext = null; // SecurityContext.Anonymous
  321. else
  322. {
  323. securityContext = securityContextProperty.ServiceSecurityContext;
  324. if (securityContext == null)
  325. throw TraceUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.SecurityContextMissing, rpc.Operation.Name)), rpc.Request);
  326. }
  327. rpc.SecurityContext = securityContext;
  328. rpc.HasSecurityContext = true;
  329. }
  330. return securityContext;
  331. }
  332. string GetUpnFromDownlevelName(string downlevelName)
  333. {
  334. if (downlevelName == null)
  335. {
  336. throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("downlevelName");
  337. }
  338. int delimiterPos = downlevelName.IndexOf('\\');
  339. if ((delimiterPos < 0) || (delimiterPos == 0) || (delimiterPos == downlevelName.Length - 1))
  340. {
  341. throw DiagnosticUtility.ExceptionUtility.ThrowHelperWarning(new InvalidOperationException(SR.GetString(SR.DownlevelNameCannotMapToUpn, downlevelName)));
  342. }
  343. string shortDomainName = downlevelName.Substring(0, delimiterPos + 1);
  344. string userName = downlevelName.Substring(delimiterPos + 1);
  345. string fullDomainName;
  346. bool found;
  347. // 1) Read from cache
  348. lock (this.domainNameMap)
  349. {
  350. found = this.domainNameMap.TryGetValue(shortDomainName, out fullDomainName);
  351. }
  352. // 2) Not found, do expensive look up
  353. if (!found)
  354. {
  355. uint capacity = 50;
  356. StringBuilder fullyQualifiedDomainName = new StringBuilder((int)capacity);
  357. if (!SafeNativeMethods.TranslateName(shortDomainName, EXTENDED_NAME_FORMAT.NameSamCompatible, EXTENDED_NAME_FORMAT.NameCanonical,
  358. fullyQualifiedDomainName, out capacity))
  359. {
  360. int errorCode = Marshal.GetLastWin32Error();
  361. if (errorCode == (int)Win32Error.ERROR_INSUFFICIENT_BUFFER)
  362. {
  363. fullyQualifiedDomainName = new StringBuilder((int)capacity);
  364. if (!SafeNativeMethods.TranslateName(shortDomainName, EXTENDED_NAME_FORMAT.NameSamCompatible, EXTENDED_NAME_FORMAT.NameCanonical,
  365. fullyQualifiedDomainName, out capacity))
  366. {
  367. errorCode = Marshal.GetLastWin32Error();
  368. throw DiagnosticUtility.ExceptionUtility.ThrowHelperWarning(new InvalidOperationException(SR.GetString(SR.DownlevelNameCannotMapToUpn, downlevelName), new Win32Exception(errorCode)));
  369. }
  370. }
  371. else
  372. {
  373. throw DiagnosticUtility.ExceptionUtility.ThrowHelperWarning(new InvalidOperationException(SR.GetString(SR.DownlevelNameCannotMapToUpn, downlevelName), new Win32Exception(errorCode)));
  374. }
  375. }
  376. // trim the trailing / from fqdn
  377. fullyQualifiedDomainName = fullyQualifiedDomainName.Remove(fullyQualifiedDomainName.Length - 1, 1);
  378. fullDomainName = fullyQualifiedDomainName.ToString();
  379. // 3) Save in cache (remove a random item if cache is full)
  380. lock (this.domainNameMap)
  381. {
  382. if (this.domainNameMap.Count >= maxDomainNameMapSize)
  383. {
  384. if (this.random == null)
  385. {
  386. this.random = new Random(unchecked((int)DateTime.Now.Ticks));
  387. }
  388. int victim = this.random.Next() % this.domainNameMap.Count;
  389. foreach (string key in this.domainNameMap.Keys)
  390. {
  391. if (victim <= 0)
  392. {
  393. this.domainNameMap.Remove(key);
  394. break;
  395. }
  396. --victim;
  397. }
  398. }
  399. this.domainNameMap[shortDomainName] = fullDomainName;
  400. }
  401. }
  402. return userName + "@" + fullDomainName;
  403. }
  404. class WindowsSidPrincipal : IPrincipal
  405. {
  406. WindowsSidIdentity identity;
  407. ServiceSecurityContext securityContext;
  408. public WindowsSidPrincipal(WindowsSidIdentity identity, ServiceSecurityContext securityContext)
  409. {
  410. this.identity = identity;
  411. this.securityContext = securityContext;
  412. }
  413. public IIdentity Identity
  414. {
  415. get { return this.identity; }
  416. }
  417. public bool IsInRole(string role)
  418. {
  419. if (role == null)
  420. throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("role");
  421. NTAccount account = new NTAccount(role);
  422. Claim claim = Claim.CreateWindowsSidClaim((SecurityIdentifier)account.Translate(typeof(SecurityIdentifier)));
  423. AuthorizationContext authContext = this.securityContext.AuthorizationContext;
  424. for (int i = 0; i < authContext.ClaimSets.Count; i++)
  425. {
  426. ClaimSet claimSet = authContext.ClaimSets[i];
  427. if (claimSet.ContainsClaim(claim))
  428. return true;
  429. }
  430. return false;
  431. }
  432. }
  433. class WindowsAnonymousIdentity
  434. {
  435. public IDisposable Impersonate()
  436. {
  437. // PreSharp Bug: Call 'Marshal.GetLastWin32Error' or 'Marshal.GetHRForLastWin32Error' before any other interop call.
  438. #pragma warning suppress 56523 // The LastWin32Error can be ignored here.
  439. IntPtr threadHandle = SafeNativeMethods.GetCurrentThread();
  440. SafeCloseHandle tokenHandle;
  441. if (!SafeNativeMethods.OpenCurrentThreadToken(threadHandle, TokenAccessLevels.Impersonate, true, out tokenHandle))
  442. {
  443. int error = Marshal.GetLastWin32Error();
  444. System.ServiceModel.Diagnostics.Utility.CloseInvalidOutSafeHandle(tokenHandle);
  445. if (error == (int)System.ServiceModel.ComIntegration.Win32Error.ERROR_NO_TOKEN)
  446. {
  447. tokenHandle = new SafeCloseHandle(IntPtr.Zero, false);
  448. }
  449. else
  450. {
  451. throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new Win32Exception(error));
  452. }
  453. }
  454. if (!SafeNativeMethods.ImpersonateAnonymousUserOnCurrentThread(threadHandle))
  455. {
  456. int error = Marshal.GetLastWin32Error();
  457. throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new Win32Exception(error));
  458. }
  459. return new ImpersonationContext(threadHandle, tokenHandle);
  460. }
  461. class ImpersonationContext : IDisposable
  462. {
  463. IntPtr threadHandle;
  464. SafeCloseHandle tokenHandle;
  465. bool disposed = false;
  466. public ImpersonationContext(IntPtr threadHandle, SafeCloseHandle tokenHandle)
  467. {
  468. this.threadHandle = threadHandle;
  469. this.tokenHandle = tokenHandle;
  470. }
  471. void Undo()
  472. {
  473. // PreSharp Bug: Call 'Marshal.GetLastWin32Error' or 'Marshal.GetHRForLastWin32Error' before any other interop call.
  474. #pragma warning suppress 56523 // The LastWin32Error can be ignored here.
  475. Fx.Assert(this.threadHandle == SafeNativeMethods.GetCurrentThread(), "");
  476. // We are in the Dispose method. If a failure occurs we just have to ignore it.
  477. // PreSharp Bug: Call 'Marshal.GetLastWin32Error' or 'Marshal.GetHRForLastWin32Error' before any other interop call.
  478. // #pragma warning suppress 56523 // The LastWin32Error can be ignored here.
  479. if (!SafeNativeMethods.SetCurrentThreadToken(IntPtr.Zero, this.tokenHandle))
  480. {
  481. int error = Marshal.GetLastWin32Error();
  482. throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityException(SR.GetString(SR.RevertImpersonationFailure,
  483. new Win32Exception(error).Message)));
  484. }
  485. tokenHandle.Close();
  486. }
  487. public void Dispose()
  488. {
  489. if (!this.disposed)
  490. {
  491. Undo();
  492. }
  493. this.disposed = true;
  494. }
  495. }
  496. }
  497. }
  498. }