| | 1 | |
|
| | 2 | |
|
| | 3 | |
|
| | 4 | | using System.Collections; |
| | 5 | | using System.Management.Automation; |
| | 6 | | using System.Security.Claims; |
| | 7 | | using Kestrun.Hosting; |
| | 8 | | using Kestrun.Languages; |
| | 9 | | using Kestrun.Models; |
| | 10 | | using Kestrun.SharedState; |
| | 11 | | using Microsoft.AspNetCore.Authentication; |
| | 12 | |
|
| | 13 | | namespace Kestrun.Authentication; |
| | 14 | |
|
| | 15 | |
|
| | 16 | | /// <summary> |
| | 17 | | /// Defines common options for authentication, including code validation, claim issuance, and claim policy configuration |
| | 18 | | /// </summary> |
| | 19 | | public 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 | | { |
| 8 | 34 | | var claims = new List<Claim>(); |
| | 35 | |
|
| | 36 | | // 1) Issue extra claims if configured |
| 8 | 37 | | claims.AddRange(await GetIssuedClaimsAsync(Context, user, Options).ConfigureAwait(false)); |
| | 38 | |
|
| | 39 | | // 2) Ensure a Name claim exists |
| 8 | 40 | | EnsureNameClaim(claims, user, alias, Options.Logger); |
| | 41 | |
|
| | 42 | | // 3) Create and return the ticket |
| 8 | 43 | | return CreateAuthenticationTicket(claims, Scheme); |
| 8 | 44 | | } |
| | 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 | | { |
| 8 | 55 | | if (options.IssueClaims is null) |
| | 56 | | { |
| 8 | 57 | | return []; |
| | 58 | | } |
| | 59 | |
|
| 0 | 60 | | var extra = await options.IssueClaims(context, user).ConfigureAwait(false); |
| 0 | 61 | | if (extra is null) |
| | 62 | | { |
| 0 | 63 | | return []; |
| | 64 | | } |
| | 65 | |
|
| | 66 | | // Filter out nulls and empty values |
| 0 | 67 | | return [.. extra |
| 0 | 68 | | .Where(c => c is not null) |
| 0 | 69 | | .OfType<Claim>() |
| 0 | 70 | | .Where(c => !string.IsNullOrEmpty(c.Value))]; |
| 8 | 71 | | } |
| | 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 | | { |
| 8 | 82 | | if (claims.Any(c => c.Type == ClaimTypes.Name)) |
| | 83 | | { |
| 0 | 84 | | return; |
| | 85 | | } |
| | 86 | |
|
| 8 | 87 | | if (logger.IsEnabled(Serilog.Events.LogEventLevel.Debug)) |
| | 88 | | { |
| 4 | 89 | | logger.Debug("No Name claim found, adding default Name claim"); |
| | 90 | | } |
| | 91 | |
|
| 8 | 92 | | var name = string.IsNullOrEmpty(alias) ? user : alias; |
| 8 | 93 | | claims.Add(new Claim(ClaimTypes.Name, name!)); |
| 8 | 94 | | } |
| | 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 | | { |
| 8 | 104 | | var claimsIdentity = new ClaimsIdentity(claims, scheme.Name); |
| 8 | 105 | | var principal = new ClaimsPrincipal(claimsIdentity); |
| 8 | 106 | | 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 | | { |
| 6 | 123 | | if (!context.Items.ContainsKey("PS_INSTANCE")) |
| | 124 | | { |
| 0 | 125 | | throw new InvalidOperationException("PowerShell runspace not found in context items. Ensure PowerShellRu |
| | 126 | | } |
| | 127 | | // Validate that the credentials dictionary is not null or empty |
| 6 | 128 | | if (credentials == null || credentials.Count == 0) |
| | 129 | | { |
| 1 | 130 | | logger.Warning("Credentials are null or empty."); |
| 1 | 131 | | return false; |
| | 132 | | } |
| | 133 | | // Validate that the code is not null or empty |
| 5 | 134 | | if (string.IsNullOrEmpty(code)) |
| | 135 | | { |
| 1 | 136 | | throw new InvalidOperationException("PowerShell authentication code is null or empty."); |
| | 137 | | } |
| | 138 | | // Retrieve the PowerShell instance from the context |
| 4 | 139 | | var ps = context.Items["PS_INSTANCE"] as PowerShell |
| 4 | 140 | | ?? throw new InvalidOperationException("PowerShell instance not found in context items."); |
| 4 | 141 | | if (ps.Runspace == null) |
| | 142 | | { |
| 0 | 143 | | throw new InvalidOperationException("PowerShell runspace is not set. Ensure PowerShellRunspaceMiddleware |
| | 144 | | } |
| | 145 | |
|
| 4 | 146 | | _ = ps.AddScript(code, useLocalScope: true); |
| 24 | 147 | | foreach (var kvp in credentials) |
| | 148 | | { |
| 8 | 149 | | _ = ps.AddParameter(kvp.Key, kvp.Value); |
| | 150 | | } |
| | 151 | |
|
| 4 | 152 | | var psResults = await ps.InvokeAsync().ConfigureAwait(false); |
| | 153 | |
|
| 4 | 154 | | if (psResults.Count == 0 || psResults[0] == null || psResults[0].BaseObject is not bool isValid) |
| | 155 | | { |
| 0 | 156 | | logger.Error("PowerShell script did not return a valid boolean result."); |
| 0 | 157 | | return false; |
| | 158 | | } |
| 4 | 159 | | return isValid; |
| | 160 | | } |
| 1 | 161 | | catch (Exception ex) |
| | 162 | | { |
| 1 | 163 | | logger.Error(ex, "Error during validating PowerShell authentication."); |
| 1 | 164 | | return false; |
| | 165 | | } |
| 6 | 166 | | } |
| | 167 | |
|
| | 168 | | /// <summary> |
| | 169 | | /// Builds a C# validator function for the specified authentication settings. |
| | 170 | | /// </summary> |
| | 171 | | /// <param name="settings">The authentication code settings.</param> |
| | 172 | | /// <param name="log">The logger instance.</param> |
| | 173 | | /// <param name="globals">Global variables to include in the validation context.</param> |
| | 174 | | /// <returns>A function that validates the authentication context.</returns> |
| | 175 | | internal static Func<HttpContext, IDictionary<string, object?>, Task<bool>> BuildCsValidator( |
| | 176 | | AuthenticationCodeSettings settings, |
| | 177 | | Serilog.ILogger log, |
| | 178 | | params (string Name, object? Prototype)[] globals) |
| | 179 | | { |
| 2 | 180 | | if (log.IsEnabled(Serilog.Events.LogEventLevel.Debug)) |
| | 181 | | { |
| 2 | 182 | | log.Debug("Building C# authentication script with globals: {Globals}", globals); |
| | 183 | | } |
| | 184 | |
|
| | 185 | | // Place-holders so Roslyn knows the globals that will exist |
| 10 | 186 | | var stencil = globals.ToDictionary(n => n.Name, n => n.Prototype, |
| 2 | 187 | | StringComparer.OrdinalIgnoreCase); |
| 2 | 188 | | if (log.IsEnabled(Serilog.Events.LogEventLevel.Debug)) |
| | 189 | | { |
| 2 | 190 | | log.Debug("Compiling C# authentication script with variables: {Variables}", stencil); |
| | 191 | | } |
| | 192 | |
|
| 2 | 193 | | var script = CSharpDelegateBuilder.Compile( |
| 2 | 194 | | settings.Code, |
| 2 | 195 | | log, // already scoped by caller |
| 2 | 196 | | settings.ExtraImports, |
| 2 | 197 | | settings.ExtraRefs, |
| 2 | 198 | | stencil, |
| 2 | 199 | | languageVersion: settings.CSharpVersion); |
| | 200 | |
|
| | 201 | | // Return the runtime delegate |
| 2 | 202 | | return async (ctx, vars) => |
| 2 | 203 | | { |
| 4 | 204 | | if (log.IsEnabled(Serilog.Events.LogEventLevel.Debug)) |
| 2 | 205 | | { |
| 4 | 206 | | log.Debug("Running C# authentication script with variables: {Variables}", vars); |
| 2 | 207 | | } |
| 2 | 208 | | // --- Kestrun plumbing ------------------------------------------------- |
| 4 | 209 | | var krReq = await KestrunRequest.NewRequest(ctx); |
| 4 | 210 | | var krRes = new KestrunResponse(krReq); |
| 4 | 211 | | var kCtx = new KestrunContext(krReq, krRes, ctx); |
| 2 | 212 | | // --------------------------------------------------------------------- |
| 4 | 213 | | var globalsDict = new Dictionary<string, object?>( |
| 4 | 214 | | vars, StringComparer.OrdinalIgnoreCase); |
| 2 | 215 | | // Merge shared state + user variables |
| 4 | 216 | | var globals = new CsGlobals( |
| 4 | 217 | | SharedStateStore.Snapshot(), |
| 4 | 218 | | kCtx, |
| 4 | 219 | | globalsDict); |
| 2 | 220 | |
|
| 4 | 221 | | var result = await script.RunAsync(globals).ConfigureAwait(false); |
| 4 | 222 | | return result.ReturnValue is true; |
| 6 | 223 | | }; |
| | 224 | | } |
| | 225 | |
|
| | 226 | | internal static Func<HttpContext, IDictionary<string, object?>, Task<bool>> BuildVBNetValidator( |
| | 227 | | AuthenticationCodeSettings settings, |
| | 228 | | Serilog.ILogger log, |
| | 229 | | params (string Name, object? Prototype)[] globals) |
| | 230 | | { |
| 3 | 231 | | if (settings is null) |
| | 232 | | { |
| 0 | 233 | | throw new ArgumentNullException(nameof(settings), "AuthenticationCodeSettings cannot be null"); |
| | 234 | | } |
| | 235 | | // Place-holders so Roslyn knows the globals that will exist |
| 15 | 236 | | var stencil = globals.ToDictionary(n => n.Name, n => n.Prototype, |
| 3 | 237 | | StringComparer.OrdinalIgnoreCase); |
| | 238 | |
|
| 3 | 239 | | if (log.IsEnabled(Serilog.Events.LogEventLevel.Debug)) |
| | 240 | | { |
| 2 | 241 | | log.Debug("Compiling VB.NET authentication script with variables: {Variables}", stencil); |
| | 242 | | } |
| | 243 | |
|
| | 244 | | // Compile the VB.NET script with the provided settings |
| 3 | 245 | | var script = VBNetDelegateBuilder.Compile<bool>( |
| 3 | 246 | | settings.Code, |
| 3 | 247 | | log, // already scoped by caller |
| 3 | 248 | | settings.ExtraImports, |
| 3 | 249 | | settings.ExtraRefs, |
| 3 | 250 | | stencil, |
| 3 | 251 | | languageVersion: settings.VisualBasicVersion); |
| | 252 | |
|
| | 253 | | // Return the runtime delegate |
| 3 | 254 | | return async (ctx, vars) => |
| 3 | 255 | | { |
| 4 | 256 | | if (log.IsEnabled(Serilog.Events.LogEventLevel.Debug)) |
| 3 | 257 | | { |
| 4 | 258 | | log.Debug("Running VB.NET authentication script with variables: {Variables}", vars); |
| 3 | 259 | | } |
| 3 | 260 | |
|
| 3 | 261 | | // --- Kestrun plumbing ------------------------------------------------- |
| 4 | 262 | | var krReq = await KestrunRequest.NewRequest(ctx); |
| 4 | 263 | | var krRes = new KestrunResponse(krReq); |
| 4 | 264 | | var kCtx = new KestrunContext(krReq, krRes, ctx); |
| 3 | 265 | | // --------------------------------------------------------------------- |
| 3 | 266 | |
|
| 3 | 267 | | // Merge shared state + user variables |
| 4 | 268 | | var globals = new CsGlobals( |
| 4 | 269 | | SharedStateStore.Snapshot(), |
| 4 | 270 | | kCtx, |
| 4 | 271 | | new Dictionary<string, object?>(vars, StringComparer.OrdinalIgnoreCase)); |
| 3 | 272 | |
|
| 4 | 273 | | var result = await script(globals).ConfigureAwait(false); |
| 3 | 274 | |
|
| 4 | 275 | | return result is bool isValid && isValid; |
| 7 | 276 | | }; |
| | 277 | | } |
| | 278 | |
|
| | 279 | |
|
| | 280 | | /// <summary> |
| | 281 | | /// Builds a PowerShell-based function for issuing claims for a user. |
| | 282 | | /// </summary> |
| | 283 | | /// <param name="settings">The authentication code settings containing the PowerShell script.</param> |
| | 284 | | /// <param name="logger">The logger instance for logging.</param> |
| | 285 | | /// <returns>A function that issues claims using the provided PowerShell script.</returns> |
| | 286 | | static Func<HttpContext, string, Task<IEnumerable<Claim>>> BuildPsIssueClaims( |
| | 287 | | AuthenticationCodeSettings settings, Serilog.ILogger logger) => |
| 2 | 288 | | async (ctx, identity) => |
| 2 | 289 | | { |
| 2 | 290 | | return await IssueClaimsPowerShellAsync(settings.Code, ctx, identity, logger); |
| 4 | 291 | | }; |
| | 292 | |
|
| | 293 | | /// <summary> |
| | 294 | | /// Issues claims for a user by executing a PowerShell script. |
| | 295 | | /// </summary> |
| | 296 | | /// <param name="code">The PowerShell script code used to issue claims.</param> |
| | 297 | | /// <param name="ctx">The HTTP context containing the PowerShell runspace.</param> |
| | 298 | | /// <param name="identity">The username for which to issue claims.</param> |
| | 299 | | /// <param name="logger">The logger instance for logging.</param> |
| | 300 | | /// <returns>A task representing the asynchronous operation, with a collection of issued claims.</returns> |
| | 301 | | static async Task<IEnumerable<Claim>> IssueClaimsPowerShellAsync(string? code, HttpContext ctx, string identity, Ser |
| | 302 | | { |
| 3 | 303 | | if (string.IsNullOrWhiteSpace(identity)) |
| | 304 | | { |
| 1 | 305 | | logger.Warning("Identity is null or empty."); |
| 1 | 306 | | return []; |
| | 307 | | } |
| 2 | 308 | | if (string.IsNullOrEmpty(code)) |
| | 309 | | { |
| 0 | 310 | | throw new InvalidOperationException("PowerShell authentication code is null or empty."); |
| | 311 | | } |
| | 312 | |
|
| | 313 | | try |
| | 314 | | { |
| 2 | 315 | | var ps = GetPowerShell(ctx); |
| 2 | 316 | | _ = ps.AddScript(code, useLocalScope: true).AddParameter("identity", identity); |
| | 317 | |
|
| 2 | 318 | | var psResults = await ps.InvokeAsync().ConfigureAwait(false); |
| 2 | 319 | | if (psResults is null || psResults.Count == 0) |
| | 320 | | { |
| 0 | 321 | | return []; |
| | 322 | | } |
| | 323 | |
|
| 2 | 324 | | var claims = new List<Claim>(psResults.Count); |
| 8 | 325 | | foreach (var r in psResults) |
| | 326 | | { |
| 2 | 327 | | if (TryToClaim(r?.BaseObject, out var claim)) |
| | 328 | | { |
| 2 | 329 | | claims.Add(claim); |
| | 330 | | } |
| | 331 | | else |
| | 332 | | { |
| 0 | 333 | | logger.Warning("PowerShell script returned an unsupported type: {Type}", r?.BaseObject?.GetType()); |
| 0 | 334 | | throw new InvalidOperationException("PowerShell script returned an unsupported type."); |
| | 335 | | } |
| | 336 | | } |
| | 337 | |
|
| 2 | 338 | | return claims; |
| | 339 | | } |
| 0 | 340 | | catch (Exception ex) |
| | 341 | | { |
| 0 | 342 | | logger.Error(ex, "Error during Issue Claims for {Identity}", identity); |
| 0 | 343 | | return []; |
| | 344 | | } |
| 3 | 345 | | } |
| | 346 | |
|
| | 347 | | /// <summary> |
| | 348 | | /// Retrieves the PowerShell instance from the HTTP context. |
| | 349 | | /// </summary> |
| | 350 | | /// <param name="ctx">The HTTP context containing the PowerShell runspace.</param> |
| | 351 | | /// <returns>The PowerShell instance associated with the context.</returns> |
| | 352 | | /// <exception cref="InvalidOperationException">Thrown when the PowerShell runspace is not found.</exception> |
| | 353 | | private static PowerShell GetPowerShell(HttpContext ctx) |
| | 354 | | { |
| 4 | 355 | | return !ctx.Items.TryGetValue("PS_INSTANCE", out var psObj) || psObj is not PowerShell ps || ps.Runspace == null |
| 4 | 356 | | ? throw new InvalidOperationException("PowerShell runspace not found or not set in context items. Ensure Pow |
| 4 | 357 | | : ps; |
| | 358 | | } |
| | 359 | |
|
| | 360 | | /// <summary> |
| | 361 | | /// Tries to create a Claim from the provided object. |
| | 362 | | /// </summary> |
| | 363 | | /// <param name="obj">The object to create a Claim from.</param> |
| | 364 | | /// <param name="claim">The created Claim, if successful.</param> |
| | 365 | | /// <returns>True if the Claim was created successfully; otherwise, false.</returns> |
| | 366 | | private static bool TryToClaim(object? obj, out Claim claim) |
| | 367 | | { |
| 6 | 368 | | switch (obj) |
| | 369 | | { |
| | 370 | | case Claim c: |
| 1 | 371 | | claim = c; |
| 1 | 372 | | return true; |
| | 373 | |
|
| 3 | 374 | | case IDictionary dict when dict.Contains("Type") && dict.Contains("Value"): |
| 3 | 375 | | var typeStr = dict["Type"]?.ToString(); |
| 3 | 376 | | var valueStr = dict["Value"]?.ToString(); |
| 3 | 377 | | if (!string.IsNullOrEmpty(typeStr) && !string.IsNullOrEmpty(valueStr)) |
| | 378 | | { |
| 3 | 379 | | claim = new Claim(typeStr, valueStr); |
| 3 | 380 | | return true; |
| | 381 | | } |
| | 382 | | break; |
| | 383 | |
|
| 1 | 384 | | case string s when s.Contains(':'): |
| 1 | 385 | | var idx = s.IndexOf(':'); |
| 1 | 386 | | if (idx >= 0 && idx < s.Length - 1) |
| | 387 | | { |
| 1 | 388 | | claim = new Claim(s[..idx], s[(idx + 1)..]); |
| 1 | 389 | | return true; |
| | 390 | | } |
| | 391 | | break; |
| | 392 | | default: |
| | 393 | | // Unsupported type |
| | 394 | | break; |
| | 395 | | } |
| | 396 | |
|
| 1 | 397 | | claim = default!; |
| 1 | 398 | | return false; |
| | 399 | | } |
| | 400 | |
|
| | 401 | |
|
| | 402 | | /// <summary> |
| | 403 | | /// Builds a C#-based function for issuing claims for a user. |
| | 404 | | /// </summary> |
| | 405 | | /// <param name="settings">The authentication code settings containing the C# script.</param> |
| | 406 | | /// <param name="logger">The logger instance for logging.</param> |
| | 407 | | /// <returns>A function that issues claims using the provided C# script.</returns> |
| | 408 | | static Func<HttpContext, string, Task<IEnumerable<Claim>>> BuildCsIssueClaims(AuthenticationCodeSettings settings, S |
| | 409 | | { |
| 3 | 410 | | if (logger.IsEnabled(Serilog.Events.LogEventLevel.Debug)) |
| | 411 | | { |
| 3 | 412 | | logger.Debug("Compiling C# script for issuing claims."); |
| | 413 | | } |
| | 414 | |
|
| | 415 | | // Compile the C# script with the provided settings |
| 3 | 416 | | var script = CSharpDelegateBuilder.Compile(settings.Code, logger, |
| 3 | 417 | | settings.ExtraImports, settings.ExtraRefs, |
| 3 | 418 | | new Dictionary<string, object?> |
| 3 | 419 | | { |
| 3 | 420 | | { "identity", "" } |
| 3 | 421 | | }, languageVersion: settings.CSharpVersion); |
| | 422 | |
|
| 3 | 423 | | return async (ctx, identity) => |
| 3 | 424 | | { |
| 2 | 425 | | var krRequest = await KestrunRequest.NewRequest(ctx); |
| 2 | 426 | | var krResponse = new KestrunResponse(krRequest); |
| 2 | 427 | | var context = new KestrunContext(krRequest, krResponse, ctx); |
| 2 | 428 | | var globals = new CsGlobals(SharedStateStore.Snapshot(), context, new Dictionary<string, object?> |
| 2 | 429 | | { |
| 2 | 430 | | { "identity", identity } |
| 2 | 431 | | }); |
| 2 | 432 | | var result = await script.RunAsync(globals).ConfigureAwait(false); |
| 2 | 433 | | return result.ReturnValue is IEnumerable<Claim> claims |
| 2 | 434 | | ? claims |
| 2 | 435 | | : []; |
| 5 | 436 | | }; |
| | 437 | | } |
| | 438 | |
|
| | 439 | |
|
| | 440 | | /// <summary> |
| | 441 | | /// Builds a VB.NET-based function for issuing claims for a user. |
| | 442 | | /// </summary> |
| | 443 | | /// <param name="settings">The authentication code settings containing the VB.NET script.</param> |
| | 444 | | /// <param name="logger">The logger instance for logging.</param> |
| | 445 | | /// <returns>A function that issues claims using the provided VB.NET script.</returns> |
| | 446 | | static Func<HttpContext, string, Task<IEnumerable<Claim>>> BuildVBNetIssueClaims(AuthenticationCodeSettings settings |
| | 447 | | { |
| 3 | 448 | | if (logger.IsEnabled(Serilog.Events.LogEventLevel.Debug)) |
| | 449 | | { |
| 2 | 450 | | logger.Debug("Compiling VB.NET script for issuing claims."); |
| | 451 | | } |
| | 452 | |
|
| | 453 | | // Compile the VB.NET script with the provided settings |
| 3 | 454 | | var script = VBNetDelegateBuilder.Compile<IEnumerable<Claim>>(settings.Code, logger, |
| 3 | 455 | | settings.ExtraImports, settings.ExtraRefs, |
| 3 | 456 | | new Dictionary<string, object?> |
| 3 | 457 | | { |
| 3 | 458 | | { "identity", "" } |
| 3 | 459 | | }, languageVersion: settings.VisualBasicVersion); |
| | 460 | |
|
| 3 | 461 | | return async (ctx, identity) => |
| 3 | 462 | | { |
| 2 | 463 | | var krRequest = await KestrunRequest.NewRequest(ctx); |
| 2 | 464 | | var krResponse = new KestrunResponse(krRequest); |
| 2 | 465 | | var context = new KestrunContext(krRequest, krResponse, ctx); |
| 2 | 466 | | var glob = new CsGlobals(SharedStateStore.Snapshot(), context, new Dictionary<string, object?> |
| 2 | 467 | | { |
| 2 | 468 | | { "identity", identity } |
| 2 | 469 | | }); |
| 3 | 470 | | // Run the VB.NET script and get the result |
| 3 | 471 | | // Note: The script should return a boolean indicating success or failure |
| 2 | 472 | | var result = await script(glob).ConfigureAwait(false); |
| 2 | 473 | | return result is IEnumerable<Claim> claims |
| 2 | 474 | | ? claims |
| 2 | 475 | | : []; |
| 5 | 476 | | }; |
| | 477 | | } |
| | 478 | | } |