Skip to content

Commit

Permalink
feat(fake-auth): add callback event to fake auth handler (#800)
Browse files Browse the repository at this point in the history
  • Loading branch information
ToWe0815 authored Oct 28, 2024
1 parent 906c1bb commit b9f626b
Show file tree
Hide file tree
Showing 6 changed files with 403 additions and 261 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
using System.Security.Claims;

namespace Zitadel.Authentication.Events.Context
{
public class LocalFakeZitadelAuthContext
{
/// <summary>
/// Constructor.
/// </summary>
/// <param name="identity">The created ClaimsIdentity.</param>
public LocalFakeZitadelAuthContext(ClaimsIdentity identity)
{
Identity = identity;
}

/// <summary>
/// The created ClaimsIdentity.
/// </summary>
public ClaimsIdentity Identity { get; init; }

/// <summary>
/// The claims of the created ClaimsIdentity.
/// </summary>
public IEnumerable<Claim> Claims => Identity.Claims;

/// <summary>
/// The "user-id" of the fake user.
/// Either set by the options or via HTTP header.
/// </summary>
public string FakeZitadelId => new ClaimsPrincipal(Identity).FindFirstValue("sub")!;

/// <summary>
/// Add a claim to the <see cref="Claims"/> list.
/// This is a convenience method for modifying <see cref="Claims"/>.
/// </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="LocalFakeZitadelAuthContext"/> for chaining.</returns>
public LocalFakeZitadelAuthContext AddClaim(
string type,
string value,
string? valueType = null,
string? issuer = null,
string? originalIssuer = null) => AddClaim(new(type, value, valueType, issuer, originalIssuer));

/// <summary>
/// Add a claim to the <see cref="Claims"/> list.
/// This is a convenience method for modifying <see cref="Claims"/>.
/// </summary>
/// <param name="claim">The claim to add.</param>
/// <returns>The <see cref="LocalFakeZitadelAuthContext"/> for chaining.</returns>
public LocalFakeZitadelAuthContext AddClaim(Claim claim)
{
Identity.AddClaim(claim);
return this;
}

/// <summary>
/// Add a single role to the identity's claims.
/// Note: the roles are actually "claims" but this method exists
/// for convenience.
/// </summary>
/// <param name="role">The role to add.</param>
/// <returns>The <see cref="LocalFakeZitadelAuthContext"/> for chaining.</returns>
public LocalFakeZitadelAuthContext AddRole(string role)
{
Identity.AddClaim(new(ClaimTypes.Role, role));
return this;
}

/// <summary>
/// Add multiple roles to the identity's claims.
/// Note: the roles are actually "claims" but this method exists
/// for convenience.
/// </summary>
/// <param name="roles">The roles to add.</param>
/// <returns>The <see cref="LocalFakeZitadelAuthContext"/> for chaining.</returns>
public LocalFakeZitadelAuthContext AddRoles(string[] roles)
{
foreach (var role in roles)
{
AddRole(role);
}

return this;
}
}
}
12 changes: 12 additions & 0 deletions src/Zitadel/Authentication/Events/LocalFakeZitadelEvents.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Zitadel.Authentication.Events.Context;
using Zitadel.Authentication.Handler;

namespace Zitadel.Authentication.Events;

public class LocalFakeZitadelEvents
{
/// <summary>
/// Invoked after a ClaimsIdentity has been generated in the <see cref="LocalFakeZitadelHandler"/>.
/// </summary>
public Func<LocalFakeZitadelAuthContext, Task> OnZitadelFakeAuth { get; set; } = context => Task.CompletedTask;
}
107 changes: 55 additions & 52 deletions src/Zitadel/Authentication/Handler/LocalFakeZitadelHandler.cs
Original file line number Diff line number Diff line change
@@ -1,52 +1,55 @@
using System.Security.Claims;
using System.Text.Encodings.Web;

using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

using Zitadel.Authentication.Options;

namespace Zitadel.Authentication.Handler;

#if NET8_0_OR_GREATER
internal class LocalFakeZitadelHandler(
IOptionsMonitor<LocalFakeZitadelSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder)
: AuthenticationHandler<LocalFakeZitadelSchemeOptions>(options, logger, encoder)
#else
internal class LocalFakeZitadelHandler(
IOptionsMonitor<LocalFakeZitadelSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock)
: AuthenticationHandler<LocalFakeZitadelSchemeOptions>(options, logger, encoder, clock)
#endif
{
private const string FakeAuthHeader = "x-zitadel-fake-auth";
private const string FakeUserIdHeader = "x-zitadel-fake-user-id";

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 hasId = Context.Request.Headers.TryGetValue(FakeUserIdHeader, out var forceUserId);

var claims = new List<Claim>
{
new(ClaimTypes.NameIdentifier, hasId ? forceUserId.ToString() : Options.FakeZitadelOptions.FakeZitadelId),
new("sub", hasId ? forceUserId.ToString() : 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(new(identity), ZitadelDefaults.FakeAuthenticationScheme)));
}
}
using System.Security.Claims;
using System.Text.Encodings.Web;

using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

using Zitadel.Authentication.Options;

namespace Zitadel.Authentication.Handler;

#if NET8_0_OR_GREATER
internal class LocalFakeZitadelHandler(
IOptionsMonitor<LocalFakeZitadelSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder)
: AuthenticationHandler<LocalFakeZitadelSchemeOptions>(options, logger, encoder)
#else
internal class LocalFakeZitadelHandler(
IOptionsMonitor<LocalFakeZitadelSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock)
: AuthenticationHandler<LocalFakeZitadelSchemeOptions>(options, logger, encoder, clock)
#endif
{
private const string FakeAuthHeader = "x-zitadel-fake-auth";
private const string FakeUserIdHeader = "x-zitadel-fake-user-id";

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 hasId = Context.Request.Headers.TryGetValue(FakeUserIdHeader, out var forceUserId);

var claims = new List<Claim>
{
new(ClaimTypes.NameIdentifier, hasId ? forceUserId.ToString() : Options.FakeZitadelOptions.FakeZitadelId),
new("sub", hasId ? forceUserId.ToString() : 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);

// Callback to enable users to manipulate the ClaimsIdentity before it is used.
Options.FakeZitadelOptions.Events.OnZitadelFakeAuth.Invoke(new(identity));

return Task.FromResult(
AuthenticateResult.Success(
new(new(identity), ZitadelDefaults.FakeAuthenticationScheme)));
}
}
115 changes: 61 additions & 54 deletions src/Zitadel/Authentication/Options/LocalFakeZitadelOptions.cs
Original file line number Diff line number Diff line change
@@ -1,54 +1,61 @@
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 = null,
string? issuer = null,
string? originalIssuer = null) => 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;
}
}
}
using System.Security.Claims;

using Zitadel.Authentication.Events;

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>
/// Gets or sets the <see cref="LocalFakeZitadelEvents"/> used to enable mocking authentication data dynamically.
/// </summary>
public LocalFakeZitadelEvents Events { get; set; } = new LocalFakeZitadelEvents();

/// <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 = null,
string? issuer = null,
string? originalIssuer = null) => 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;
}
}
}
Loading

0 comments on commit b9f626b

Please sign in to comment.