| | | 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="host">The Kestrun host instance.</param> |
| | | 172 | | /// <param name="settings">The authentication code settings.</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 | | KestrunHost host, |
| | | 177 | | AuthenticationCodeSettings settings, |
| | | 178 | | params (string Name, object? Prototype)[] globals) |
| | | 179 | | { |
| | 2 | 180 | | var log = host.Logger; |
| | 2 | 181 | | if (log.IsEnabled(Serilog.Events.LogEventLevel.Debug)) |
| | | 182 | | { |
| | 2 | 183 | | log.Debug("Building C# authentication script with globals: {Globals}", globals); |
| | | 184 | | } |
| | | 185 | | |
| | | 186 | | // Place-holders so Roslyn knows the globals that will exist |
| | 10 | 187 | | var stencil = globals.ToDictionary(n => n.Name, n => n.Prototype, |
| | 2 | 188 | | StringComparer.OrdinalIgnoreCase); |
| | 2 | 189 | | if (log.IsEnabled(Serilog.Events.LogEventLevel.Debug)) |
| | | 190 | | { |
| | 2 | 191 | | log.Debug("Compiling C# authentication script with variables: {Variables}", stencil); |
| | | 192 | | } |
| | | 193 | | |
| | 2 | 194 | | var script = CSharpDelegateBuilder.Compile( |
| | 2 | 195 | | settings.Code, |
| | 2 | 196 | | log, // already scoped by caller |
| | 2 | 197 | | settings.ExtraImports, |
| | 2 | 198 | | settings.ExtraRefs, |
| | 2 | 199 | | stencil, |
| | 2 | 200 | | languageVersion: settings.CSharpVersion); |
| | | 201 | | |
| | | 202 | | // Return the runtime delegate |
| | 2 | 203 | | return async (ctx, vars) => |
| | 2 | 204 | | { |
| | 4 | 205 | | if (log.IsEnabled(Serilog.Events.LogEventLevel.Debug)) |
| | 2 | 206 | | { |
| | 4 | 207 | | log.Debug("Running C# authentication script with variables: {Variables}", vars); |
| | 2 | 208 | | } |
| | 2 | 209 | | // --- Kestrun plumbing ------------------------------------------------- |
| | 4 | 210 | | var krReq = await KestrunRequest.NewRequest(ctx); |
| | 4 | 211 | | var krRes = new KestrunResponse(krReq); |
| | 4 | 212 | | var kCtx = new KestrunContext(host, krReq, krRes, ctx); |
| | 2 | 213 | | // --------------------------------------------------------------------- |
| | 4 | 214 | | var globalsDict = new Dictionary<string, object?>( |
| | 4 | 215 | | vars, StringComparer.OrdinalIgnoreCase); |
| | 2 | 216 | | // Merge shared state + user variables |
| | 4 | 217 | | var globals = new CsGlobals( |
| | 4 | 218 | | SharedStateStore.Snapshot(), |
| | 4 | 219 | | kCtx, |
| | 4 | 220 | | globalsDict); |
| | 2 | 221 | | |
| | 4 | 222 | | var result = await script.RunAsync(globals).ConfigureAwait(false); |
| | 4 | 223 | | return result.ReturnValue is true; |
| | 6 | 224 | | }; |
| | | 225 | | } |
| | | 226 | | |
| | | 227 | | internal static Func<HttpContext, IDictionary<string, object?>, Task<bool>> BuildVBNetValidator( |
| | | 228 | | KestrunHost host, |
| | | 229 | | AuthenticationCodeSettings settings, |
| | | 230 | | params (string Name, object? Prototype)[] globals) |
| | | 231 | | { |
| | 3 | 232 | | if (host is null) |
| | | 233 | | { |
| | 0 | 234 | | throw new ArgumentNullException(nameof(host), "KestrunHost cannot be null"); |
| | | 235 | | } |
| | 3 | 236 | | if (settings is null) |
| | | 237 | | { |
| | 0 | 238 | | throw new ArgumentNullException(nameof(settings), "AuthenticationCodeSettings cannot be null"); |
| | | 239 | | } |
| | 3 | 240 | | var log = host.Logger; |
| | | 241 | | // Place-holders so Roslyn knows the globals that will exist |
| | 15 | 242 | | var stencil = globals.ToDictionary(n => n.Name, n => n.Prototype, |
| | 3 | 243 | | StringComparer.OrdinalIgnoreCase); |
| | | 244 | | |
| | 3 | 245 | | if (log.IsEnabled(Serilog.Events.LogEventLevel.Debug)) |
| | | 246 | | { |
| | 2 | 247 | | log.Debug("Compiling VB.NET authentication script with variables: {Variables}", stencil); |
| | | 248 | | } |
| | | 249 | | |
| | | 250 | | // Compile the VB.NET script with the provided settings |
| | 3 | 251 | | var script = VBNetDelegateBuilder.Compile<bool>( |
| | 3 | 252 | | settings.Code, |
| | 3 | 253 | | log, // already scoped by caller |
| | 3 | 254 | | settings.ExtraImports, |
| | 3 | 255 | | settings.ExtraRefs, |
| | 3 | 256 | | stencil, |
| | 3 | 257 | | languageVersion: settings.VisualBasicVersion); |
| | | 258 | | |
| | | 259 | | // Return the runtime delegate |
| | 3 | 260 | | return async (ctx, vars) => |
| | 3 | 261 | | { |
| | 4 | 262 | | if (log.IsEnabled(Serilog.Events.LogEventLevel.Debug)) |
| | 3 | 263 | | { |
| | 4 | 264 | | log.Debug("Running VB.NET authentication script with variables: {Variables}", vars); |
| | 3 | 265 | | } |
| | 3 | 266 | | |
| | 3 | 267 | | // --- Kestrun plumbing ------------------------------------------------- |
| | 4 | 268 | | var krReq = await KestrunRequest.NewRequest(ctx); |
| | 4 | 269 | | var krRes = new KestrunResponse(krReq); |
| | 4 | 270 | | var kCtx = new KestrunContext(host, krReq, krRes, ctx); |
| | 3 | 271 | | // --------------------------------------------------------------------- |
| | 3 | 272 | | |
| | 3 | 273 | | // Merge shared state + user variables |
| | 4 | 274 | | var globals = new CsGlobals( |
| | 4 | 275 | | SharedStateStore.Snapshot(), |
| | 4 | 276 | | kCtx, |
| | 4 | 277 | | new Dictionary<string, object?>(vars, StringComparer.OrdinalIgnoreCase)); |
| | 3 | 278 | | |
| | 4 | 279 | | var result = await script(globals).ConfigureAwait(false); |
| | 3 | 280 | | |
| | 4 | 281 | | return result is bool isValid && isValid; |
| | 7 | 282 | | }; |
| | | 283 | | } |
| | | 284 | | |
| | | 285 | | |
| | | 286 | | /// <summary> |
| | | 287 | | /// Builds a PowerShell-based function for issuing claims for a user. |
| | | 288 | | /// </summary> |
| | | 289 | | /// <param name="host">The Kestrun host instance.</param> |
| | | 290 | | /// <param name="settings">The authentication code settings containing the PowerShell script.</param> |
| | | 291 | | /// <returns>A function that issues claims using the provided PowerShell script.</returns> |
| | | 292 | | static Func<HttpContext, string, Task<IEnumerable<Claim>>> BuildPsIssueClaims( |
| | | 293 | | KestrunHost host, |
| | | 294 | | AuthenticationCodeSettings settings) => |
| | 2 | 295 | | async (ctx, identity) => |
| | 2 | 296 | | { |
| | 2 | 297 | | return await IssueClaimsPowerShellAsync(settings.Code, ctx, identity, host.Logger); |
| | 4 | 298 | | }; |
| | | 299 | | |
| | | 300 | | /// <summary> |
| | | 301 | | /// Issues claims for a user by executing a PowerShell script. |
| | | 302 | | /// </summary> |
| | | 303 | | /// <param name="code">The PowerShell script code used to issue claims.</param> |
| | | 304 | | /// <param name="ctx">The HTTP context containing the PowerShell runspace.</param> |
| | | 305 | | /// <param name="identity">The username for which to issue claims.</param> |
| | | 306 | | /// <param name="logger">The logger instance for logging.</param> |
| | | 307 | | /// <returns>A task representing the asynchronous operation, with a collection of issued claims.</returns> |
| | | 308 | | static async Task<IEnumerable<Claim>> IssueClaimsPowerShellAsync(string? code, HttpContext ctx, string identity, Ser |
| | | 309 | | { |
| | 3 | 310 | | if (string.IsNullOrWhiteSpace(identity)) |
| | | 311 | | { |
| | 1 | 312 | | logger.Warning("Identity is null or empty."); |
| | 1 | 313 | | return []; |
| | | 314 | | } |
| | 2 | 315 | | if (string.IsNullOrEmpty(code)) |
| | | 316 | | { |
| | 0 | 317 | | throw new InvalidOperationException("PowerShell authentication code is null or empty."); |
| | | 318 | | } |
| | | 319 | | |
| | | 320 | | try |
| | | 321 | | { |
| | 2 | 322 | | var ps = GetPowerShell(ctx); |
| | 2 | 323 | | _ = ps.AddScript(code, useLocalScope: true).AddParameter("identity", identity); |
| | | 324 | | |
| | 2 | 325 | | var psResults = await ps.InvokeAsync().ConfigureAwait(false); |
| | 2 | 326 | | if (psResults is null || psResults.Count == 0) |
| | | 327 | | { |
| | 0 | 328 | | return []; |
| | | 329 | | } |
| | | 330 | | |
| | 2 | 331 | | var claims = new List<Claim>(psResults.Count); |
| | 8 | 332 | | foreach (var r in psResults) |
| | | 333 | | { |
| | 2 | 334 | | if (TryToClaim(r?.BaseObject, out var claim)) |
| | | 335 | | { |
| | 2 | 336 | | claims.Add(claim); |
| | | 337 | | } |
| | | 338 | | else |
| | | 339 | | { |
| | 0 | 340 | | logger.Warning("PowerShell script returned an unsupported type: {Type}", r?.BaseObject?.GetType()); |
| | 0 | 341 | | throw new InvalidOperationException("PowerShell script returned an unsupported type."); |
| | | 342 | | } |
| | | 343 | | } |
| | | 344 | | |
| | 2 | 345 | | return claims; |
| | | 346 | | } |
| | 0 | 347 | | catch (Exception ex) |
| | | 348 | | { |
| | 0 | 349 | | logger.Error(ex, "Error during Issue Claims for {Identity}", identity); |
| | 0 | 350 | | return []; |
| | | 351 | | } |
| | 3 | 352 | | } |
| | | 353 | | |
| | | 354 | | /// <summary> |
| | | 355 | | /// Retrieves the PowerShell instance from the HTTP context. |
| | | 356 | | /// </summary> |
| | | 357 | | /// <param name="ctx">The HTTP context containing the PowerShell runspace.</param> |
| | | 358 | | /// <returns>The PowerShell instance associated with the context.</returns> |
| | | 359 | | /// <exception cref="InvalidOperationException">Thrown when the PowerShell runspace is not found.</exception> |
| | | 360 | | private static PowerShell GetPowerShell(HttpContext ctx) |
| | | 361 | | { |
| | 4 | 362 | | return !ctx.Items.TryGetValue("PS_INSTANCE", out var psObj) || psObj is not PowerShell ps || ps.Runspace == null |
| | 4 | 363 | | ? throw new InvalidOperationException("PowerShell runspace not found or not set in context items. Ensure Pow |
| | 4 | 364 | | : ps; |
| | | 365 | | } |
| | | 366 | | |
| | | 367 | | /// <summary> |
| | | 368 | | /// Tries to create a Claim from the provided object. |
| | | 369 | | /// </summary> |
| | | 370 | | /// <param name="obj">The object to create a Claim from.</param> |
| | | 371 | | /// <param name="claim">The created Claim, if successful.</param> |
| | | 372 | | /// <returns>True if the Claim was created successfully; otherwise, false.</returns> |
| | | 373 | | private static bool TryToClaim(object? obj, out Claim claim) |
| | | 374 | | { |
| | 6 | 375 | | switch (obj) |
| | | 376 | | { |
| | | 377 | | case Claim c: |
| | 1 | 378 | | claim = c; |
| | 1 | 379 | | return true; |
| | | 380 | | |
| | 3 | 381 | | case IDictionary dict when dict.Contains("Type") && dict.Contains("Value"): |
| | 3 | 382 | | var typeStr = dict["Type"]?.ToString(); |
| | 3 | 383 | | var valueStr = dict["Value"]?.ToString(); |
| | 3 | 384 | | if (!string.IsNullOrEmpty(typeStr) && !string.IsNullOrEmpty(valueStr)) |
| | | 385 | | { |
| | 3 | 386 | | claim = new Claim(typeStr, valueStr); |
| | 3 | 387 | | return true; |
| | | 388 | | } |
| | | 389 | | break; |
| | | 390 | | |
| | 1 | 391 | | case string s when s.Contains(':'): |
| | 1 | 392 | | var idx = s.IndexOf(':'); |
| | 1 | 393 | | if (idx >= 0 && idx < s.Length - 1) |
| | | 394 | | { |
| | 1 | 395 | | claim = new Claim(s[..idx], s[(idx + 1)..]); |
| | 1 | 396 | | return true; |
| | | 397 | | } |
| | | 398 | | break; |
| | | 399 | | default: |
| | | 400 | | // Unsupported type |
| | | 401 | | break; |
| | | 402 | | } |
| | | 403 | | |
| | 1 | 404 | | claim = default!; |
| | 1 | 405 | | return false; |
| | | 406 | | } |
| | | 407 | | |
| | | 408 | | |
| | | 409 | | /// <summary> |
| | | 410 | | /// Builds a C#-based function for issuing claims for a user. |
| | | 411 | | /// </summary> |
| | | 412 | | /// <param name="host">The Kestrun host instance.</param> |
| | | 413 | | /// <param name="settings">The authentication code settings containing the C# script.</param> |
| | | 414 | | /// <returns>A function that issues claims using the provided C# script.</returns> |
| | | 415 | | static Func<HttpContext, string, Task<IEnumerable<Claim>>> BuildCsIssueClaims( |
| | | 416 | | KestrunHost host, |
| | | 417 | | AuthenticationCodeSettings settings) |
| | | 418 | | { |
| | 3 | 419 | | if (host is null) |
| | | 420 | | { |
| | 0 | 421 | | throw new ArgumentNullException(nameof(host), "KestrunHost cannot be null"); |
| | | 422 | | } |
| | 3 | 423 | | var logger = host.Logger; |
| | 3 | 424 | | if (logger.IsEnabled(Serilog.Events.LogEventLevel.Debug)) |
| | | 425 | | { |
| | 3 | 426 | | logger.Debug("Compiling C# script for issuing claims."); |
| | | 427 | | } |
| | | 428 | | |
| | | 429 | | // Compile the C# script with the provided settings |
| | 3 | 430 | | var script = CSharpDelegateBuilder.Compile(settings.Code, logger, |
| | 3 | 431 | | settings.ExtraImports, settings.ExtraRefs, |
| | 3 | 432 | | new Dictionary<string, object?> |
| | 3 | 433 | | { |
| | 3 | 434 | | { "identity", "" } |
| | 3 | 435 | | }, languageVersion: settings.CSharpVersion); |
| | | 436 | | |
| | 3 | 437 | | return async (ctx, identity) => |
| | 3 | 438 | | { |
| | 2 | 439 | | var krRequest = await KestrunRequest.NewRequest(ctx); |
| | 2 | 440 | | var krResponse = new KestrunResponse(krRequest); |
| | 2 | 441 | | var context = new KestrunContext(host, krRequest, krResponse, ctx); |
| | 2 | 442 | | var globals = new CsGlobals(SharedStateStore.Snapshot(), context, new Dictionary<string, object?> |
| | 2 | 443 | | { |
| | 2 | 444 | | { "identity", identity } |
| | 2 | 445 | | }); |
| | 2 | 446 | | var result = await script.RunAsync(globals).ConfigureAwait(false); |
| | 2 | 447 | | return result.ReturnValue is IEnumerable<Claim> claims |
| | 2 | 448 | | ? claims |
| | 2 | 449 | | : []; |
| | 5 | 450 | | }; |
| | | 451 | | } |
| | | 452 | | |
| | | 453 | | |
| | | 454 | | /// <summary> |
| | | 455 | | /// Builds a VB.NET-based function for issuing claims for a user. |
| | | 456 | | /// </summary> |
| | | 457 | | /// <param name="host">The Kestrun host instance.</param> |
| | | 458 | | /// <param name="settings">The authentication code settings containing the VB.NET script.</param> |
| | | 459 | | /// <returns>A function that issues claims using the provided VB.NET script.</returns> |
| | | 460 | | static Func<HttpContext, string, Task<IEnumerable<Claim>>> BuildVBNetIssueClaims( |
| | | 461 | | KestrunHost host, |
| | | 462 | | AuthenticationCodeSettings settings) |
| | | 463 | | { |
| | 3 | 464 | | if (host is null) |
| | | 465 | | { |
| | 0 | 466 | | throw new ArgumentNullException(nameof(host), "KestrunHost cannot be null"); |
| | | 467 | | } |
| | 3 | 468 | | var logger = host.Logger; |
| | 3 | 469 | | if (logger.IsEnabled(Serilog.Events.LogEventLevel.Debug)) |
| | | 470 | | { |
| | 2 | 471 | | logger.Debug("Compiling VB.NET script for issuing claims."); |
| | | 472 | | } |
| | | 473 | | |
| | | 474 | | // Compile the VB.NET script with the provided settings |
| | 3 | 475 | | var script = VBNetDelegateBuilder.Compile<IEnumerable<Claim>>(settings.Code, logger, |
| | 3 | 476 | | settings.ExtraImports, settings.ExtraRefs, |
| | 3 | 477 | | new Dictionary<string, object?> |
| | 3 | 478 | | { |
| | 3 | 479 | | { "identity", "" } |
| | 3 | 480 | | }, languageVersion: settings.VisualBasicVersion); |
| | | 481 | | |
| | 3 | 482 | | return async (ctx, identity) => |
| | 3 | 483 | | { |
| | 2 | 484 | | var krRequest = await KestrunRequest.NewRequest(ctx); |
| | 2 | 485 | | var krResponse = new KestrunResponse(krRequest); |
| | 2 | 486 | | var context = new KestrunContext(host, krRequest, krResponse, ctx); |
| | 2 | 487 | | var glob = new CsGlobals(SharedStateStore.Snapshot(), context, new Dictionary<string, object?> |
| | 2 | 488 | | { |
| | 2 | 489 | | { "identity", identity } |
| | 2 | 490 | | }); |
| | 3 | 491 | | // Run the VB.NET script and get the result |
| | 3 | 492 | | // Note: The script should return a boolean indicating success or failure |
| | 2 | 493 | | var result = await script(glob).ConfigureAwait(false); |
| | 2 | 494 | | return result is IEnumerable<Claim> claims |
| | 2 | 495 | | ? claims |
| | 2 | 496 | | : []; |
| | 5 | 497 | | }; |
| | | 498 | | } |
| | | 499 | | } |