< Summary - Kestrun — Combined Coverage

Information
Class: Kestrun.Authentication.IAuthHandler
Assembly: Kestrun
File(s): /home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Authentication/IAuthHandler.cs
Tag: Kestrun/Kestrun@0d738bf294e6281b936d031e1979d928007495ff
Line coverage
88%
Covered lines: 220
Uncovered lines: 29
Coverable lines: 249
Total lines: 586
Line coverage: 88.3%
Branch coverage
74%
Covered branches: 76
Total branches: 102
Branch coverage: 74.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 08/26/2025 - 14:53:17 Line coverage: 87.5% (183/209) Branch coverage: 71.1% (64/90) Total lines: 478 Tag: Kestrun/Kestrun@78d1e497d8ba989d121b57aa39aa3c6b22de743109/06/2025 - 18:30:33 Line coverage: 90.4% (189/209) Branch coverage: 75.5% (68/90) Total lines: 478 Tag: Kestrun/Kestrun@aeddbedb8a96e9137aac94c2d5edd011b57ac87109/12/2025 - 03:43:11 Line coverage: 90.4% (189/209) Branch coverage: 75.5% (68/90) Total lines: 477 Tag: Kestrun/Kestrun@d160286e3020330b1eb862d66a37db2e26fc904210/13/2025 - 16:52:37 Line coverage: 89.4% (196/219) Branch coverage: 73.9% (71/96) Total lines: 499 Tag: Kestrun/Kestrun@10d476bee71c71ad215bb8ab59f219887b5b4a5e11/14/2025 - 12:29:34 Line coverage: 89.7% (201/224) Branch coverage: 73.9% (71/96) Total lines: 503 Tag: Kestrun/Kestrun@5e12b09a6838e68e704cd3dc975331b9e680a62612/18/2025 - 21:41:58 Line coverage: 88.3% (220/249) Branch coverage: 74.5% (76/102) Total lines: 586 Tag: Kestrun/Kestrun@0d738bf294e6281b936d031e1979d928007495ff 08/26/2025 - 14:53:17 Line coverage: 87.5% (183/209) Branch coverage: 71.1% (64/90) Total lines: 478 Tag: Kestrun/Kestrun@78d1e497d8ba989d121b57aa39aa3c6b22de743109/06/2025 - 18:30:33 Line coverage: 90.4% (189/209) Branch coverage: 75.5% (68/90) Total lines: 478 Tag: Kestrun/Kestrun@aeddbedb8a96e9137aac94c2d5edd011b57ac87109/12/2025 - 03:43:11 Line coverage: 90.4% (189/209) Branch coverage: 75.5% (68/90) Total lines: 477 Tag: Kestrun/Kestrun@d160286e3020330b1eb862d66a37db2e26fc904210/13/2025 - 16:52:37 Line coverage: 89.4% (196/219) Branch coverage: 73.9% (71/96) Total lines: 499 Tag: Kestrun/Kestrun@10d476bee71c71ad215bb8ab59f219887b5b4a5e11/14/2025 - 12:29:34 Line coverage: 89.7% (201/224) Branch coverage: 73.9% (71/96) Total lines: 503 Tag: Kestrun/Kestrun@5e12b09a6838e68e704cd3dc975331b9e680a62612/18/2025 - 21:41:58 Line coverage: 88.3% (220/249) Branch coverage: 74.5% (76/102) Total lines: 586 Tag: Kestrun/Kestrun@0d738bf294e6281b936d031e1979d928007495ff

Metrics

File(s)

/home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Authentication/IAuthHandler.cs

#LineLine coverage
 1
 2
 3
 4using System.Collections;
 5using System.Management.Automation;
 6using System.Security.Claims;
 7using Kestrun.Hosting;
 8using Kestrun.Languages;
 9using Kestrun.Logging;
 10using Kestrun.Models;
 11using Kestrun.Utilities;
 12using Microsoft.AspNetCore.Authentication;
 13
 14namespace Kestrun.Authentication;
 15
 16/// <summary>
 17/// Defines common options for authentication, including code validation, claim issuance, and claim policy configuration
 18/// </summary>
 19public interface IAuthHandler
 20{
 21    /// <summary>
 22    /// Generates an <see cref="AuthenticationTicket"/> for the specified user and authentication scheme, issuing additi
 23    /// </summary>
 24    /// <param name="Context">The current HTTP context.</param>
 25    /// <param name="user">The user name for whom the ticket is being generated.</param>
 26    /// <param name="Options">Authentication options including claim issuance delegates.</param>
 27    /// <param name="Scheme">The authentication scheme to use.</param>
 28    /// <param name="alias">An optional alias for the user.</param>
 29    /// <returns>An <see cref="AuthenticationTicket"/> representing the authenticated user.</returns>
 30    static async Task<AuthenticationTicket> GetAuthenticationTicketAsync(
 31        HttpContext Context, string user,
 32    IAuthenticationCommonOptions Options, AuthenticationScheme Scheme, string? alias = null)
 33    {
 834        var claims = new List<Claim>();
 35
 36        // 1) Issue extra claims if configured
 837        claims.AddRange(await GetIssuedClaimsAsync(Context, user, Options).ConfigureAwait(false));
 38
 39        // 2) Ensure a Name claim exists
 840        EnsureNameClaim(claims, user, alias, Options.Logger);
 41
 42        // 3) Create and return the ticket
 843        return CreateAuthenticationTicket(claims, Scheme);
 844    }
 45
 46    /// <summary>
 47    /// Issues claims for the specified user based on the provided context and options.
 48    /// </summary>
 49    /// <param name="context">The HTTP context.</param>
 50    /// <param name="user">The user name for whom claims are being issued.</param>
 51    /// <param name="options">Authentication options including claim issuance delegates.</param>
 52    /// <returns>A collection of issued claims.</returns>
 53    private static async Task<IEnumerable<Claim>> GetIssuedClaimsAsync(HttpContext context, string user, IAuthentication
 54    {
 855        if (options.IssueClaims is null)
 56        {
 857            return [];
 58        }
 59
 060        var extra = await options.IssueClaims(context, user).ConfigureAwait(false);
 061        if (extra is null)
 62        {
 063            return [];
 64        }
 65
 66        // Filter out nulls and empty values
 067        return [.. extra
 068            .Where(c => c is not null)
 069            .OfType<Claim>()
 070            .Where(c => !string.IsNullOrEmpty(c.Value))];
 871    }
 72
 73    /// <summary>
 74    /// Ensures that a Name claim is present in the list of claims.
 75    /// </summary>
 76    /// <param name="claims">The list of claims to check.</param>
 77    /// <param name="user">The user name to use if a Name claim is added.</param>
 78    /// <param name="alias">An optional alias for the user.</param>
 79    /// <param name="logger">The logger instance.</param>
 80    private static void EnsureNameClaim(List<Claim> claims, string user, string? alias, Serilog.ILogger logger)
 81    {
 882        if (claims.Any(c => c.Type == ClaimTypes.Name))
 83        {
 084            return;
 85        }
 86
 887        if (logger.IsEnabled(Serilog.Events.LogEventLevel.Debug))
 88        {
 289            logger.Debug("No Name claim found, adding default Name claim");
 90        }
 91
 892        var name = string.IsNullOrEmpty(alias) ? user : alias;
 893        claims.Add(new Claim(ClaimTypes.Name, name));
 894    }
 95
 96    /// <summary>
 97    /// Creates an authentication ticket from the specified claims and authentication scheme.
 98    /// </summary>
 99    /// <param name="claims">The claims to include in the ticket.</param>
 100    /// <param name="scheme">The authentication scheme to use.</param>
 101    /// <returns>An authentication ticket containing the specified claims.</returns>
 102    private static AuthenticationTicket CreateAuthenticationTicket(IEnumerable<Claim> claims, AuthenticationScheme schem
 103    {
 8104        var claimsIdentity = new ClaimsIdentity(claims, scheme.Name);
 8105        var principal = new ClaimsPrincipal(claimsIdentity);
 8106        return new AuthenticationTicket(principal, scheme.Name);
 107    }
 108
 109    /// <summary>
 110    /// Authenticates the user using PowerShell script.
 111    /// This method is used to validate the username and password against a PowerShell script.
 112    /// </summary>
 113    /// <param name="code">The PowerShell script code used for authentication.</param>
 114    /// <param name="context">The HTTP context.</param>
 115    /// <param name="credentials">A dictionary containing the credentials to validate (e.g., username and password).</pa
 116    /// <param name="logger">The logger instance.</param>
 117    /// <returns>A task representing the asynchronous operation.</returns>
 118    /// <exception cref="InvalidOperationException"></exception>
 119    static async ValueTask<bool> ValidatePowerShellAsync(string? code, HttpContext context, Dictionary<string, string> c
 120    {
 121        try
 122        {
 9123            ValidatePowerShellInputs(code, context, credentials, logger);
 6124            var ps = GetPowerShellForValidation(context);
 125
 6126            _ = ps.AddScript(code, useLocalScope: true);
 36127            foreach (var kvp in credentials)
 128            {
 12129                _ = ps.AddParameter(kvp.Key, kvp.Value);
 130            }
 131
 6132            var psResults = await ExecutePowerShellScriptAsync(ps, context, logger).ConfigureAwait(false);
 133
 6134            return ValidatePowerShellResult(psResults, logger);
 135        }
 3136        catch (Exception ex)
 137        {
 3138            logger.Error(ex, "Error during validating PowerShell authentication.");
 3139            return false;
 140        }
 9141    }
 142
 143    /// <summary>
 144    /// Validates that all required inputs for PowerShell authentication are present and valid.
 145    /// </summary>
 146    /// <param name="code">The PowerShell script code to validate.</param>
 147    /// <param name="context">The HTTP context containing the PowerShell runspace.</param>
 148    /// <param name="credentials">The credentials dictionary to validate.</param>
 149    /// <param name="logger">The logger instance for logging warnings.</param>
 150    /// <exception cref="InvalidOperationException">Thrown when required inputs are missing or invalid.</exception>
 151    private static void ValidatePowerShellInputs(string? code, HttpContext context, Dictionary<string, string>? credenti
 152    {
 9153        if (!context.Items.ContainsKey("PS_INSTANCE"))
 154        {
 1155            throw new InvalidOperationException("PowerShell runspace not found in context items. Ensure PowerShellRunspa
 156        }
 157
 8158        if (credentials == null || credentials.Count == 0)
 159        {
 1160            logger.Warning("Credentials are null or empty.");
 1161            throw new InvalidOperationException("Credentials are null or empty.");
 162        }
 163
 7164        if (string.IsNullOrEmpty(code))
 165        {
 1166            throw new InvalidOperationException("PowerShell authentication code is null or empty.");
 167        }
 6168    }
 169
 170    /// <summary>
 171    /// Retrieves and validates the PowerShell instance from the HTTP context.
 172    /// </summary>
 173    /// <param name="context">The HTTP context containing the PowerShell instance.</param>
 174    /// <returns>The validated PowerShell instance.</returns>
 175    /// <exception cref="InvalidOperationException">Thrown when the PowerShell instance or runspace is not found or inva
 176    private static PowerShell GetPowerShellForValidation(HttpContext context)
 177    {
 6178        var ps = context.Items["PS_INSTANCE"] as PowerShell
 6179              ?? throw new InvalidOperationException("PowerShell instance not found in context items.");
 180
 6181        return ps.Runspace == null
 6182            ? throw new InvalidOperationException("PowerShell runspace is not set. Ensure PowerShellRunspaceMiddleware i
 6183            : ps;
 184    }
 185
 186    /// <summary>
 187    /// Executes the PowerShell script with support for request cancellation.
 188    /// </summary>
 189    /// <param name="ps">The PowerShell instance to execute.</param>
 190    /// <param name="context">The HTTP context containing cancellation token and request information.</param>
 191    /// <param name="logger">The logger instance for logging debug information.</param>
 192    /// <returns>A collection of PowerShell objects returned from script execution.</returns>
 193    /// <exception cref="OperationCanceledException">Thrown when the request is cancelled.</exception>
 194    private static async ValueTask<PSDataCollection<PSObject>> ExecutePowerShellScriptAsync(PowerShell ps, HttpContext c
 195    {
 196        try
 197        {
 6198            return await ps.InvokeWithRequestAbortAsync(
 6199                context.RequestAborted,
 0200                onAbortLog: () => logger.DebugSanitized("Request aborted; stopping PowerShell pipeline for {Path}", cont
 6201            ).ConfigureAwait(false);
 202        }
 0203        catch (OperationCanceledException) when (context.RequestAborted.IsCancellationRequested)
 204        {
 0205            if (logger.IsEnabled(Serilog.Events.LogEventLevel.Debug))
 206            {
 0207                logger.DebugSanitized("PowerShell pipeline cancelled due to request abortion for {Path}", context.Reques
 208            }
 0209            throw;
 210        }
 6211    }
 212
 213    /// <summary>
 214    /// Validates and extracts the boolean result from PowerShell script execution.
 215    /// </summary>
 216    /// <param name="psResults">The collection of PowerShell objects returned from script execution.</param>
 217    /// <param name="logger">The logger instance for logging errors.</param>
 218    /// <returns>The boolean result from the PowerShell script, or false if the result is invalid.</returns>
 219    private static bool ValidatePowerShellResult(PSDataCollection<PSObject>? psResults, Serilog.ILogger logger)
 220    {
 6221        if (psResults == null || psResults.Count == 0)
 222        {
 1223            logger.Error("PowerShell script did not return a valid boolean result.");
 1224            return false;
 225        }
 226
 5227        if (psResults[0]?.BaseObject is not bool isValid)
 228        {
 1229            logger.Error("PowerShell script did not return a valid boolean result.");
 1230            return false;
 231        }
 232
 4233        return isValid;
 234    }
 235
 236    /// <summary>
 237    /// Builds a C# validator function for the specified authentication settings.
 238    /// </summary>
 239    /// <param name="host">The Kestrun host instance.</param>
 240    /// <param name="settings">The authentication code settings.</param>
 241    /// <param name="globals">Global variables to include in the validation context.</param>
 242    /// <returns>A function that validates the authentication context.</returns>
 243    internal static Func<HttpContext, IDictionary<string, object?>, Task<bool>> BuildCsValidator(
 244        KestrunHost host,
 245        AuthenticationCodeSettings settings,
 246          params (string Name, object? Prototype)[] globals)
 247    {
 2248        var log = host.Logger;
 2249        if (log.IsEnabled(Serilog.Events.LogEventLevel.Debug))
 250        {
 2251            log.Debug("Building C# authentication script with globals: {Globals}", globals);
 252        }
 253
 254        // Place-holders so Roslyn knows the globals that will exist
 10255        var stencil = globals.ToDictionary(n => n.Name, n => n.Prototype,
 2256                                             StringComparer.OrdinalIgnoreCase);
 2257        if (log.IsEnabled(Serilog.Events.LogEventLevel.Debug))
 258        {
 2259            log.Debug("Compiling C# authentication script with variables: {Variables}", stencil);
 260        }
 261
 2262        var script = CSharpDelegateBuilder.Compile(
 2263            host: host,
 2264            code: settings.Code,                   // already scoped by caller
 2265            extraImports: settings.ExtraImports,
 2266            extraRefs: settings.ExtraRefs,
 2267            locals: stencil,
 2268            languageVersion: settings.CSharpVersion);
 269
 270        // Return the runtime delegate
 2271        return async (ctx, vars) =>
 2272        {
 4273            if (log.IsEnabled(Serilog.Events.LogEventLevel.Debug))
 2274            {
 4275                log.Debug("Running C# authentication script with variables: {Variables}", vars);
 2276            }
 2277            // --- Kestrun plumbing -------------------------------------------------
 4278            var krReq = await KestrunRequest.NewRequest(ctx);
 4279            var krRes = new KestrunResponse(krReq);
 4280            var kCtx = new KestrunContext(host, krReq, krRes, ctx);
 2281            // ---------------------------------------------------------------------
 4282            var globalsDict = new Dictionary<string, object?>(
 4283                    vars, StringComparer.OrdinalIgnoreCase);
 2284            // Merge shared state + user variables
 4285            var globals = new CsGlobals(
 4286                host.SharedState.Snapshot(),
 4287                kCtx,
 4288                globalsDict);
 2289
 4290            var result = await script.RunAsync(globals).ConfigureAwait(false);
 4291            return result.ReturnValue is true;
 6292        };
 293    }
 294
 295    internal static Func<HttpContext, IDictionary<string, object?>, Task<bool>> BuildVBNetValidator(
 296        KestrunHost host,
 297        AuthenticationCodeSettings settings,
 298      params (string Name, object? Prototype)[] globals)
 299    {
 3300        if (host is null)
 301        {
 0302            throw new ArgumentNullException(nameof(host), "KestrunHost cannot be null");
 303        }
 3304        if (settings is null)
 305        {
 0306            throw new ArgumentNullException(nameof(settings), "AuthenticationCodeSettings cannot be null");
 307        }
 3308        var log = host.Logger;
 309        // Place-holders so Roslyn knows the globals that will exist
 15310        var stencil = globals.ToDictionary(n => n.Name, n => n.Prototype,
 3311                                                 StringComparer.OrdinalIgnoreCase);
 312
 3313        if (log.IsEnabled(Serilog.Events.LogEventLevel.Debug))
 314        {
 2315            log.Debug("Compiling VB.NET authentication script with variables: {Variables}", stencil);
 316        }
 317
 318        // Compile the VB.NET script with the provided settings
 3319        var script = VBNetDelegateBuilder.Compile<bool>(
 3320            host: host,
 3321            code: settings.Code,
 3322            extraImports: settings.ExtraImports,
 3323            extraRefs: settings.ExtraRefs,
 3324            locals: stencil,
 3325            languageVersion: settings.VisualBasicVersion);
 326
 327        // Return the runtime delegate
 3328        return async (ctx, vars) =>
 3329        {
 4330            if (log.IsEnabled(Serilog.Events.LogEventLevel.Debug))
 3331            {
 4332                log.Debug("Running VB.NET authentication script with variables: {Variables}", vars);
 3333            }
 3334
 3335            // --- Kestrun plumbing -------------------------------------------------
 4336            var krReq = await KestrunRequest.NewRequest(ctx);
 4337            var krRes = new KestrunResponse(krReq);
 4338            var kCtx = new KestrunContext(host, krReq, krRes, ctx);
 3339            // ---------------------------------------------------------------------
 3340
 3341            // Merge shared state + user variables
 4342            var globals = new CsGlobals(
 4343                host.SharedState.Snapshot(),
 4344                kCtx,
 4345                new Dictionary<string, object?>(vars, StringComparer.OrdinalIgnoreCase));
 3346
 4347            var result = await script(globals).ConfigureAwait(false);
 3348
 4349            return result is bool isValid && isValid;
 7350        };
 351    }
 352
 353    /// <summary>
 354    /// Builds a PowerShell-based function for issuing claims for a user.
 355    /// </summary>
 356    /// <param name="host">The Kestrun host instance.</param>
 357    /// <param name="settings">The authentication code settings containing the PowerShell script.</param>
 358    /// <returns>A function that issues claims using the provided PowerShell script.</returns>
 359    static Func<HttpContext, string, Task<IEnumerable<Claim>>> BuildPsIssueClaims(
 360        KestrunHost host,
 361        AuthenticationCodeSettings settings) =>
 2362            async (ctx, identity) =>
 2363        {
 2364            return await IssueClaimsPowerShellAsync(settings.Code, ctx, identity, host.Logger);
 4365        };
 366
 367    /// <summary>
 368    /// Issues claims for a user by executing a PowerShell script.
 369    /// </summary>
 370    /// <param name="code">The PowerShell script code used to issue claims.</param>
 371    /// <param name="context">The HTTP context containing the PowerShell runspace.</param>
 372    /// <param name="identity">The username for which to issue claims.</param>
 373    /// <param name="logger">The logger instance for logging.</param>
 374    /// <returns>A task representing the asynchronous operation, with a collection of issued claims.</returns>
 375    static async Task<IEnumerable<Claim>> IssueClaimsPowerShellAsync(string? code, HttpContext context, string identity,
 376    {
 3377        if (string.IsNullOrWhiteSpace(identity))
 378        {
 1379            logger.Warning("Identity is null or empty.");
 1380            return [];
 381        }
 2382        if (string.IsNullOrEmpty(code))
 383        {
 0384            throw new InvalidOperationException("PowerShell authentication code is null or empty.");
 385        }
 386
 387        try
 388        {
 2389            var ps = GetPowerShell(context);
 2390            _ = ps.AddScript(code, useLocalScope: true).AddParameter("identity", identity);
 391
 392            //  var psResults = await ps.InvokeAsync().ConfigureAwait(false);
 393            PSDataCollection<PSObject> psResults;
 394            try
 395            {
 2396                psResults = await ps.InvokeWithRequestAbortAsync(
 2397                    context.RequestAborted,
 0398                    onAbortLog: () => logger.DebugSanitized("Request aborted; stopping PowerShell pipeline for {Path}", 
 2399                ).ConfigureAwait(false);
 2400            }
 0401            catch (OperationCanceledException) when (context.RequestAborted.IsCancellationRequested)
 402            {
 0403                if (logger.IsEnabled(Serilog.Events.LogEventLevel.Debug))
 404                {
 0405                    logger.DebugSanitized("PowerShell pipeline cancelled due to request abortion for {Path}", context.Re
 406                }
 407                // Treat as cancellation, not an error.
 0408                return [];
 409            }
 2410            if (psResults is null || psResults.Count == 0)
 411            {
 0412                return [];
 413            }
 414
 2415            var claims = new List<Claim>(psResults.Count);
 8416            foreach (var r in psResults)
 417            {
 2418                if (TryToClaim(r?.BaseObject, out var claim))
 419                {
 2420                    claims.Add(claim);
 421                }
 422                else
 423                {
 0424                    logger.Warning("PowerShell script returned an unsupported type: {Type}", r?.BaseObject?.GetType());
 0425                    throw new InvalidOperationException("PowerShell script returned an unsupported type.");
 426                }
 427            }
 428
 2429            return claims;
 430        }
 0431        catch (Exception ex)
 432        {
 0433            logger.Error(ex, "Error during Issue Claims for {Identity}", identity);
 0434            return [];
 435        }
 3436    }
 437
 438    /// <summary>
 439    /// Retrieves the PowerShell instance from the HTTP context.
 440    /// </summary>
 441    /// <param name="ctx">The HTTP context containing the PowerShell runspace.</param>
 442    /// <returns>The PowerShell instance associated with the context.</returns>
 443    /// <exception cref="InvalidOperationException">Thrown when the PowerShell runspace is not found.</exception>
 444    private static PowerShell GetPowerShell(HttpContext ctx)
 445    {
 4446        return !ctx.Items.TryGetValue("PS_INSTANCE", out var psObj) || psObj is not PowerShell ps || ps.Runspace == null
 4447            ? throw new InvalidOperationException("PowerShell runspace not found or not set in context items. Ensure Pow
 4448            : ps;
 449    }
 450
 451    /// <summary>
 452    /// Tries to create a Claim from the provided object.
 453    /// </summary>
 454    /// <param name="obj">The object to create a Claim from.</param>
 455    /// <param name="claim">The created Claim, if successful.</param>
 456    /// <returns>True if the Claim was created successfully; otherwise, false.</returns>
 457    private static bool TryToClaim(object? obj, out Claim claim)
 458    {
 6459        switch (obj)
 460        {
 461            case Claim c:
 1462                claim = c;
 1463                return true;
 464
 3465            case IDictionary dict when dict.Contains("Type") && dict.Contains("Value"):
 3466                var typeStr = dict["Type"]?.ToString();
 3467                var valueStr = dict["Value"]?.ToString();
 3468                if (!string.IsNullOrEmpty(typeStr) && !string.IsNullOrEmpty(valueStr))
 469                {
 3470                    claim = new Claim(typeStr, valueStr);
 3471                    return true;
 472                }
 473                break;
 474
 1475            case string s when s.Contains(':'):
 1476                var idx = s.IndexOf(':');
 1477                if (idx >= 0 && idx < s.Length - 1)
 478                {
 1479                    claim = new Claim(s[..idx], s[(idx + 1)..]);
 1480                    return true;
 481                }
 482                break;
 483            default:
 484                // Unsupported type
 485                break;
 486        }
 487
 1488        claim = default!;
 1489        return false;
 490    }
 491
 492    /// <summary>
 493    /// Builds a C#-based function for issuing claims for a user.
 494    /// </summary>
 495    /// <param name="host">The Kestrun host instance.</param>
 496    /// <param name="settings">The authentication code settings containing the C# script.</param>
 497    /// <returns>A function that issues claims using the provided C# script.</returns>
 498    static Func<HttpContext, string, Task<IEnumerable<Claim>>> BuildCsIssueClaims(
 499        KestrunHost host,
 500        AuthenticationCodeSettings settings)
 501    {
 3502        if (host is null)
 503        {
 0504            throw new ArgumentNullException(nameof(host), "KestrunHost cannot be null");
 505        }
 3506        var logger = host.Logger;
 3507        if (logger.IsEnabled(Serilog.Events.LogEventLevel.Debug))
 508        {
 3509            logger.Debug("Compiling C# script for issuing claims.");
 510        }
 511
 512        // Compile the C# script with the provided settings
 3513        var script = CSharpDelegateBuilder.Compile(host: host,
 3514        code: settings.Code,
 3515        extraImports: settings.ExtraImports,
 3516        extraRefs: settings.ExtraRefs,
 3517        locals: new Dictionary<string, object?>
 3518            {
 3519                { "identity", "" }
 3520            }, languageVersion: settings.CSharpVersion);
 521
 3522        return async (ctx, identity) =>
 3523        {
 2524            var krRequest = await KestrunRequest.NewRequest(ctx);
 2525            var krResponse = new KestrunResponse(krRequest);
 2526            var context = new KestrunContext(host, krRequest, krResponse, ctx);
 2527            var globals = new CsGlobals(host.SharedState.Snapshot(), context, new Dictionary<string, object?>
 2528            {
 2529                { "identity", identity }
 2530            });
 2531            var result = await script.RunAsync(globals).ConfigureAwait(false);
 2532            return result.ReturnValue is IEnumerable<Claim> claims
 2533                ? claims
 2534                : [];
 5535        };
 536    }
 537
 538    /// <summary>
 539    /// Builds a VB.NET-based function for issuing claims for a user.
 540    /// </summary>
 541    /// <param name="host">The Kestrun host instance.</param>
 542    /// <param name="settings">The authentication code settings containing the VB.NET script.</param>
 543    /// <returns>A function that issues claims using the provided VB.NET script.</returns>
 544    static Func<HttpContext, string, Task<IEnumerable<Claim>>> BuildVBNetIssueClaims(
 545        KestrunHost host,
 546        AuthenticationCodeSettings settings)
 547    {
 3548        if (host is null)
 549        {
 0550            throw new ArgumentNullException(nameof(host), "KestrunHost cannot be null");
 551        }
 3552        var logger = host.Logger;
 3553        if (logger.IsEnabled(Serilog.Events.LogEventLevel.Debug))
 554        {
 2555            logger.Debug("Compiling VB.NET script for issuing claims.");
 556        }
 557
 558        // Compile the VB.NET script with the provided settings
 3559        var script = VBNetDelegateBuilder.Compile<IEnumerable<Claim>>(
 3560            host: host,
 3561            code: settings.Code,
 3562            extraImports: settings.ExtraImports,
 3563            extraRefs: settings.ExtraRefs,
 3564            locals: new Dictionary<string, object?>
 3565            {
 3566                { "identity", "" }
 3567            }, languageVersion: settings.VisualBasicVersion);
 568
 3569        return async (ctx, identity) =>
 3570        {
 2571            var krRequest = await KestrunRequest.NewRequest(ctx);
 2572            var krResponse = new KestrunResponse(krRequest);
 2573            var context = new KestrunContext(host, krRequest, krResponse, ctx);
 2574            var glob = new CsGlobals(host.SharedState.Snapshot(), context, new Dictionary<string, object?>
 2575            {
 2576                { "identity", identity }
 2577            });
 3578            // Run the VB.NET script and get the result
 3579            // Note: The script should return a boolean indicating success or failure
 2580            var result = await script(glob).ConfigureAwait(false);
 2581            return result is IEnumerable<Claim> claims
 2582              ? claims
 2583           : [];
 5584        };
 585    }
 586}

Methods/Properties

GetAuthenticationTicketAsync()
GetIssuedClaimsAsync()
EnsureNameClaim(System.Collections.Generic.List`1<System.Security.Claims.Claim>,System.String,System.String,Serilog.ILogger)
CreateAuthenticationTicket(System.Collections.Generic.IEnumerable`1<System.Security.Claims.Claim>,Microsoft.AspNetCore.Authentication.AuthenticationScheme)
ValidatePowerShellAsync()
ValidatePowerShellInputs(System.String,Microsoft.AspNetCore.Http.HttpContext,System.Collections.Generic.Dictionary`2<System.String,System.String>,Serilog.ILogger)
GetPowerShellForValidation(Microsoft.AspNetCore.Http.HttpContext)
ExecutePowerShellScriptAsync()
ValidatePowerShellResult(System.Management.Automation.PSDataCollection`1<System.Management.Automation.PSObject>,Serilog.ILogger)
BuildCsValidator(Kestrun.Hosting.KestrunHost,Kestrun.Authentication.AuthenticationCodeSettings,System.ValueTuple`2<System.String,System.Object>[])
BuildVBNetValidator(Kestrun.Hosting.KestrunHost,Kestrun.Authentication.AuthenticationCodeSettings,System.ValueTuple`2<System.String,System.Object>[])
BuildPsIssueClaims(Kestrun.Hosting.KestrunHost,Kestrun.Authentication.AuthenticationCodeSettings)
IssueClaimsPowerShellAsync()
GetPowerShell(Microsoft.AspNetCore.Http.HttpContext)
TryToClaim(System.Object,System.Security.Claims.Claim&)
BuildCsIssueClaims(Kestrun.Hosting.KestrunHost,Kestrun.Authentication.AuthenticationCodeSettings)
BuildVBNetIssueClaims(Kestrun.Hosting.KestrunHost,Kestrun.Authentication.AuthenticationCodeSettings)