Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(fake-auth): add callback event to fake auth handler #800

Merged
merged 12 commits into from
Oct 28, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using System.Security.Claims;
using IdentityModel;

Check failure on line 2 in src/Zitadel/Authentication/Events/Context/LocalFakeZitadelAuthContext.cs

View workflow job for this annotation

GitHub Actions / Test (.NET 6.x)

Check failure on line 2 in src/Zitadel/Authentication/Events/Context/LocalFakeZitadelAuthContext.cs

View workflow job for this annotation

GitHub Actions / Test (.NET 6.x)

Check failure on line 2 in src/Zitadel/Authentication/Events/Context/LocalFakeZitadelAuthContext.cs

View workflow job for this annotation

GitHub Actions / Test (.NET 6.x)

Check failure on line 2 in src/Zitadel/Authentication/Events/Context/LocalFakeZitadelAuthContext.cs

View workflow job for this annotation

GitHub Actions / Test (.NET 7.x)

Check failure on line 2 in src/Zitadel/Authentication/Events/Context/LocalFakeZitadelAuthContext.cs

View workflow job for this annotation

GitHub Actions / Test (.NET 7.x)

Check failure on line 2 in src/Zitadel/Authentication/Events/Context/LocalFakeZitadelAuthContext.cs

View workflow job for this annotation

GitHub Actions / Test (.NET 7.x)

Check failure on line 2 in src/Zitadel/Authentication/Events/Context/LocalFakeZitadelAuthContext.cs

View workflow job for this annotation

GitHub Actions / Test (.NET 8.x)

Check failure on line 2 in src/Zitadel/Authentication/Events/Context/LocalFakeZitadelAuthContext.cs

View workflow job for this annotation

GitHub Actions / Test (.NET 8.x)

Check failure on line 2 in src/Zitadel/Authentication/Events/Context/LocalFakeZitadelAuthContext.cs

View workflow job for this annotation

GitHub Actions / Test (.NET 8.x)


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

Check failure on line 16 in src/Zitadel/Authentication/Events/Context/LocalFakeZitadelAuthContext.cs

View workflow job for this annotation

GitHub Actions / Test (.NET 6.x)

Check failure on line 16 in src/Zitadel/Authentication/Events/Context/LocalFakeZitadelAuthContext.cs

View workflow job for this annotation

GitHub Actions / Test (.NET 6.x)

Check failure on line 16 in src/Zitadel/Authentication/Events/Context/LocalFakeZitadelAuthContext.cs

View workflow job for this annotation

GitHub Actions / Test (.NET 6.x)

Check failure on line 16 in src/Zitadel/Authentication/Events/Context/LocalFakeZitadelAuthContext.cs

View workflow job for this annotation

GitHub Actions / Test (.NET 7.x)

Check failure on line 16 in src/Zitadel/Authentication/Events/Context/LocalFakeZitadelAuthContext.cs

View workflow job for this annotation

GitHub Actions / Test (.NET 7.x)

Check failure on line 16 in src/Zitadel/Authentication/Events/Context/LocalFakeZitadelAuthContext.cs

View workflow job for this annotation

GitHub Actions / Test (.NET 7.x)

Check failure on line 16 in src/Zitadel/Authentication/Events/Context/LocalFakeZitadelAuthContext.cs

View workflow job for this annotation

GitHub Actions / Test (.NET 8.x)

Check failure on line 16 in src/Zitadel/Authentication/Events/Context/LocalFakeZitadelAuthContext.cs

View workflow job for this annotation

GitHub Actions / Test (.NET 8.x)

Check failure on line 16 in src/Zitadel/Authentication/Events/Context/LocalFakeZitadelAuthContext.cs

View workflow job for this annotation

GitHub Actions / Test (.NET 8.x)

/// <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)));
}
}
116 changes: 62 additions & 54 deletions src/Zitadel/Authentication/Options/LocalFakeZitadelOptions.cs
Original file line number Diff line number Diff line change
@@ -1,54 +1,62 @@
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;
}
}
}

Check failure on line 1 in src/Zitadel/Authentication/Options/LocalFakeZitadelOptions.cs

View workflow job for this annotation

GitHub Actions / Test (.NET 6.x)

Check failure on line 1 in src/Zitadel/Authentication/Options/LocalFakeZitadelOptions.cs

View workflow job for this annotation

GitHub Actions / Test (.NET 6.x)

Check failure on line 1 in src/Zitadel/Authentication/Options/LocalFakeZitadelOptions.cs

View workflow job for this annotation

GitHub Actions / Test (.NET 6.x)

Check failure on line 1 in src/Zitadel/Authentication/Options/LocalFakeZitadelOptions.cs

View workflow job for this annotation

GitHub Actions / Test (.NET 7.x)

Check failure on line 1 in src/Zitadel/Authentication/Options/LocalFakeZitadelOptions.cs

View workflow job for this annotation

GitHub Actions / Test (.NET 7.x)

Check failure on line 1 in src/Zitadel/Authentication/Options/LocalFakeZitadelOptions.cs

View workflow job for this annotation

GitHub Actions / Test (.NET 7.x)

Check failure on line 1 in src/Zitadel/Authentication/Options/LocalFakeZitadelOptions.cs

View workflow job for this annotation

GitHub Actions / Test (.NET 8.x)

Check failure on line 1 in src/Zitadel/Authentication/Options/LocalFakeZitadelOptions.cs

View workflow job for this annotation

GitHub Actions / Test (.NET 8.x)

Check failure on line 1 in src/Zitadel/Authentication/Options/LocalFakeZitadelOptions.cs

View workflow job for this annotation

GitHub Actions / Test (.NET 8.x)

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
Loading