| File: OtlpApiKeyAuthenticationHandlerTests.cs | Web Access |
| Project: src\tests\Aspire.Dashboard.Tests\Aspire.Dashboard.Tests.csproj (Aspire.Dashboard.Tests) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Text.Encodings.Web; using Aspire.Dashboard.Authentication.OtlpApiKey; using Aspire.Dashboard.Configuration; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.InternalTesting; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Xunit; namespace Aspire.Dashboard.Tests; public class OtlpApiKeyAuthenticationHandlerTests { [Fact] public async Task AuthenticateAsync_NoHeader_Failure() { // Arrange var handler = await CreateAuthHandlerAsync(primaryApiKey: "abc", secondaryApiKey: null, otlpApiKeyHeader: null).DefaultTimeout(); // Act var result = await handler.AuthenticateAsync().DefaultTimeout(); // Assert Assert.NotNull(result.Failure); Assert.Equal($"API key from '{OtlpApiKeyAuthenticationHandler.ApiKeyHeaderName}' header is missing.", result.Failure.Message); } [Fact] public async Task AuthenticateAsync_BigApiKeys_NoMatch_Failure() { // Arrange var handler = await CreateAuthHandlerAsync(primaryApiKey: new string('!', 1000), secondaryApiKey: null, otlpApiKeyHeader: new string('!', 999)).DefaultTimeout(); // Act var result = await handler.AuthenticateAsync().DefaultTimeout(); // Assert Assert.NotNull(result.Failure); Assert.Equal($"Incoming API key from '{OtlpApiKeyAuthenticationHandler.ApiKeyHeaderName}' header doesn't match configured API key.", result.Failure.Message); } [Fact] public async Task AuthenticateAsync_BigApiKeys_Match_Success() { // Arrange var handler = await CreateAuthHandlerAsync(primaryApiKey: new string('!', 1000), secondaryApiKey: null, otlpApiKeyHeader: new string('!', 1000)).DefaultTimeout(); // Act var result = await handler.AuthenticateAsync().DefaultTimeout(); // Assert Assert.Null(result.Failure); } [Theory] [InlineData("abc", null, "abc", true)] [InlineData("abcd", null, "abc", false)] [InlineData("abc", null, "abcd", false)] [InlineData("abc", "abcd", "abcd", true)] public async Task AuthenticateAsync_MatchHeader_Success(string primaryApiKey, string? secondaryApiKey, string otlpApiKeyHeader, bool success) { // Arrange var handler = await CreateAuthHandlerAsync(primaryApiKey, secondaryApiKey, otlpApiKeyHeader).DefaultTimeout(); // Act var result = await handler.AuthenticateAsync().DefaultTimeout(); // Assert Assert.Equal(success, result.Failure == null); } private static async Task<OtlpApiKeyAuthenticationHandler> CreateAuthHandlerAsync(string primaryApiKey, string? secondaryApiKey, string? otlpApiKeyHeader) { var options = new DashboardOptions { Otlp = { GrpcEndpointUrl = "http://localhost", PrimaryApiKey = primaryApiKey, SecondaryApiKey = secondaryApiKey } }; Assert.True(options.Otlp.TryParseOptions(out _)); var handler = new OtlpApiKeyAuthenticationHandler( new TestOptionsMonitor<DashboardOptions>(options), new TestOptionsMonitor<OtlpApiKeyAuthenticationHandlerOptions>(new OtlpApiKeyAuthenticationHandlerOptions()), NullLoggerFactory.Instance, UrlEncoder.Default); var httpContext = new DefaultHttpContext(); if (otlpApiKeyHeader != null) { httpContext.Request.Headers[OtlpApiKeyAuthenticationHandler.ApiKeyHeaderName] = otlpApiKeyHeader; } await handler.InitializeAsync(new AuthenticationScheme("Test", "Test", handler.GetType()), httpContext); return handler; } private sealed class TestOptionsMonitor<T> : IOptionsMonitor<T> { public TestOptionsMonitor(T options) => CurrentValue = options; public T CurrentValue { get; } public T Get(string? name) => CurrentValue; public IDisposable OnChange(Action<T, string> listener) => throw new NotImplementedException(); public IDisposable OnChange(Action<T> listener) => throw new NotImplementedException(); } }