dotnet / dotnet/aspnetcore

.IsInRole() exception - Active Directory user principal role checking broken during trusted domain interruptions

Open
#49,039 5 comments 0 reactions 0 assignees View on GitHub
area-identity enhancement
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 10h
Merged PRs (30d)
281

Description

### Is there an existing issue for this?

- [X] I have searched the existing issues

### Describe the bug

Manual use of .IsInRole() or invocation of a user principal role check through the use of Blazor's AuthorizeView component, will by default transverse all windows domain trusts when in an Active Directory context.

This creates a weak point in deployed applications within multi-domain environments, since interruption of contact with any trusted domain will cause exception status for all running instances within the primary domain.

This impact is not limited by:
- the domain of the user being evaluated
- the memberships of the user being evaluated (meaning that the user does not have to actually be a member of any cross-domain groups, merely that a domain trust has to exist).

### Expected Behavior

I expected to find either:
- a way to scope restrict (or otherwise configure) role checking to a specific domain
- some form of exception handling for Blazor's Authorize components

### Steps To Reproduce

True reproduction requires the configuration of an Active Directory domain trust, and then severing the access to the trusted domain.
(For anyone seeking to truly reproduce, the simplest method might be to intentionally create a DNS failure between 2 testing domains.)

Here is example code of .IsInRole() being called manually, illustrating fairly common use:

using (PrincipalContext pc = new(ContextType.Domain, ActiveDirectoryDomain))
{
using (UserPrincipal up = UserPrincipal.FindByIdentity(pc, user.Identity.Name))
{
appState.SetCurrentUser(user.Identity.Name, up.DisplayName);

foreach (var role in ADGroupRole)
{
if (userGroups.Any(x => x.Name.ToLower() == role.Key.ToLower()) == true && user.IsInRole(role.Value) == false)
{
var userClaims = new ClaimsIdentity(new List()
{
new Claim(ClaimTypes.Role, role.Value)
});
user.AddIdentity(userClaims);
}
}
...

Here is example code of Role checking being invoked through Blazor's AuthorizeView component:
```

...

```

Both result in identical exception.

### Exceptions (if any)

Win32Exception: The trust relationship between the primary domain and the trusted domain failed

( Exception details captured from Blazor application )

> System.ComponentModel.Win32Exception (1788): The trust relationship between the primary domain and the trusted domain failed.
> at System.Security.Principal.NTAccount.TranslateToSids(IdentityReferenceCollection sourceAccounts, Boolean& someFailed)
> at System.Security.Principal.NTAccount.Translate(IdentityReferenceCollection sourceAccounts, Type targetType, Boolean& someFailed)
> at System.Security.Principal.NTAccount.Translate(IdentityReferenceCollection sourceAccounts, Type targetType, Boolean forceSuccess)
> at System.Security.Principal.WindowsPrincipal.IsInRole(String role)
> at Microsoft.AspNetCore.Authorization.Infrastructure.RolesAuthorizationRequirement.<>c__DisplayClass4_0.b__0(String r)
> at System.Linq.Enumerable.Any[TSource](IEnumerable`1 source, Func`2 predicate)
> at Microsoft.AspNetCore.Authorization.Infrastructure.RolesAuthorizationRequirement.HandleRequirementAsync(AuthorizationHandlerContext context, RolesAuthorizationRequirement requirement)
> at Microsoft.AspNetCore.Authorization.AuthorizationHandler`1.HandleAsync(AuthorizationHandlerContext context)
> at Microsoft.AspNetCore.Authorization.Infrastructure.PassThroughAuthorizationHandler.HandleAsync(AuthorizationHandlerContext context)
> at Microsoft.AspNetCore.Authorization.DefaultAuthorizationService.AuthorizeAsync(ClaimsPrincipal user, Object resource, IEnumerable`1 requirements)
> at Microsoft.AspNetCore.Components.Authorization.AuthorizeViewCore.IsAuthorizedAsync(ClaimsPrincipal user)
> at Microsoft.AspNetCore.Components.Authorization.AuthorizeViewCore.OnParametersSetAsync()
> at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
> at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
> at Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.HandleException(Exception exception)
> at Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToPendingTasks(Task task, ComponentState owningComponentState)
> at Microsoft.AspNetCore.Components.Rendering.ComponentState.SupplyCombinedParameters(ParameterView directAndCascadingParameters)
> at Microsoft.AspNetCore.Components.Rendering.ComponentState.SetDirectParameters(ParameterView parameters)
> at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewComponentFrame(DiffContext& diffContext, Int32 frameIndex)
> at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewSubtree(DiffContext& diffContext, Int32 frameIndex)
> at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InsertNewFrame(DiffContext& diffContext, Int32 newFrameIndex)
> at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.AppendDiffEntriesForRange(DiffContext& diffContext, Int32 oldStartIndex, Int32 oldEndIndexExcl, Int32 newStartIndex, Int32 newEndIndexExcl)
> at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.ComputeDiff(Renderer renderer, RenderBatchBuilder batchBuilder, Int32 componentId, ArrayRange`1 oldTree, ArrayRange`1 newTree)
> at Microsoft.AspNetCore.Components.Rendering.ComponentState.RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment renderFragment, Exception& renderFragmentException)
> at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue()
> --- End of stack trace from previous location ---
> at Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.HandleException(Exception exception)
> at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue()
> at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessPendingRender()
> at Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToRenderQueue(Int32 componentId, RenderFragment renderFragment)
> at Microsoft.AspNetCore.Components.RenderHandle.Render(RenderFragment renderFragment)
> at Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged()
> at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
> at Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.HandleException(Exception exception)
> at Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToPendingTasks(Task task, ComponentState owningComponentState)
> at Microsoft.AspNetCore.Components.Rendering.ComponentState.SupplyCombinedParameters(ParameterView directAndCascadingParameters)
> at Microsoft.AspNetCore.Components.Rendering.ComponentState.SetDirectParameters(ParameterView parameters)
> at Microsoft.AspNetCore.Components.RenderTree.Renderer.RenderRootComponentAsync(Int32 componentId, ParameterView initialParameters)
> at Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.CreateInitialRenderAsync(Type componentType, ParameterView initialParameters)
> at Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.RenderComponentAsync(Type componentType, ParameterView initialParameters)
> at Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.<>c__11`1.<b__11_0>d.MoveNext()
> --- End of stack trace from previous location ---
> at Microsoft.AspNetCore.Mvc.ViewFeatures.StaticComponentRenderer.PrerenderComponentAsync(ParameterView parameters, HttpContext httpContext, Type componentType)
> at Microsoft.AspNetCore.Mvc.ViewFeatures.ComponentRenderer.PrerenderedServerComponentAsync(HttpContext context, ServerComponentInvocationSequence invocationId, Type type, ParameterView parametersCollection)
> at Microsoft.AspNetCore.Mvc.ViewFeatures.ComponentRenderer.RenderComponentAsync(ViewContext viewContext, Type componentType, RenderMode renderMode, Object parameters)
> at Microsoft.AspNetCore.Mvc.TagHelpers.ComponentTagHelper.ProcessAsync(TagHelperContext context, TagHelperOutput output)
> at Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner.g__Awaited|0_0(Task task, TagHelperExecutionContext executionContext, Int32 i, Int32 count)
> at MesIntegrationPortal.Pages.Pages__Host.b__24_1() in C:\Users\---\Pages\_Host.cshtml:line 25
> at Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext.SetOutputContentAsync()
> at MesIntegrationPortal.Pages.Pages__Host.ExecuteAsync() in C:\Users\----\Pages\_Host.cshtml:line 5
> at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context)
> at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, Boolean invokeViewStarts)
> at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context)
> at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, String contentType, Nullable`1 statusCode)
> at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, String contentType, Nullable`1 statusCode)
> at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|30_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
> at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
> at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
> at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
> --- End of stack trace from previous location ---
> at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
> at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
> at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
> at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
> --- End of stack trace from previous location ---
> at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
> at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
> at Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
> at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
>

### .NET Version

7.0.302

### Anything else?

It is possible to find multiple occurrences of this issue being discussed on sites like StackExchange, but most discussions are obscured by the fact that it is an environment specific risk (and frankly many developers don't have the Domain administration background to fully understand the context).

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.