Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Security.Claims;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Logging;

namespace Microsoft.AspNetCore.Components.Server;
Expand Down Expand Up @@ -61,7 +62,7 @@ private async Task RevalidationLoop(Task<AuthenticationState> authenticationStat
try
{
var authenticationState = await authenticationStateTask;
if (authenticationState.User.Identity?.IsAuthenticated == true)
if (SecurityHelper.IsAuthenticated(authenticationState.User))
{
while (!cancellationToken.IsCancellationRequested)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
<Compile Include="$(SharedSourceRoot)ValueStopwatch\*.cs" />
<Compile Include="$(SharedSourceRoot)LinkerFlags.cs" LinkBase="Shared" />
<Compile Include="$(SharedSourceRoot)\PooledArrayBufferWriter.cs" LinkBase="Shared" />
<Compile Include="$(SharedSourceRoot)SecurityHelper\**\*.cs" LinkBase="Shared" />

<!-- Add a project dependency without reference output assemblies to enforce build order -->
<!-- Applying workaround for https://github.com/microsoft/msbuild/issues/2661 and https://github.com/dotnet/sdk/issues/952 -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

<ItemGroup>
<Compile Include="$(RepoRoot)src\Shared\TaskToApm.cs" Link="Streams\TaskToApm.cs" />
<Compile Include="$(SharedSourceRoot)SecurityHelper\**\*.cs" LinkBase="Shared" />
</ItemGroup>

</Project>
5 changes: 3 additions & 2 deletions src/Middleware/OutputCaching/src/Policies/DefaultPolicy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Primitives;

namespace Microsoft.AspNetCore.OutputCaching;
Expand Down Expand Up @@ -50,7 +51,7 @@ ValueTask IOutputCachePolicy.ServeResponseAsync(OutputCacheContext context, Canc
return ValueTask.CompletedTask;
}

if (context.HttpContext.User?.Identity?.IsAuthenticated == true)
if (SecurityHelper.IsAuthenticated(context.HttpContext.User))
{
context.AllowCacheStorage = false;
return ValueTask.CompletedTask;
Expand Down Expand Up @@ -79,7 +80,7 @@ private static bool AttemptOutputCaching(OutputCacheContext context)
}

// Verify existence of authorization headers
if (!StringValues.IsNullOrEmpty(request.Headers.Authorization) || request.HttpContext.User?.Identity?.IsAuthenticated == true)
if (!StringValues.IsNullOrEmpty(request.Headers.Authorization) || SecurityHelper.IsAuthenticated(request.HttpContext.User))
{
return false;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Security.Claims;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging.Testing;
using Microsoft.Net.Http.Headers;
Expand Down Expand Up @@ -89,6 +90,63 @@ public async Task AttemptResponseCaching_AuthorizationHeaders_NotAllowed()
Assert.False(context.AllowCacheLookup);
}

[Fact]
public async Task AttemptOutputCaching_AuthenticatedUser_NotAllowed()
{
var sink = new TestSink();
var context = TestUtils.CreateTestContext(testSink: sink);
context.HttpContext.Request.Method = HttpMethods.Get;
context.HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity(authenticationType: "custom"));

var policy = new OutputCachePolicyBuilder().Build();

await policy.CacheRequestAsync(context, default);

Assert.False(context.AllowCacheStorage);
Assert.False(context.AllowCacheLookup);
}

[Fact]
public async Task AttemptOutputCaching_AuthenticatedByNonPrimaryIdentity_NotAllowed()
{
var sink = new TestSink();
var context = TestUtils.CreateTestContext(testSink: sink);
context.HttpContext.Request.Method = HttpMethods.Get;
context.HttpContext.User = new ClaimsPrincipal(new[]
{
new ClaimsIdentity(),
new ClaimsIdentity(authenticationType: "custom"),
});

Assert.False(context.HttpContext.User.Identity?.IsAuthenticated);

var policy = new OutputCachePolicyBuilder().Build();

await policy.CacheRequestAsync(context, default);

Assert.False(context.AllowCacheStorage);
Assert.False(context.AllowCacheLookup);
}

[Fact]
public async Task IsResponseCacheable_AuthenticatedByNonPrimaryIdentity_NotAllowed()
{
var sink = new TestSink();
var context = TestUtils.CreateTestContext(testSink: sink);
context.HttpContext.User = new ClaimsPrincipal(new[]
{
new ClaimsIdentity(),
new ClaimsIdentity(authenticationType: "custom"),
});

Assert.False(context.HttpContext.User.Identity?.IsAuthenticated);

var policy = new OutputCachePolicyBuilder().Build();
await policy.ServeResponseAsync(context, default);

Assert.False(context.AllowCacheStorage);
}

[Fact]
public async Task AllowCacheStorage_NoStore_IsAllowed()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Internal;

namespace Microsoft.AspNetCore.Authorization.Infrastructure;

Expand All @@ -21,11 +21,7 @@ public class DenyAnonymousAuthorizationRequirement : AuthorizationHandler<DenyAn
/// <param name="requirement">The requirement to evaluate.</param>
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DenyAnonymousAuthorizationRequirement requirement)
{
var user = context.User;
var userIsAnonymous =
user?.Identity == null ||
!user.Identities.Any(i => i.IsAuthenticated);
if (!userIsAnonymous)
if (SecurityHelper.IsAuthenticated(context.User))
{
context.Succeed(requirement);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Microsoft.AspNetCore.Authorization.AuthorizeAttribute</Description>
<Compile Include="$(SharedSourceRoot)ThrowHelpers\ArgumentNullThrowHelper.cs" LinkBase="Shared" />
<Compile Include="$(SharedSourceRoot)CallerArgument\CallerArgumentExpressionAttribute.cs" LinkBase="Shared" />
<Compile Include="$(SharedSourceRoot)Debugger\DebuggerHelpers.cs" LinkBase="Shared" />
<Compile Include="$(SharedSourceRoot)SecurityHelper\**\*.cs" LinkBase="Shared" />
</ItemGroup>

</Project>
11 changes: 11 additions & 0 deletions src/Shared/SecurityHelper/SecurityHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,15 @@ public static ClaimsPrincipal MergeUserPrincipal(ClaimsPrincipal? existingPrinci
}
return newPrincipal;
}

/// <summary>
/// Determines whether the <paramref name="user"/> is authenticated.
/// Uses the same aggregate semantics as authorization's
/// <c>DenyAnonymousAuthorizationRequirement</c>: the principal is authenticated when it has an
/// identity and at least one of its identities is authenticated (not only the primary identity).
/// </summary>
/// <param name="user">The <see cref="ClaimsPrincipal"/> to inspect.</param>
/// <returns><see langword="true"/> if the principal is authenticated; otherwise <see langword="false"/>.</returns>
public static bool IsAuthenticated(ClaimsPrincipal? user)
=> user?.Identity is not null && user.Identities.Any(static identity => identity.IsAuthenticated);
}
37 changes: 37 additions & 0 deletions src/Shared/test/Shared.Tests/SecurityHelperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,41 @@ public void AddingPreservesNewIdentitiesAndDropsEmpty()
Assert.Equal(identityNoAuthTypeWithClaim, user.Identities.Skip(2).First());
Assert.Equal(identityEmptyWithAuthType, user.Identities.Skip(3).First());
}

[Fact]
public void IsAuthenticated_NullUser_ReturnsFalse()
{
Assert.False(SecurityHelper.IsAuthenticated(null));
}

[Fact]
public void IsAuthenticated_NoIdentities_ReturnsFalse()
{
Assert.False(SecurityHelper.IsAuthenticated(new ClaimsPrincipal()));
}

[Fact]
public void IsAuthenticated_UnauthenticatedIdentity_ReturnsFalse()
{
Assert.False(SecurityHelper.IsAuthenticated(new ClaimsPrincipal(new ClaimsIdentity())));
}

[Fact]
public void IsAuthenticated_AuthenticatedIdentity_ReturnsTrue()
{
Assert.True(SecurityHelper.IsAuthenticated(new ClaimsPrincipal(new ClaimsIdentity(authenticationType: "custom"))));
}

[Fact]
public void IsAuthenticated_AuthenticatedByNonPrimaryIdentity_ReturnsTrue()
{
var user = new ClaimsPrincipal(new[]
{
new ClaimsIdentity(),
new ClaimsIdentity(authenticationType: "custom"),
});

Assert.False(user.Identity.IsAuthenticated);
Assert.True(SecurityHelper.IsAuthenticated(user));
}
}
Loading