-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(auth): Add local fake authentication for testing. (#10)
This closes #9. This adds an `AddFakeZitadel` method extension to the authentication builder. With this local fake, one can bypass authentication for development. To fail a specific request, add the "x-zitadel-fake-auth" header with the value "false" to the request. Signed-off-by: Christoph Bühler <christoph@smartive.ch>
- Loading branch information
Showing
8 changed files
with
320 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
47 changes: 47 additions & 0 deletions
47
src/Zitadel/Authentication/Handler/LocalFakeZitadelHandler.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Security.Claims; | ||
using System.Text.Encodings.Web; | ||
using System.Threading.Tasks; | ||
using Microsoft.AspNetCore.Authentication; | ||
using Microsoft.Extensions.Logging; | ||
using Microsoft.Extensions.Options; | ||
using Zitadel.Authentication.Options; | ||
|
||
namespace Zitadel.Authentication.Handler | ||
{ | ||
internal class LocalFakeZitadelHandler : AuthenticationHandler<LocalFakeZitadelSchemeOptions> | ||
{ | ||
private const string FakeAuthHeader = "x-zitadel-fake-auth"; | ||
|
||
public LocalFakeZitadelHandler( | ||
IOptionsMonitor<LocalFakeZitadelSchemeOptions> options, | ||
ILoggerFactory logger, | ||
UrlEncoder encoder, | ||
ISystemClock clock) | ||
: base(options, logger, encoder, clock) | ||
{ | ||
} | ||
|
||
protected override Task<AuthenticateResult> HandleAuthenticateAsync() | ||
{ | ||
if (Context.Request.Headers.TryGetValue(FakeAuthHeader, out var value) && value == "false") | ||
{ | ||
return Task.FromResult(AuthenticateResult.Fail($@"The {FakeAuthHeader} was set with value ""false"".")); | ||
} | ||
|
||
var claims = new List<Claim> | ||
{ | ||
new(ClaimTypes.NameIdentifier, Options.FakeZitadelOptions.FakeZitadelId), | ||
new("sub", Options.FakeZitadelOptions.FakeZitadelId), | ||
}.Concat(Options.FakeZitadelOptions.AdditionalClaims) | ||
.Concat(Options.FakeZitadelOptions.Roles.Select(r => new Claim(ClaimTypes.Role, r))); | ||
|
||
var identity = new ClaimsIdentity(claims, ZitadelDefaults.FakeAuthenticationScheme); | ||
|
||
return Task.FromResult( | ||
AuthenticateResult.Success( | ||
new AuthenticationTicket(new ClaimsPrincipal(identity), ZitadelDefaults.FakeAuthenticationScheme))); | ||
} | ||
} | ||
} |
55 changes: 55 additions & 0 deletions
55
src/Zitadel/Authentication/Options/LocalFakeZitadelOptions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
using System.Collections.Generic; | ||
using System.Security.Claims; | ||
|
||
namespace Zitadel.Authentication.Options | ||
{ | ||
public class LocalFakeZitadelOptions | ||
{ | ||
/// <summary> | ||
/// The "user-id" of the fake user. | ||
/// This populates the "sub" and "nameidentifier" claims. | ||
/// </summary> | ||
public string FakeZitadelId { get; set; } = string.Empty; | ||
|
||
/// <summary> | ||
/// A list of additional claims to add to the identity. | ||
/// </summary> | ||
public IList<Claim> AdditionalClaims { get; set; } = new List<Claim>(); | ||
|
||
/// <summary> | ||
/// List of roles that are attached to the identity. | ||
/// Note: the roles are actually "claims" but this list exists | ||
/// for convenience. | ||
/// </summary> | ||
public IEnumerable<string> Roles { get; set; } = new List<string>(); | ||
|
||
/// <summary> | ||
/// Add a claim to the <see cref="AdditionalClaims"/> list. | ||
/// This is a convenience method for modifying <see cref="AdditionalClaims"/>. | ||
/// </summary> | ||
/// <param name="type">Type of the claim (examples: <see cref="ClaimTypes"/>).</param> | ||
/// <param name="value">The value.</param> | ||
/// <param name="valueType">Type of the value (examples: <see cref="ClaimValueTypes"/>).</param> | ||
/// <param name="issuer">The issuer for this claim.</param> | ||
/// <param name="originalIssuer">The original issuer of this claim.</param> | ||
/// <returns>The <see cref="LocalFakeZitadelOptions"/> for chaining.</returns> | ||
public LocalFakeZitadelOptions AddClaim( | ||
string type, | ||
string value, | ||
string? valueType, | ||
string? issuer, | ||
string? originalIssuer) => AddClaim(new(type, value, valueType, issuer, originalIssuer)); | ||
|
||
/// <summary> | ||
/// Add a claim to the <see cref="AdditionalClaims"/> list. | ||
/// This is a convenience method for modifying <see cref="AdditionalClaims"/>. | ||
/// </summary> | ||
/// <param name="claim">The claim to add.</param> | ||
/// <returns>The <see cref="LocalFakeZitadelOptions"/> for chaining.</returns> | ||
public LocalFakeZitadelOptions AddClaim(Claim claim) | ||
{ | ||
AdditionalClaims.Add(claim); | ||
return this; | ||
} | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
src/Zitadel/Authentication/Options/LocalFakeZitadelSchemeOptions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
using Microsoft.AspNetCore.Authentication; | ||
|
||
namespace Zitadel.Authentication.Options | ||
{ | ||
internal class LocalFakeZitadelSchemeOptions : AuthenticationSchemeOptions | ||
{ | ||
public LocalFakeZitadelOptions FakeZitadelOptions { get; set; } = new(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
55 changes: 55 additions & 0 deletions
55
tests/Zitadel.Test/Authentication/ZitadelFakeAuthenticationHandler.Test.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
using System.Net.Http; | ||
using System.Net.Http.Json; | ||
using System.Security.Claims; | ||
using System.Threading.Tasks; | ||
using FluentAssertions; | ||
using Microsoft.AspNetCore.Http; | ||
using Xunit; | ||
using Zitadel.Test.WebFactories; | ||
|
||
namespace Zitadel.Test.Authentication | ||
{ | ||
public class ZitadelFakeAuthenticationHandler : IClassFixture<FakeAuthenticationHandlerWebFactory> | ||
{ | ||
private readonly FakeAuthenticationHandlerWebFactory _factory; | ||
|
||
public ZitadelFakeAuthenticationHandler(FakeAuthenticationHandlerWebFactory factory) | ||
{ | ||
_factory = factory; | ||
} | ||
|
||
[Fact] | ||
public async Task Should_Be_Able_To_Call_Unauthorized_Endpoint() | ||
{ | ||
var client = _factory.CreateClient(); | ||
var result = | ||
await client.GetFromJsonAsync("/unauthed", typeof(AuthenticationHandlerWebFactory.Unauthed)) as | ||
AuthenticationHandlerWebFactory.Unauthed; | ||
result.Should().NotBeNull(); | ||
result?.Ping.Should().Be("Pong"); | ||
} | ||
|
||
[Fact] | ||
public async Task Should_Return_Unauthorized_With_The_Fail_Header() | ||
{ | ||
var client = _factory.CreateClient(); | ||
var request = new HttpRequestMessage(HttpMethod.Get, "/authed") | ||
{ | ||
Headers = { { "x-zitadel-fake-auth", "false" } }, | ||
}; | ||
var result = await client.SendAsync(request); | ||
result.StatusCode.Should().Be(StatusCodes.Status401Unauthorized); | ||
} | ||
|
||
[Fact] | ||
public async Task Should_Return_Authorized() | ||
{ | ||
var client = _factory.CreateClient(); | ||
var result = await client.GetFromJsonAsync("/authed", typeof(AuthenticationHandlerWebFactory.Authed)) as | ||
AuthenticationHandlerWebFactory.Authed; | ||
result?.AuthType.Should().Be("ZitadelLocalFake"); | ||
result?.UserId.Should().Be("1234"); | ||
result?.Claims.Should().Contain(claim => claim.Key == ClaimTypes.Role && claim.Value == "User"); | ||
} | ||
} | ||
} |
107 changes: 107 additions & 0 deletions
107
tests/Zitadel.Test/WebFactories/FakeAuthenticationHandlerWebFactory.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Security.Claims; | ||
using Microsoft.AspNetCore.Builder; | ||
using Microsoft.AspNetCore.Hosting; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Mvc.Testing; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Hosting; | ||
using Zitadel.Authentication; | ||
|
||
namespace Zitadel.Test.WebFactories | ||
{ | ||
public class FakeAuthenticationHandlerWebFactory : WebApplicationFactory<FakeAuthenticationHandlerWebFactory> | ||
{ | ||
#region Startup | ||
|
||
public void ConfigureServices(IServiceCollection services) | ||
{ | ||
services | ||
.AddAuthorization() | ||
.AddAuthentication(ZitadelDefaults.FakeAuthenticationScheme) | ||
.AddFakeZitadel( | ||
options => | ||
{ | ||
options.FakeZitadelId = "1234"; | ||
options.AdditionalClaims = new List<Claim> | ||
{ | ||
new("foo", "bar"), | ||
}; | ||
options.Roles = new List<string> { "User" }; | ||
}); | ||
} | ||
|
||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) | ||
{ | ||
app.UseRouting(); | ||
|
||
app.UseAuthentication(); | ||
app.UseAuthorization(); | ||
|
||
app.UseEndpoints( | ||
endpoints => | ||
{ | ||
endpoints.MapGet( | ||
"/unauthed", | ||
async context => { await context.Response.WriteAsJsonAsync(new Unauthed { Ping = "Pong" }); }); | ||
endpoints.MapGet( | ||
"/authed", | ||
async context => | ||
{ | ||
await context.Response.WriteAsJsonAsync( | ||
new Authed | ||
{ | ||
Ping = "Pong", | ||
AuthType = context.User.Identity?.AuthenticationType, | ||
UserId = context.User.FindFirstValue(ClaimTypes.NameIdentifier), | ||
Claims = context.User.Claims.Select( | ||
c => new KeyValuePair<string, string>(c.Type, c.Value)) | ||
.ToList(), | ||
}); | ||
}) | ||
.RequireAuthorization(); | ||
}); | ||
} | ||
|
||
#endregion | ||
|
||
#region WebApplicationFactory | ||
|
||
protected override IHostBuilder CreateHostBuilder() | ||
=> Host | ||
.CreateDefaultBuilder() | ||
.ConfigureWebHostDefaults( | ||
builder => builder | ||
.UseStartup<FakeAuthenticationHandlerWebFactory>()); | ||
|
||
protected override IHost CreateHost(IHostBuilder builder) | ||
{ | ||
builder.UseContentRoot(Directory.GetCurrentDirectory()); | ||
return base.CreateHost(builder); | ||
} | ||
|
||
#endregion | ||
|
||
#region Result Classes | ||
|
||
internal record Unauthed | ||
{ | ||
public string Ping { get; init; } | ||
} | ||
|
||
internal record Authed | ||
{ | ||
public string Ping { get; init; } | ||
|
||
public string AuthType { get; init; } | ||
|
||
public string UserId { get; init; } | ||
|
||
public List<KeyValuePair<string, string>> Claims { get; init; } | ||
} | ||
|
||
#endregion | ||
} | ||
} |