< 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@ca54e35c77799b76774b3805b6f075cdbc0c5fbe
Line coverage
87%
Covered lines: 210
Uncovered lines: 29
Coverable lines: 239
Total lines: 576
Line coverage: 87.8%
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 09/08/2025 - 20:34:03 Line coverage: 90.4% (189/209) Branch coverage: 75.5% (68/90) Total lines: 478 Tag: Kestrun/Kestrun@3790ee5884494a7a2a829344a47743e0bf492e7209/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@0d738bf294e6281b936d031e1979d928007495ff01/02/2026 - 00:16:25 Line coverage: 87.8% (210/239) Branch coverage: 74.5% (76/102) Total lines: 576 Tag: Kestrun/Kestrun@8405dc23b786b9d436fba0d65fb80baa4171e1d0 09/08/2025 - 20:34:03 Line coverage: 90.4% (189/209) Branch coverage: 75.5% (68/90) Total lines: 478 Tag: Kestrun/Kestrun@3790ee5884494a7a2a829344a47743e0bf492e7209/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@0d738bf294e6281b936d031e1979d928007495ff01/02/2026 - 00:16:25 Line coverage: 87.8% (210/239) Branch coverage: 74.5% (76/102) Total lines: 576 Tag: Kestrun/Kestrun@8405dc23b786b9d436fba0d65fb80baa4171e1d0

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            // Create Kestrun context
 4278            var kCtx = new KestrunContext(host, ctx);
 2279            // ---------------------------------------------------------------------
 4280            var globalsDict = new Dictionary<string, object?>(
 4281                    vars, StringComparer.OrdinalIgnoreCase);
 2282            // Merge shared state + user variables
 4283            var globals = new CsGlobals(
 4284                host.SharedState.Snapshot(),
 4285                kCtx,
 4286                globalsDict);
 2287
 4288            var result = await script.RunAsync(globals).ConfigureAwait(false);
 4289            return result.ReturnValue is true;
 6290        };
 291    }
 292
 293    internal static Func<HttpContext, IDictionary<string, object?>, Task<bool>> BuildVBNetValidator(
 294        KestrunHost host,
 295        AuthenticationCodeSettings settings,
 296      params (string Name, object? Prototype)[] globals)
 297    {
 3298        if (host is null)
 299        {
 0300            throw new ArgumentNullException(nameof(host), "KestrunHost cannot be null");
 301        }
 3302        if (settings is null)
 303        {
 0304            throw new ArgumentNullException(nameof(settings), "AuthenticationCodeSettings cannot be null");
 305        }
 3306        var log = host.Logger;
 307        // Place-holders so Roslyn knows the globals that will exist
 15308        var stencil = globals.ToDictionary(n => n.Name, n => n.Prototype,
 3309                                                 StringComparer.OrdinalIgnoreCase);
 310
 3311        if (log.IsEnabled(Serilog.Events.LogEventLevel.Debug))
 312        {
 2313            log.Debug("Compiling VB.NET authentication script with variables: {Variables}", stencil);
 314        }
 315
 316        // Compile the VB.NET script with the provided settings
 3317        var script = VBNetDelegateBuilder.Compile<bool>(
 3318            host: host,
 3319            code: settings.Code,
 3320            extraImports: settings.ExtraImports,
 3321            extraRefs: settings.ExtraRefs,
 3322            locals: stencil,
 3323            languageVersion: settings.VisualBasicVersion);
 324
 325        // Return the runtime delegate
 3326        return async (ctx, vars) =>
 3327        {
 4328            if (log.IsEnabled(Serilog.Events.LogEventLevel.Debug))
 3329            {
 4330                log.Debug("Running VB.NET authentication script with variables: {Variables}", vars);
 3331            }
 3332
 4333            var kCtx = new KestrunContext(host, ctx);
 3334
 3335            // Merge shared state + user variables
 4336            var globals = new CsGlobals(
 4337                host.SharedState.Snapshot(),
 4338                kCtx,
 4339                new Dictionary<string, object?>(vars, StringComparer.OrdinalIgnoreCase));
 3340
 4341            var result = await script(globals).ConfigureAwait(false);
 3342
 4343            return result is bool isValid && isValid;
 7344        };
 345    }
 346
 347    /// <summary>
 348    /// Builds a PowerShell-based function for issuing claims for a user.
 349    /// </summary>
 350    /// <param name="host">The Kestrun host instance.</param>
 351    /// <param name="settings">The authentication code settings containing the PowerShell script.</param>
 352    /// <returns>A function that issues claims using the provided PowerShell script.</returns>
 353    static Func<HttpContext, string, Task<IEnumerable<Claim>>> BuildPsIssueClaims(
 354        KestrunHost host,
 355        AuthenticationCodeSettings settings) =>
 2356            async (ctx, identity) =>
 2357        {
 2358            return await IssueClaimsPowerShellAsync(settings.Code, ctx, identity, host.Logger);
 4359        };
 360
 361    /// <summary>
 362    /// Issues claims for a user by executing a PowerShell script.
 363    /// </summary>
 364    /// <param name="code">The PowerShell script code used to issue claims.</param>
 365    /// <param name="context">The HTTP context containing the PowerShell runspace.</param>
 366    /// <param name="identity">The username for which to issue claims.</param>
 367    /// <param name="logger">The logger instance for logging.</param>
 368    /// <returns>A task representing the asynchronous operation, with a collection of issued claims.</returns>
 369    static async Task<IEnumerable<Claim>> IssueClaimsPowerShellAsync(string? code, HttpContext context, string identity,
 370    {
 3371        if (string.IsNullOrWhiteSpace(identity))
 372        {
 1373            logger.Warning("Identity is null or empty.");
 1374            return [];
 375        }
 2376        if (string.IsNullOrEmpty(code))
 377        {
 0378            throw new InvalidOperationException("PowerShell authentication code is null or empty.");
 379        }
 380
 381        try
 382        {
 2383            var ps = GetPowerShell(context);
 2384            _ = ps.AddScript(code, useLocalScope: true).AddParameter("identity", identity);
 385
 386            //  var psResults = await ps.InvokeAsync().ConfigureAwait(false);
 387            PSDataCollection<PSObject> psResults;
 388            try
 389            {
 2390                psResults = await ps.InvokeWithRequestAbortAsync(
 2391                    context.RequestAborted,
 0392                    onAbortLog: () => logger.DebugSanitized("Request aborted; stopping PowerShell pipeline for {Path}", 
 2393                ).ConfigureAwait(false);
 2394            }
 0395            catch (OperationCanceledException) when (context.RequestAborted.IsCancellationRequested)
 396            {
 0397                if (logger.IsEnabled(Serilog.Events.LogEventLevel.Debug))
 398                {
 0399                    logger.DebugSanitized("PowerShell pipeline cancelled due to request abortion for {Path}", context.Re
 400                }
 401                // Treat as cancellation, not an error.
 0402                return [];
 403            }
 2404            if (psResults is null || psResults.Count == 0)
 405            {
 0406                return [];
 407            }
 408
 2409            var claims = new List<Claim>(psResults.Count);
 8410            foreach (var r in psResults)
 411            {
 2412                if (TryToClaim(r?.BaseObject, out var claim))
 413                {
 2414                    claims.Add(claim);
 415                }
 416                else
 417                {
 0418                    logger.Warning("PowerShell script returned an unsupported type: {Type}", r?.BaseObject?.GetType());
 0419                    throw new InvalidOperationException("PowerShell script returned an unsupported type.");
 420                }
 421            }
 422
 2423            return claims;
 424        }
 0425        catch (Exception ex)
 426        {
 0427            logger.Error(ex, "Error during Issue Claims for {Identity}", identity);
 0428            return [];
 429        }
 3430    }
 431
 432    /// <summary>
 433    /// Retrieves the PowerShell instance from the HTTP context.
 434    /// </summary>
 435    /// <param name="ctx">The HTTP context containing the PowerShell runspace.</param>
 436    /// <returns>The PowerShell instance associated with the context.</returns>
 437    /// <exception cref="InvalidOperationException">Thrown when the PowerShell runspace is not found.</exception>
 438    private static PowerShell GetPowerShell(HttpContext ctx)
 439    {
 4440        return !ctx.Items.TryGetValue("PS_INSTANCE", out var psObj) || psObj is not PowerShell ps || ps.Runspace == null
 4441            ? throw new InvalidOperationException("PowerShell runspace not found or not set in context items. Ensure Pow
 4442            : ps;
 443    }
 444
 445    /// <summary>
 446    /// Tries to create a Claim from the provided object.
 447    /// </summary>
 448    /// <param name="obj">The object to create a Claim from.</param>
 449    /// <param name="claim">The created Claim, if successful.</param>
 450    /// <returns>True if the Claim was created successfully; otherwise, false.</returns>
 451    private static bool TryToClaim(object? obj, out Claim claim)
 452    {
 6453        switch (obj)
 454        {
 455            case Claim c:
 1456                claim = c;
 1457                return true;
 458
 3459            case IDictionary dict when dict.Contains("Type") && dict.Contains("Value"):
 3460                var typeStr = dict["Type"]?.ToString();
 3461                var valueStr = dict["Value"]?.ToString();
 3462                if (!string.IsNullOrEmpty(typeStr) && !string.IsNullOrEmpty(valueStr))
 463                {
 3464                    claim = new Claim(typeStr, valueStr);
 3465                    return true;
 466                }
 467                break;
 468
 1469            case string s when s.Contains(':'):
 1470                var idx = s.IndexOf(':');
 1471                if (idx >= 0 && idx < s.Length - 1)
 472                {
 1473                    claim = new Claim(s[..idx], s[(idx + 1)..]);
 1474                    return true;
 475                }
 476                break;
 477            default:
 478                // Unsupported type
 479                break;
 480        }
 481
 1482        claim = default!;
 1483        return false;
 484    }
 485
 486    /// <summary>
 487    /// Builds a C#-based function for issuing claims for a user.
 488    /// </summary>
 489    /// <param name="host">The Kestrun host instance.</param>
 490    /// <param name="settings">The authentication code settings containing the C# script.</param>
 491    /// <returns>A function that issues claims using the provided C# script.</returns>
 492    static Func<HttpContext, string, Task<IEnumerable<Claim>>> BuildCsIssueClaims(
 493        KestrunHost host,
 494        AuthenticationCodeSettings settings)
 495    {
 3496        if (host is null)
 497        {
 0498            throw new ArgumentNullException(nameof(host), "KestrunHost cannot be null");
 499        }
 3500        var logger = host.Logger;
 3501        if (logger.IsEnabled(Serilog.Events.LogEventLevel.Debug))
 502        {
 3503            logger.Debug("Compiling C# script for issuing claims.");
 504        }
 505
 506        // Compile the C# script with the provided settings
 3507        var script = CSharpDelegateBuilder.Compile(host: host,
 3508        code: settings.Code,
 3509        extraImports: settings.ExtraImports,
 3510        extraRefs: settings.ExtraRefs,
 3511        locals: new Dictionary<string, object?>
 3512            {
 3513                { "identity", "" }
 3514            }, languageVersion: settings.CSharpVersion);
 515
 3516        return async (ctx, identity) =>
 3517        {
 2518            var context = new KestrunContext(host, ctx);
 2519            var globals = new CsGlobals(host.SharedState.Snapshot(), context, new Dictionary<string, object?>
 2520            {
 2521                { "identity", identity }
 2522            });
 2523            var result = await script.RunAsync(globals).ConfigureAwait(false);
 2524            return result.ReturnValue is IEnumerable<Claim> claims
 2525                ? claims
 2526                : [];
 5527        };
 528    }
 529
 530    /// <summary>
 531    /// Builds a VB.NET-based function for issuing claims for a user.
 532    /// </summary>
 533    /// <param name="host">The Kestrun host instance.</param>
 534    /// <param name="settings">The authentication code settings containing the VB.NET script.</param>
 535    /// <returns>A function that issues claims using the provided VB.NET script.</returns>
 536    static Func<HttpContext, string, Task<IEnumerable<Claim>>> BuildVBNetIssueClaims(
 537        KestrunHost host,
 538        AuthenticationCodeSettings settings)
 539    {
 3540        if (host is null)
 541        {
 0542            throw new ArgumentNullException(nameof(host), "KestrunHost cannot be null");
 543        }
 3544        var logger = host.Logger;
 3545        if (logger.IsEnabled(Serilog.Events.LogEventLevel.Debug))
 546        {
 2547            logger.Debug("Compiling VB.NET script for issuing claims.");
 548        }
 549
 550        // Compile the VB.NET script with the provided settings
 3551        var script = VBNetDelegateBuilder.Compile<IEnumerable<Claim>>(
 3552            host: host,
 3553            code: settings.Code,
 3554            extraImports: settings.ExtraImports,
 3555            extraRefs: settings.ExtraRefs,
 3556            locals: new Dictionary<string, object?>
 3557            {
 3558                { "identity", "" }
 3559            }, languageVersion: settings.VisualBasicVersion);
 560
 3561        return async (ctx, identity) =>
 3562        {
 2563            var context = new KestrunContext(host, ctx);
 2564            var glob = new CsGlobals(host.SharedState.Snapshot(), context, new Dictionary<string, object?>
 2565            {
 2566                { "identity", identity }
 2567            });
 3568            // Run the VB.NET script and get the result
 3569            // Note: The script should return a boolean indicating success or failure
 2570            var result = await script(glob).ConfigureAwait(false);
 2571            return result is IEnumerable<Claim> claims
 2572              ? claims
 2573           : [];
 5574        };
 575    }
 576}

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)