/mcs/class/referencesource/System.ServiceModel/System/ServiceModel/Dispatcher/SecurityImpersonationBehavior.cs
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
- //-----------------------------------------------------------------------------
- // Copyright (c) Microsoft Corporation. All rights reserved.
- //-----------------------------------------------------------------------------
- namespace System.ServiceModel.Dispatcher
- {
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Diagnostics;
- using System.IdentityModel.Claims;
- using System.IdentityModel.Policy;
- using System.IdentityModel.Tokens;
- using System.Runtime;
- using System.Runtime.CompilerServices;
- using System.Runtime.InteropServices;
- using System.Security;
- using System.Security.Principal;
- using System.ServiceModel;
- using System.ServiceModel.Activation;
- using System.ServiceModel.Description;
- using System.ServiceModel.Diagnostics;
- using System.ServiceModel.Security;
- using System.ServiceModel.Security.Tokens;
- using System.Text;
- using System.Threading;
- using ClaimsIdentity = System.Security.Claims.ClaimsIdentity;
- using ClaimsPrincipal = System.Security.Claims.ClaimsPrincipal;
- using EXTENDED_NAME_FORMAT = System.ServiceModel.ComIntegration.EXTENDED_NAME_FORMAT;
- using SafeCloseHandle = System.IdentityModel.SafeCloseHandle;
- using SafeNativeMethods = System.ServiceModel.ComIntegration.SafeNativeMethods;
- using Win32Error = System.ServiceModel.ComIntegration.Win32Error;
-
- internal sealed class SecurityImpersonationBehavior
- {
- PrincipalPermissionMode principalPermissionMode;
- object roleProvider;
- bool impersonateCallerForAllOperations;
- Dictionary<string, string> domainNameMap;
- Random random;
- const int maxDomainNameMapSize = 5;
- static WindowsPrincipal anonymousWindowsPrincipal;
- AuditLevel auditLevel = ServiceSecurityAuditBehavior.defaultMessageAuthenticationAuditLevel;
- AuditLogLocation auditLogLocation = ServiceSecurityAuditBehavior.defaultAuditLogLocation;
- bool suppressAuditFailure = ServiceSecurityAuditBehavior.defaultSuppressAuditFailure;
- SecurityImpersonationBehavior(DispatchRuntime dispatch)
- {
- this.principalPermissionMode = dispatch.PrincipalPermissionMode;
- this.impersonateCallerForAllOperations = dispatch.ImpersonateCallerForAllOperations;
- this.auditLevel = dispatch.MessageAuthenticationAuditLevel;
- this.auditLogLocation = dispatch.SecurityAuditLogLocation;
- this.suppressAuditFailure = dispatch.SuppressAuditFailure;
- if (dispatch.IsRoleProviderSet)
- {
- ApplyRoleProvider(dispatch);
- }
- this.domainNameMap = new Dictionary<string, string>(maxDomainNameMapSize, StringComparer.OrdinalIgnoreCase);
- }
- public static SecurityImpersonationBehavior CreateIfNecessary(DispatchRuntime dispatch)
- {
- if (IsSecurityBehaviorNeeded(dispatch))
- {
- return new SecurityImpersonationBehavior(dispatch);
- }
- else
- {
- return null;
- }
- }
- static WindowsPrincipal AnonymousWindowsPrincipal
- {
- get
- {
- if (anonymousWindowsPrincipal == null)
- anonymousWindowsPrincipal = new WindowsPrincipal(WindowsIdentity.GetAnonymous());
- return anonymousWindowsPrincipal;
- }
- }
- [MethodImpl(MethodImplOptions.NoInlining)]
- void ApplyRoleProvider(DispatchRuntime dispatch)
- {
- this.roleProvider = dispatch.RoleProvider;
- }
- static bool IsSecurityBehaviorNeeded(DispatchRuntime dispatch)
- {
- if (AspNetEnvironment.Current.RequiresImpersonation)
- {
- return true;
- }
- if (dispatch.PrincipalPermissionMode != PrincipalPermissionMode.None)
- {
- return true;
- }
- // Impersonation behavior is required if
- // 1) Contract requires it or
- // 2) Contract allows it and config requires it
- for (int i = 0; i < dispatch.Operations.Count; i++)
- {
- DispatchOperation operation = dispatch.Operations[i];
- if (operation.Impersonation == ImpersonationOption.Required)
- {
- return true;
- }
- else if (operation.Impersonation == ImpersonationOption.NotAllowed)
- {
- // a validation rule enforces that config cannot require impersonation in this case
- return false;
- }
- }
- // contract allows impersonation. Return true if config requires it.
- return dispatch.ImpersonateCallerForAllOperations;
- }
- [MethodImpl(MethodImplOptions.NoInlining)]
- IPrincipal SetCurrentThreadPrincipal(ServiceSecurityContext securityContext, out bool isThreadPrincipalSet)
- {
- IPrincipal result = null;
- IPrincipal principal = null;
- ClaimsPrincipal claimsPrincipal = OperationContext.Current.ClaimsPrincipal;
- if (this.principalPermissionMode == PrincipalPermissionMode.UseWindowsGroups)
- {
- principal = ( claimsPrincipal is WindowsPrincipal ) ? claimsPrincipal : GetWindowsPrincipal( securityContext );
- }
- else if (this.principalPermissionMode == PrincipalPermissionMode.UseAspNetRoles)
- {
- principal = new RoleProviderPrincipal(this.roleProvider, securityContext);
- }
- else if (this.principalPermissionMode == PrincipalPermissionMode.Custom)
- {
- principal = GetCustomPrincipal(securityContext);
- }
- else if (this.principalPermissionMode == PrincipalPermissionMode.Always)
- {
- principal = claimsPrincipal ?? new ClaimsPrincipal( new ClaimsIdentity() );
- }
- if (principal != null)
- {
- result = Thread.CurrentPrincipal;
- Thread.CurrentPrincipal = principal;
- isThreadPrincipalSet = true;
- }
- else
- {
- isThreadPrincipalSet = false;
- }
- return result;
- }
- [MethodImpl(MethodImplOptions.NoInlining)]
- static IPrincipal GetCustomPrincipal(ServiceSecurityContext securityContext)
- {
- object customPrincipal;
- if (securityContext.AuthorizationContext.Properties.TryGetValue(SecurityUtils.Principal, out customPrincipal) && customPrincipal is IPrincipal)
- return (IPrincipal)customPrincipal;
- else
- throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.NoPrincipalSpecifiedInAuthorizationContext)));
- }
- internal bool IsSecurityContextImpersonationRequired(ref MessageRpc rpc)
- {
- return ((rpc.Operation.Impersonation == ImpersonationOption.Required)
- || ((rpc.Operation.Impersonation == ImpersonationOption.Allowed) && this.impersonateCallerForAllOperations));
- }
- internal bool IsImpersonationEnabledOnCurrentOperation(ref MessageRpc rpc)
- {
- return this.IsSecurityContextImpersonationRequired(ref rpc) ||
- AspNetEnvironment.Current.RequiresImpersonation ||
- this.principalPermissionMode != PrincipalPermissionMode.None;
- }
- [Fx.Tag.SecurityNote(Critical = "Calls SecurityCritical method StartImpersonation2."
- + "Caller must ensure that this method is called at an appropriate time and that impersonationContext out param is Dispose()'d correctly.")]
- [SecurityCritical]
- public void StartImpersonation(ref MessageRpc rpc, out IDisposable impersonationContext, out IPrincipal originalPrincipal, out bool isThreadPrincipalSet)
- {
- impersonationContext = null;
- originalPrincipal = null;
- isThreadPrincipalSet = false;
- ServiceSecurityContext securityContext;
- bool setThreadPrincipal = this.principalPermissionMode != PrincipalPermissionMode.None;
- bool isSecurityContextImpersonationOn = IsSecurityContextImpersonationRequired(ref rpc);
- if (setThreadPrincipal || isSecurityContextImpersonationOn)
- securityContext = GetAndCacheSecurityContext(ref rpc);
- else
- securityContext = null;
- if (setThreadPrincipal && securityContext != null)
- originalPrincipal = this.SetCurrentThreadPrincipal(securityContext, out isThreadPrincipalSet);
- if (isSecurityContextImpersonationOn || AspNetEnvironment.Current.RequiresImpersonation)
- {
- impersonationContext = StartImpersonation2(ref rpc, securityContext, isSecurityContextImpersonationOn);
- }
- }
- [Fx.Tag.SecurityNote(Critical = "Calls SecurityCritical method HostedImpersonationContext.Impersonate."
- + "Caller must ensure that this method is called at an appropriate time and that result is Dispose()'d correctly.")]
- [SecurityCritical]
- IDisposable StartImpersonation2(ref MessageRpc rpc, ServiceSecurityContext securityContext, bool isSecurityContextImpersonationOn)
- {
- IDisposable impersonationContext = null;
- try
- {
- if (isSecurityContextImpersonationOn)
- {
- if (securityContext == null)
- throw TraceUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.SFxSecurityContextPropertyMissingFromRequestMessage)), rpc.Request);
- WindowsIdentity impersonationToken = securityContext.WindowsIdentity;
- if (impersonationToken.User != null)
- {
- impersonationContext = impersonationToken.Impersonate();
- }
- else if (securityContext.PrimaryIdentity is WindowsSidIdentity)
- {
- WindowsSidIdentity sidIdentity = (WindowsSidIdentity)securityContext.PrimaryIdentity;
- if (sidIdentity.SecurityIdentifier.IsWellKnown(WellKnownSidType.AnonymousSid))
- {
- impersonationContext = new WindowsAnonymousIdentity().Impersonate();
- }
- else
- {
- string fullyQualifiedDomainName = GetUpnFromDownlevelName(sidIdentity.Name);
- using (WindowsIdentity windowsIdentity = new WindowsIdentity(fullyQualifiedDomainName, SecurityUtils.AuthTypeKerberos))
- {
- impersonationContext = windowsIdentity.Impersonate();
- }
- }
- }
- else
- throw TraceUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.SecurityContextDoesNotAllowImpersonation, rpc.Operation.Action)), rpc.Request);
- }
- else if (AspNetEnvironment.Current.RequiresImpersonation)
- {
- if (rpc.HostingProperty != null)
- {
- impersonationContext = rpc.HostingProperty.Impersonate();
- }
- }
- SecurityTraceRecordHelper.TraceImpersonationSucceeded(rpc.EventTraceActivity, rpc.Operation);
- // update the impersonation succeed audit
- if (AuditLevel.Success == (this.auditLevel & AuditLevel.Success))
- {
- SecurityAuditHelper.WriteImpersonationSuccessEvent(this.auditLogLocation,
- this.suppressAuditFailure, rpc.Operation.Name, SecurityUtils.GetIdentityNamesFromContext(securityContext.AuthorizationContext));
- }
- }
- catch (Exception ex)
- {
- if (Fx.IsFatal(ex))
- {
- throw;
- }
- SecurityTraceRecordHelper.TraceImpersonationFailed(rpc.EventTraceActivity, rpc.Operation, ex);
- //
- // Update the impersonation failure audit
- // Copy SecurityAuthorizationBehavior.Audit level to here!!!
- //
- if (AuditLevel.Failure == (this.auditLevel & AuditLevel.Failure))
- {
- try
- {
- string primaryIdentity;
- if (securityContext != null)
- primaryIdentity = SecurityUtils.GetIdentityNamesFromContext(securityContext.AuthorizationContext);
- else
- primaryIdentity = SecurityUtils.AnonymousIdentity.Name;
- SecurityAuditHelper.WriteImpersonationFailureEvent(this.auditLogLocation,
- this.suppressAuditFailure, rpc.Operation.Name, primaryIdentity, ex);
- }
- #pragma warning suppress 56500
- catch (Exception auditException)
- {
- if (Fx.IsFatal(auditException))
- throw;
- DiagnosticUtility.TraceHandledException(auditException, TraceEventType.Error);
- }
- }
- throw;
- }
- return impersonationContext;
- }
- public void StopImpersonation(ref MessageRpc rpc, IDisposable impersonationContext, IPrincipal originalPrincipal, bool isThreadPrincipalSet)
- {
- try
- {
- if (IsSecurityContextImpersonationRequired(ref rpc) || AspNetEnvironment.Current.RequiresImpersonation)
- {
- if (impersonationContext != null)
- {
- impersonationContext.Dispose();
- }
- }
- if (isThreadPrincipalSet)
- {
- Thread.CurrentPrincipal = originalPrincipal;
- }
- }
- #pragma warning suppress 56500 // covered by FxCOP
- catch
- {
- string message = null;
- try
- {
- message = SR.GetString(SR.SFxRevertImpersonationFailed0);
- }
- finally
- {
- DiagnosticUtility.FailFast(message);
- }
- }
- }
- IPrincipal GetWindowsPrincipal(ServiceSecurityContext securityContext)
- {
- WindowsIdentity wid = securityContext.WindowsIdentity;
- if (!wid.IsAnonymous)
- return new WindowsPrincipal(wid);
- WindowsSidIdentity wsid = securityContext.PrimaryIdentity as WindowsSidIdentity;
- if (wsid != null)
- return new WindowsSidPrincipal(wsid, securityContext);
- return AnonymousWindowsPrincipal;
- }
- ServiceSecurityContext GetAndCacheSecurityContext(ref MessageRpc rpc)
- {
- ServiceSecurityContext securityContext = rpc.SecurityContext;
- if (!rpc.HasSecurityContext)
- {
- SecurityMessageProperty securityContextProperty = rpc.Request.Properties.Security;
- if (securityContextProperty == null)
- securityContext = null; // SecurityContext.Anonymous
- else
- {
- securityContext = securityContextProperty.ServiceSecurityContext;
- if (securityContext == null)
- throw TraceUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.SecurityContextMissing, rpc.Operation.Name)), rpc.Request);
- }
- rpc.SecurityContext = securityContext;
- rpc.HasSecurityContext = true;
- }
- return securityContext;
- }
- string GetUpnFromDownlevelName(string downlevelName)
- {
- if (downlevelName == null)
- {
- throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("downlevelName");
- }
- int delimiterPos = downlevelName.IndexOf('\\');
- if ((delimiterPos < 0) || (delimiterPos == 0) || (delimiterPos == downlevelName.Length - 1))
- {
- throw DiagnosticUtility.ExceptionUtility.ThrowHelperWarning(new InvalidOperationException(SR.GetString(SR.DownlevelNameCannotMapToUpn, downlevelName)));
- }
- string shortDomainName = downlevelName.Substring(0, delimiterPos + 1);
- string userName = downlevelName.Substring(delimiterPos + 1);
- string fullDomainName;
- bool found;
- // 1) Read from cache
- lock (this.domainNameMap)
- {
- found = this.domainNameMap.TryGetValue(shortDomainName, out fullDomainName);
- }
- // 2) Not found, do expensive look up
- if (!found)
- {
- uint capacity = 50;
- StringBuilder fullyQualifiedDomainName = new StringBuilder((int)capacity);
- if (!SafeNativeMethods.TranslateName(shortDomainName, EXTENDED_NAME_FORMAT.NameSamCompatible, EXTENDED_NAME_FORMAT.NameCanonical,
- fullyQualifiedDomainName, out capacity))
- {
- int errorCode = Marshal.GetLastWin32Error();
- if (errorCode == (int)Win32Error.ERROR_INSUFFICIENT_BUFFER)
- {
- fullyQualifiedDomainName = new StringBuilder((int)capacity);
- if (!SafeNativeMethods.TranslateName(shortDomainName, EXTENDED_NAME_FORMAT.NameSamCompatible, EXTENDED_NAME_FORMAT.NameCanonical,
- fullyQualifiedDomainName, out capacity))
- {
- errorCode = Marshal.GetLastWin32Error();
- throw DiagnosticUtility.ExceptionUtility.ThrowHelperWarning(new InvalidOperationException(SR.GetString(SR.DownlevelNameCannotMapToUpn, downlevelName), new Win32Exception(errorCode)));
- }
- }
- else
- {
- throw DiagnosticUtility.ExceptionUtility.ThrowHelperWarning(new InvalidOperationException(SR.GetString(SR.DownlevelNameCannotMapToUpn, downlevelName), new Win32Exception(errorCode)));
- }
- }
- // trim the trailing / from fqdn
- fullyQualifiedDomainName = fullyQualifiedDomainName.Remove(fullyQualifiedDomainName.Length - 1, 1);
- fullDomainName = fullyQualifiedDomainName.ToString();
- // 3) Save in cache (remove a random item if cache is full)
- lock (this.domainNameMap)
- {
- if (this.domainNameMap.Count >= maxDomainNameMapSize)
- {
- if (this.random == null)
- {
- this.random = new Random(unchecked((int)DateTime.Now.Ticks));
- }
- int victim = this.random.Next() % this.domainNameMap.Count;
- foreach (string key in this.domainNameMap.Keys)
- {
- if (victim <= 0)
- {
- this.domainNameMap.Remove(key);
- break;
- }
- --victim;
- }
- }
- this.domainNameMap[shortDomainName] = fullDomainName;
- }
- }
- return userName + "@" + fullDomainName;
- }
- class WindowsSidPrincipal : IPrincipal
- {
- WindowsSidIdentity identity;
- ServiceSecurityContext securityContext;
- public WindowsSidPrincipal(WindowsSidIdentity identity, ServiceSecurityContext securityContext)
- {
- this.identity = identity;
- this.securityContext = securityContext;
- }
- public IIdentity Identity
- {
- get { return this.identity; }
- }
- public bool IsInRole(string role)
- {
- if (role == null)
- throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("role");
- NTAccount account = new NTAccount(role);
- Claim claim = Claim.CreateWindowsSidClaim((SecurityIdentifier)account.Translate(typeof(SecurityIdentifier)));
- AuthorizationContext authContext = this.securityContext.AuthorizationContext;
- for (int i = 0; i < authContext.ClaimSets.Count; i++)
- {
- ClaimSet claimSet = authContext.ClaimSets[i];
- if (claimSet.ContainsClaim(claim))
- return true;
- }
- return false;
- }
- }
- class WindowsAnonymousIdentity
- {
- public IDisposable Impersonate()
- {
- // PreSharp Bug: Call 'Marshal.GetLastWin32Error' or 'Marshal.GetHRForLastWin32Error' before any other interop call.
- #pragma warning suppress 56523 // The LastWin32Error can be ignored here.
- IntPtr threadHandle = SafeNativeMethods.GetCurrentThread();
- SafeCloseHandle tokenHandle;
- if (!SafeNativeMethods.OpenCurrentThreadToken(threadHandle, TokenAccessLevels.Impersonate, true, out tokenHandle))
- {
- int error = Marshal.GetLastWin32Error();
- System.ServiceModel.Diagnostics.Utility.CloseInvalidOutSafeHandle(tokenHandle);
- if (error == (int)System.ServiceModel.ComIntegration.Win32Error.ERROR_NO_TOKEN)
- {
- tokenHandle = new SafeCloseHandle(IntPtr.Zero, false);
- }
- else
- {
- throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new Win32Exception(error));
- }
- }
- if (!SafeNativeMethods.ImpersonateAnonymousUserOnCurrentThread(threadHandle))
- {
- int error = Marshal.GetLastWin32Error();
- throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new Win32Exception(error));
- }
- return new ImpersonationContext(threadHandle, tokenHandle);
- }
- class ImpersonationContext : IDisposable
- {
- IntPtr threadHandle;
- SafeCloseHandle tokenHandle;
- bool disposed = false;
- public ImpersonationContext(IntPtr threadHandle, SafeCloseHandle tokenHandle)
- {
- this.threadHandle = threadHandle;
- this.tokenHandle = tokenHandle;
- }
- void Undo()
- {
- // PreSharp Bug: Call 'Marshal.GetLastWin32Error' or 'Marshal.GetHRForLastWin32Error' before any other interop call.
- #pragma warning suppress 56523 // The LastWin32Error can be ignored here.
- Fx.Assert(this.threadHandle == SafeNativeMethods.GetCurrentThread(), "");
- // We are in the Dispose method. If a failure occurs we just have to ignore it.
- // PreSharp Bug: Call 'Marshal.GetLastWin32Error' or 'Marshal.GetHRForLastWin32Error' before any other interop call.
- // #pragma warning suppress 56523 // The LastWin32Error can be ignored here.
- if (!SafeNativeMethods.SetCurrentThreadToken(IntPtr.Zero, this.tokenHandle))
- {
- int error = Marshal.GetLastWin32Error();
- throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityException(SR.GetString(SR.RevertImpersonationFailure,
- new Win32Exception(error).Message)));
- }
- tokenHandle.Close();
- }
- public void Dispose()
- {
- if (!this.disposed)
- {
- Undo();
- }
- this.disposed = true;
- }
- }
- }
- }
- }