|
|
@@ -0,0 +1,356 @@
|
|
|
+// Licensed to the .NET Foundation under one or more agreements.
|
|
|
+// The .NET Foundation licenses this file to you under the MIT license.
|
|
|
+
|
|
|
+using System.Security.Claims;
|
|
|
+using Microsoft.AspNetCore.Http;
|
|
|
+using Microsoft.AspNetCore.InternalTesting;
|
|
|
+using Microsoft.Extensions.Options;
|
|
|
+using Microsoft.Extensions.Diagnostics.Metrics.Testing;
|
|
|
+using Moq;
|
|
|
+
|
|
|
+namespace Microsoft.AspNetCore.Authentication;
|
|
|
+
|
|
|
+public class AuthenticationMetricsTest
|
|
|
+{
|
|
|
+ [Fact]
|
|
|
+ public async Task Authenticate_Success()
|
|
|
+ {
|
|
|
+ // Arrange
|
|
|
+ var authenticationHandler = new Mock<IAuthenticationHandler>();
|
|
|
+ authenticationHandler.Setup(h => h.AuthenticateAsync()).Returns(Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(), "custom"))));
|
|
|
+
|
|
|
+ var meterFactory = new TestMeterFactory();
|
|
|
+ var httpContext = new DefaultHttpContext();
|
|
|
+ var authenticationService = CreateAuthenticationService(authenticationHandler.Object, meterFactory);
|
|
|
+ var meter = meterFactory.Meters.Single();
|
|
|
+
|
|
|
+ using var authenticationRequestsCollector = new MetricCollector<double>(meterFactory, AuthenticationMetrics.MeterName, "aspnetcore.authentication.authenticate.duration");
|
|
|
+
|
|
|
+ // Act
|
|
|
+ await authenticationService.AuthenticateAsync(httpContext, scheme: "custom");
|
|
|
+
|
|
|
+ // Assert
|
|
|
+ Assert.Equal(AuthenticationMetrics.MeterName, meter.Name);
|
|
|
+ Assert.Null(meter.Version);
|
|
|
+
|
|
|
+ var measurement = Assert.Single(authenticationRequestsCollector.GetMeasurementSnapshot());
|
|
|
+ Assert.True(measurement.Value > 0);
|
|
|
+ Assert.Equal("custom", (string)measurement.Tags["aspnetcore.authentication.scheme"]);
|
|
|
+ Assert.Equal("success", (string)measurement.Tags["aspnetcore.authentication.result"]);
|
|
|
+ Assert.False(measurement.Tags.ContainsKey("error.type"));
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public async Task Authenticate_Failure()
|
|
|
+ {
|
|
|
+ // Arrange
|
|
|
+ var authenticationHandler = new Mock<IAuthenticationHandler>();
|
|
|
+ authenticationHandler.Setup(h => h.AuthenticateAsync()).Returns(Task.FromResult(AuthenticateResult.Fail("Authentication failed")));
|
|
|
+
|
|
|
+ var meterFactory = new TestMeterFactory();
|
|
|
+ var httpContext = new DefaultHttpContext();
|
|
|
+ var authenticationService = CreateAuthenticationService(authenticationHandler.Object, meterFactory);
|
|
|
+ var meter = meterFactory.Meters.Single();
|
|
|
+
|
|
|
+ using var authenticationRequestsCollector = new MetricCollector<double>(meterFactory, AuthenticationMetrics.MeterName, "aspnetcore.authentication.authenticate.duration");
|
|
|
+
|
|
|
+ // Act
|
|
|
+ await authenticationService.AuthenticateAsync(httpContext, scheme: "custom");
|
|
|
+
|
|
|
+ // Assert
|
|
|
+ Assert.Equal(AuthenticationMetrics.MeterName, meter.Name);
|
|
|
+ Assert.Null(meter.Version);
|
|
|
+
|
|
|
+ var measurement = Assert.Single(authenticationRequestsCollector.GetMeasurementSnapshot());
|
|
|
+ Assert.True(measurement.Value > 0);
|
|
|
+ Assert.Equal("custom", (string)measurement.Tags["aspnetcore.authentication.scheme"]);
|
|
|
+ Assert.Equal("failure", (string)measurement.Tags["aspnetcore.authentication.result"]);
|
|
|
+ Assert.Equal("Microsoft.AspNetCore.Authentication.AuthenticationFailureException", (string)measurement.Tags["error.type"]);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public async Task Authenticate_NoResult()
|
|
|
+ {
|
|
|
+ // Arrange
|
|
|
+ var authenticationHandler = new Mock<IAuthenticationHandler>();
|
|
|
+ authenticationHandler.Setup(h => h.AuthenticateAsync()).Returns(Task.FromResult(AuthenticateResult.NoResult()));
|
|
|
+
|
|
|
+ var meterFactory = new TestMeterFactory();
|
|
|
+ var httpContext = new DefaultHttpContext();
|
|
|
+ var authenticationService = CreateAuthenticationService(authenticationHandler.Object, meterFactory);
|
|
|
+ var meter = meterFactory.Meters.Single();
|
|
|
+
|
|
|
+ using var authenticationRequestsCollector = new MetricCollector<double>(meterFactory, AuthenticationMetrics.MeterName, "aspnetcore.authentication.authenticate.duration");
|
|
|
+
|
|
|
+ // Act
|
|
|
+ await authenticationService.AuthenticateAsync(httpContext, scheme: "custom");
|
|
|
+
|
|
|
+ // Assert
|
|
|
+ Assert.Equal(AuthenticationMetrics.MeterName, meter.Name);
|
|
|
+ Assert.Null(meter.Version);
|
|
|
+
|
|
|
+ var measurement = Assert.Single(authenticationRequestsCollector.GetMeasurementSnapshot());
|
|
|
+ Assert.True(measurement.Value > 0);
|
|
|
+ Assert.Equal("custom", (string)measurement.Tags["aspnetcore.authentication.scheme"]);
|
|
|
+ Assert.Equal("none", (string)measurement.Tags["aspnetcore.authentication.result"]);
|
|
|
+ Assert.False(measurement.Tags.ContainsKey("error.type"));
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public async Task Authenticate_ExceptionThrownInHandler()
|
|
|
+ {
|
|
|
+ // Arrange
|
|
|
+ var authenticationHandler = new Mock<IAuthenticationHandler>();
|
|
|
+ authenticationHandler.Setup(h => h.AuthenticateAsync()).Throws(new InvalidOperationException("An error occurred during authentication"));
|
|
|
+
|
|
|
+ var meterFactory = new TestMeterFactory();
|
|
|
+ var httpContext = new DefaultHttpContext();
|
|
|
+ var authenticationService = CreateAuthenticationService(authenticationHandler.Object, meterFactory);
|
|
|
+ var meter = meterFactory.Meters.Single();
|
|
|
+
|
|
|
+ using var authenticationRequestsCollector = new MetricCollector<double>(meterFactory, AuthenticationMetrics.MeterName, "aspnetcore.authentication.authenticate.duration");
|
|
|
+
|
|
|
+ // Act
|
|
|
+ var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => authenticationService.AuthenticateAsync(httpContext, scheme: "custom"));
|
|
|
+
|
|
|
+ // Assert
|
|
|
+ Assert.Equal("An error occurred during authentication", ex.Message);
|
|
|
+ Assert.Equal(AuthenticationMetrics.MeterName, meter.Name);
|
|
|
+ Assert.Null(meter.Version);
|
|
|
+
|
|
|
+ var measurement = Assert.Single(authenticationRequestsCollector.GetMeasurementSnapshot());
|
|
|
+ Assert.True(measurement.Value > 0);
|
|
|
+ Assert.Equal("custom", (string)measurement.Tags["aspnetcore.authentication.scheme"]);
|
|
|
+ Assert.Equal("System.InvalidOperationException", (string)measurement.Tags["error.type"]);
|
|
|
+ Assert.False(measurement.Tags.ContainsKey("aspnetcore.authentication.result"));
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public async Task Challenge()
|
|
|
+ {
|
|
|
+ // Arrange
|
|
|
+ var meterFactory = new TestMeterFactory();
|
|
|
+ var httpContext = new DefaultHttpContext();
|
|
|
+ var authenticationService = CreateAuthenticationService(Mock.Of<IAuthenticationHandler>(), meterFactory);
|
|
|
+ var meter = meterFactory.Meters.Single();
|
|
|
+
|
|
|
+ using var challengesCollector = new MetricCollector<long>(meterFactory, AuthenticationMetrics.MeterName, "aspnetcore.authentication.challenges");
|
|
|
+
|
|
|
+ // Act
|
|
|
+ await authenticationService.ChallengeAsync(httpContext, scheme: "custom", properties: null);
|
|
|
+
|
|
|
+ // Assert
|
|
|
+ Assert.Equal(AuthenticationMetrics.MeterName, meter.Name);
|
|
|
+ Assert.Null(meter.Version);
|
|
|
+
|
|
|
+ var measurement = Assert.Single(challengesCollector.GetMeasurementSnapshot());
|
|
|
+ Assert.Equal(1, measurement.Value);
|
|
|
+ Assert.Equal("custom", (string)measurement.Tags["aspnetcore.authentication.scheme"]);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public async Task Challenge_ExceptionThrownInHandler()
|
|
|
+ {
|
|
|
+ // Arrange
|
|
|
+ var authenticationHandler = new Mock<IAuthenticationHandler>();
|
|
|
+ authenticationHandler.Setup(h => h.ChallengeAsync(It.IsAny<AuthenticationProperties>())).Throws(new InvalidOperationException("An error occurred during challenge"));
|
|
|
+
|
|
|
+ var meterFactory = new TestMeterFactory();
|
|
|
+ var httpContext = new DefaultHttpContext();
|
|
|
+ var authenticationService = CreateAuthenticationService(authenticationHandler.Object, meterFactory);
|
|
|
+ var meter = meterFactory.Meters.Single();
|
|
|
+
|
|
|
+ using var challengesCollector = new MetricCollector<long>(meterFactory, AuthenticationMetrics.MeterName, "aspnetcore.authentication.challenges");
|
|
|
+
|
|
|
+ // Act
|
|
|
+ var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => authenticationService.ChallengeAsync(httpContext, scheme: "custom", properties: null));
|
|
|
+
|
|
|
+ // Assert
|
|
|
+ Assert.Equal("An error occurred during challenge", ex.Message);
|
|
|
+ Assert.Equal(AuthenticationMetrics.MeterName, meter.Name);
|
|
|
+ Assert.Null(meter.Version);
|
|
|
+
|
|
|
+ var measurement = Assert.Single(challengesCollector.GetMeasurementSnapshot());
|
|
|
+ Assert.Equal(1, measurement.Value);
|
|
|
+ Assert.Equal("custom", (string)measurement.Tags["aspnetcore.authentication.scheme"]);
|
|
|
+ Assert.Equal("System.InvalidOperationException", (string)measurement.Tags["error.type"]);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public async Task Forbid()
|
|
|
+ {
|
|
|
+ // Arrange
|
|
|
+ var meterFactory = new TestMeterFactory();
|
|
|
+ var httpContext = new DefaultHttpContext();
|
|
|
+ var authenticationService = CreateAuthenticationService(Mock.Of<IAuthenticationHandler>(), meterFactory);
|
|
|
+ var meter = meterFactory.Meters.Single();
|
|
|
+
|
|
|
+ using var forbidsCollector = new MetricCollector<long>(meterFactory, AuthenticationMetrics.MeterName, "aspnetcore.authentication.forbids");
|
|
|
+
|
|
|
+ // Act
|
|
|
+ await authenticationService.ForbidAsync(httpContext, scheme: "custom", properties: null);
|
|
|
+
|
|
|
+ // Assert
|
|
|
+ Assert.Equal(AuthenticationMetrics.MeterName, meter.Name);
|
|
|
+ Assert.Null(meter.Version);
|
|
|
+
|
|
|
+ var measurement = Assert.Single(forbidsCollector.GetMeasurementSnapshot());
|
|
|
+ Assert.Equal(1, measurement.Value);
|
|
|
+ Assert.Equal("custom", (string)measurement.Tags["aspnetcore.authentication.scheme"]);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public async Task Forbid_ExceptionThrownInHandler()
|
|
|
+ {
|
|
|
+ // Arrange
|
|
|
+ var authenticationHandler = new Mock<IAuthenticationHandler>();
|
|
|
+ authenticationHandler.Setup(h => h.ForbidAsync(It.IsAny<AuthenticationProperties>())).Throws(new InvalidOperationException("An error occurred during forbid"));
|
|
|
+
|
|
|
+ var meterFactory = new TestMeterFactory();
|
|
|
+ var httpContext = new DefaultHttpContext();
|
|
|
+ var authenticationService = CreateAuthenticationService(authenticationHandler.Object, meterFactory);
|
|
|
+ var meter = meterFactory.Meters.Single();
|
|
|
+
|
|
|
+ using var forbidsCollector = new MetricCollector<long>(meterFactory, AuthenticationMetrics.MeterName, "aspnetcore.authentication.forbids");
|
|
|
+
|
|
|
+ // Act
|
|
|
+ var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => authenticationService.ForbidAsync(httpContext, scheme: "custom", properties: null));
|
|
|
+
|
|
|
+ // Assert
|
|
|
+ Assert.Equal("An error occurred during forbid", ex.Message);
|
|
|
+ Assert.Equal(AuthenticationMetrics.MeterName, meter.Name);
|
|
|
+ Assert.Null(meter.Version);
|
|
|
+
|
|
|
+ var measurement = Assert.Single(forbidsCollector.GetMeasurementSnapshot());
|
|
|
+ Assert.Equal(1, measurement.Value);
|
|
|
+ Assert.Equal("custom", (string)measurement.Tags["aspnetcore.authentication.scheme"]);
|
|
|
+ Assert.Equal("System.InvalidOperationException", (string)measurement.Tags["error.type"]);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public async Task SignIn()
|
|
|
+ {
|
|
|
+ // Arrange
|
|
|
+ var meterFactory = new TestMeterFactory();
|
|
|
+ var httpContext = new DefaultHttpContext();
|
|
|
+ var authenticationService = CreateAuthenticationService(Mock.Of<IAuthenticationSignInHandler>(), meterFactory);
|
|
|
+ var meter = meterFactory.Meters.Single();
|
|
|
+
|
|
|
+ using var signInsCollector = new MetricCollector<long>(meterFactory, AuthenticationMetrics.MeterName, "aspnetcore.authentication.sign_ins");
|
|
|
+
|
|
|
+ // Act
|
|
|
+ await authenticationService.SignInAsync(httpContext, scheme: "custom", new ClaimsPrincipal(), properties: null);
|
|
|
+
|
|
|
+ // Assert
|
|
|
+ Assert.Equal(AuthenticationMetrics.MeterName, meter.Name);
|
|
|
+ Assert.Null(meter.Version);
|
|
|
+
|
|
|
+ var measurement = Assert.Single(signInsCollector.GetMeasurementSnapshot());
|
|
|
+ Assert.Equal(1, measurement.Value);
|
|
|
+ Assert.Equal("custom", (string)measurement.Tags["aspnetcore.authentication.scheme"]);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public async Task SignIn_ExceptionThrownInHandler()
|
|
|
+ {
|
|
|
+ // Arrange
|
|
|
+ var authenticationHandler = new Mock<IAuthenticationSignInHandler>();
|
|
|
+ authenticationHandler.Setup(h => h.SignInAsync(It.IsAny<ClaimsPrincipal>(), It.IsAny<AuthenticationProperties>())).Throws(new InvalidOperationException("An error occurred during sign in"));
|
|
|
+
|
|
|
+ var meterFactory = new TestMeterFactory();
|
|
|
+ var httpContext = new DefaultHttpContext();
|
|
|
+ var authenticationService = CreateAuthenticationService(authenticationHandler.Object, meterFactory);
|
|
|
+ var meter = meterFactory.Meters.Single();
|
|
|
+
|
|
|
+ using var signInsCollector = new MetricCollector<long>(meterFactory, AuthenticationMetrics.MeterName, "aspnetcore.authentication.sign_ins");
|
|
|
+
|
|
|
+ // Act
|
|
|
+ var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => authenticationService.SignInAsync(httpContext, scheme: "custom", new ClaimsPrincipal(), properties: null));
|
|
|
+
|
|
|
+ // Assert
|
|
|
+ Assert.Equal("An error occurred during sign in", ex.Message);
|
|
|
+ Assert.Equal(AuthenticationMetrics.MeterName, meter.Name);
|
|
|
+ Assert.Null(meter.Version);
|
|
|
+
|
|
|
+ var measurement = Assert.Single(signInsCollector.GetMeasurementSnapshot());
|
|
|
+ Assert.Equal(1, measurement.Value);
|
|
|
+ Assert.Equal("custom", (string)measurement.Tags["aspnetcore.authentication.scheme"]);
|
|
|
+ Assert.Equal("System.InvalidOperationException", (string)measurement.Tags["error.type"]);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public async Task SignOut()
|
|
|
+ {
|
|
|
+ // Arrange
|
|
|
+ var httpContext = new DefaultHttpContext();
|
|
|
+ var meterFactory = new TestMeterFactory();
|
|
|
+ var authenticationService = CreateAuthenticationService(Mock.Of<IAuthenticationSignOutHandler>(), meterFactory);
|
|
|
+ var meter = meterFactory.Meters.Single();
|
|
|
+
|
|
|
+ using var signOutsCollector = new MetricCollector<long>(meterFactory, AuthenticationMetrics.MeterName, "aspnetcore.authentication.sign_outs");
|
|
|
+
|
|
|
+ // Act
|
|
|
+ await authenticationService.SignOutAsync(httpContext, scheme: "custom", properties: null);
|
|
|
+
|
|
|
+ // Assert
|
|
|
+ Assert.Equal(AuthenticationMetrics.MeterName, meter.Name);
|
|
|
+ Assert.Null(meter.Version);
|
|
|
+
|
|
|
+ var measurement = Assert.Single(signOutsCollector.GetMeasurementSnapshot());
|
|
|
+ Assert.Equal(1, measurement.Value);
|
|
|
+ Assert.Equal("custom", (string)measurement.Tags["aspnetcore.authentication.scheme"]);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public async Task SignOut_ExceptionThrownInHandler()
|
|
|
+ {
|
|
|
+ // Arrange
|
|
|
+ var authenticationHandler = new Mock<IAuthenticationSignOutHandler>();
|
|
|
+ authenticationHandler.Setup(h => h.SignOutAsync(It.IsAny<AuthenticationProperties>())).Throws(new InvalidOperationException("An error occurred during sign out"));
|
|
|
+
|
|
|
+ var httpContext = new DefaultHttpContext();
|
|
|
+ var meterFactory = new TestMeterFactory();
|
|
|
+ var authenticationService = CreateAuthenticationService(authenticationHandler.Object, meterFactory);
|
|
|
+ var meter = meterFactory.Meters.Single();
|
|
|
+
|
|
|
+ using var signOutsCollector = new MetricCollector<long>(meterFactory, AuthenticationMetrics.MeterName, "aspnetcore.authentication.sign_outs");
|
|
|
+
|
|
|
+ // Act
|
|
|
+ var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => authenticationService.SignOutAsync(httpContext, scheme: "custom", properties: null));
|
|
|
+
|
|
|
+ // Assert
|
|
|
+ Assert.Equal("An error occurred during sign out", ex.Message);
|
|
|
+ Assert.Equal(AuthenticationMetrics.MeterName, meter.Name);
|
|
|
+ Assert.Null(meter.Version);
|
|
|
+
|
|
|
+ var measurement = Assert.Single(signOutsCollector.GetMeasurementSnapshot());
|
|
|
+ Assert.Equal(1, measurement.Value);
|
|
|
+ Assert.Equal("custom", (string)measurement.Tags["aspnetcore.authentication.scheme"]);
|
|
|
+ Assert.Equal("System.InvalidOperationException", (string)measurement.Tags["error.type"]);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static AuthenticationServiceImpl CreateAuthenticationService(IAuthenticationHandler authenticationHandler, TestMeterFactory meterFactory)
|
|
|
+ {
|
|
|
+ var authenticationHandlerProvider = new Mock<IAuthenticationHandlerProvider>();
|
|
|
+ authenticationHandlerProvider.Setup(p => p.GetHandlerAsync(It.IsAny<HttpContext>(), "custom")).Returns(Task.FromResult(authenticationHandler));
|
|
|
+
|
|
|
+ var claimsTransform = new Mock<IClaimsTransformation>();
|
|
|
+ claimsTransform.Setup(t => t.TransformAsync(It.IsAny<ClaimsPrincipal>())).Returns((ClaimsPrincipal p) => Task.FromResult(p));
|
|
|
+
|
|
|
+ var options = Options.Create(new AuthenticationOptions
|
|
|
+ {
|
|
|
+ DefaultSignInScheme = "custom",
|
|
|
+ RequireAuthenticatedSignIn = false,
|
|
|
+ });
|
|
|
+
|
|
|
+ var metrics = new AuthenticationMetrics(meterFactory);
|
|
|
+ var authenticationService = new AuthenticationServiceImpl(
|
|
|
+ Mock.Of<IAuthenticationSchemeProvider>(),
|
|
|
+ authenticationHandlerProvider.Object,
|
|
|
+ claimsTransform.Object,
|
|
|
+ options,
|
|
|
+ metrics);
|
|
|
+
|
|
|
+ return authenticationService;
|
|
|
+ }
|
|
|
+}
|