Skip to content

Commit

Permalink
Add config flag to ignore authenticated status (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
jorros authored Jul 18, 2022
1 parent b128961 commit fcc4985
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 19 deletions.
1 change: 1 addition & 0 deletions src/AnonymousUser/AnonymousUser.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<RootNamespace>InsightArchitectures.Extensions.AspNetCore.AnonymousUser</RootNamespace>
</PropertyGroup>

<ItemGroup>
Expand Down
34 changes: 22 additions & 12 deletions src/AnonymousUser/AnonymousUserMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ namespace InsightArchitectures.Extensions.AspNetCore.AnonymousUser
/// </summary>
public class AnonymousUserMiddleware
{
private RequestDelegate _nextDelegate;
private AnonymousUserOptions _options;
private readonly RequestDelegate _nextDelegate;
private readonly AnonymousUserOptions _options;

/// <summary>
/// Constructor requires the next delegate and options.
Expand All @@ -25,10 +25,14 @@ public AnonymousUserMiddleware(RequestDelegate nextDelegate, AnonymousUserOption

private async Task HandleRequestAsync(HttpContext httpContext)
{
var cookieEncoder = _options.EncoderService ?? throw new ArgumentNullException(nameof(_options.EncoderService), $"{nameof(_options.EncoderService)} is null and should have a valid encoder.");
_ = _options.UserIdentifierFactory ?? throw new ArgumentNullException(nameof(_options.UserIdentifierFactory), $"{nameof(_options.UserIdentifierFactory)} is null and should have a valid factory.");
var cookieEncoder = _options.EncoderService ?? throw new ArgumentNullException(
nameof(_options.EncoderService),
$"{nameof(_options.EncoderService)} is null and should have a valid encoder.");
_ = _options.UserIdentifierFactory ?? throw new ArgumentNullException(
nameof(_options.UserIdentifierFactory),
$"{nameof(_options.UserIdentifierFactory)} is null and should have a valid factory.");

if (httpContext.User.Identity?.IsAuthenticated == true)
if (_options.SkipAuthenticated && httpContext.User.Identity?.IsAuthenticated == true)
{
return;
}
Expand All @@ -52,16 +56,22 @@ private async Task HandleRequestAsync(HttpContext httpContext)
uid = _options.UserIdentifierFactory.Invoke(httpContext);
var encodedUid = await cookieEncoder.EncodeAsync(uid);

var cookieOptions = new CookieOptions
{
Expires = _options.Expires,
};
var cookieOptions = new CookieOptions { Expires = _options.Expires };

httpContext.Response.Cookies.Append(_options.CookieName, encodedUid, cookieOptions);
}

var identity = new ClaimsIdentity(new[] { new Claim(_options.ClaimType, uid) });
httpContext.User.AddIdentity(identity);
var claim = new Claim(_options.ClaimType, uid);

if (httpContext.User.Identity is ClaimsIdentity ci)
{
ci.AddClaim(claim);
}
else
{
var identity = new ClaimsIdentity(new[] { claim });
httpContext.User.AddIdentity(identity);
}
}

/// <summary>
Expand All @@ -75,4 +85,4 @@ public async Task InvokeAsync(HttpContext httpContext)
await _nextDelegate.Invoke(httpContext);
}
}
}
}
5 changes: 4 additions & 1 deletion src/AnonymousUser/AnonymousUserOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@ public class AnonymousUserOptions
/// <summary>Should the cookie only be allowed on https requests.</summary>
public bool Secure { get; set; }

/// <summary>Should the anonymous session id be skipped when an user is authenticated.</summary>
public bool SkipAuthenticated { get; set; }

/// <summary>Can be overridden to customise the ID generation.</summary>
public Func<HttpContext, string> UserIdentifierFactory { get; set; } = _ => Guid.NewGuid().ToString();

/// <summary>The encoder service to encode/decode the cookie value. Default set to internal base64 encoder.</summary>
public ICookieEncoder EncoderService { get; set; } = new Base64CookieEncoder();
}
}
}
19 changes: 13 additions & 6 deletions tests/AnonymousUserTests/AnonymousUserMiddlewareTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace AnonymousUserTests
{
public class AnonymousUserMiddlewareTests
{
[Test, CustomAutoDataAttribute]
[Test, CustomAutoData]
public async Task NoCookiesShouldCreateCookie(HttpContext context, [Frozen] AnonymousUserOptions options, AnonymousUserMiddleware sut)
{
await sut.InvokeAsync(context);
Expand All @@ -22,7 +22,7 @@ public async Task NoCookiesShouldCreateCookie(HttpContext context, [Frozen] Anon
Assert.IsFalse(string.IsNullOrWhiteSpace(actual));
}

[Test, CustomAutoDataAttribute]
[Test, CustomAutoData]
public async Task ExistingCookieShouldNotAddCookieToResponse(HttpContext context, [Frozen] Mock<HttpRequest> httpRequest, [Frozen] AnonymousUserOptions options, AnonymousUserMiddleware sut)
{
var cookies = new Dictionary<string, string>
Expand All @@ -38,7 +38,7 @@ public async Task ExistingCookieShouldNotAddCookieToResponse(HttpContext context
Assert.IsTrue(string.IsNullOrWhiteSpace(actual));
}

[Test, CustomAutoDataAttribute]
[Test, CustomAutoData]
public async Task SecureCookieWithHttpShouldExpire(HttpContext context, [Frozen] Mock<HttpRequest> httpRequest, [Frozen] AnonymousUserOptions options, AnonymousUserMiddleware sut)
{
var cookies = new Dictionary<string, string>
Expand All @@ -56,15 +56,22 @@ public async Task SecureCookieWithHttpShouldExpire(HttpContext context, [Frozen]
Assert.IsEmpty(actual);
}

[Test, CustomAutoDataAttribute]
[Test, CustomAutoData]
public async Task AuthenticatedUserShouldSkipMiddleware(HttpContext context, [Frozen] Mock<ClaimsPrincipal> claimsPrincipal, AnonymousUserMiddleware sut)
{
claimsPrincipal.Setup(x => x.Identity).Returns(new ClaimsIdentity(null, "Test"));
var identityMock = new Mock<ClaimsIdentity>(() => new ClaimsIdentity(null, "Test"));
claimsPrincipal.Setup(x => x.Identity).Returns(identityMock.Object);
identityMock.Setup(x => x.IsAuthenticated).Returns(true);

claimsPrincipal.Setup(x => x.AddIdentity(It.IsAny<ClaimsIdentity>())).Verifiable();
identityMock.Setup(x => x.AddClaim(It.IsAny<Claim>())).Verifiable();

context.User = claimsPrincipal.Object;

await sut.InvokeAsync(context);

claimsPrincipal.Verify(x => x.AddIdentity(It.IsAny<ClaimsIdentity>()), Times.Never);
identityMock.Verify(x => x.AddClaim(It.IsAny<Claim>()), Times.Never);
}

private string? GetCookieValueFromResponse(HttpResponse response, string cookieName)
Expand All @@ -87,4 +94,4 @@ public async Task AuthenticatedUserShouldSkipMiddleware(HttpContext context, [Fr
return null;
}
}
}
}

0 comments on commit fcc4985

Please sign in to comment.