diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ChallengeResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/ChallengeResult.cs index 5f1fb28767..d7d73eed6e 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ChallengeResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ChallengeResult.cs @@ -101,8 +101,10 @@ public override async Task ExecuteResultAsync(ActionContext context) var loggerFactory = context.HttpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger(); + logger.ChallengeResultExecuting(AuthenticationSchemes); + var authentication = context.HttpContext.Authentication; - if (AuthenticationSchemes.Count > 0) + if (AuthenticationSchemes != null && AuthenticationSchemes.Count > 0) { foreach (var scheme in AuthenticationSchemes) { @@ -113,8 +115,6 @@ public override async Task ExecuteResultAsync(ActionContext context) { await authentication.ChallengeAsync(Properties); } - - logger.ChallengeResultExecuting(AuthenticationSchemes); } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs b/src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs index d34770ca35..e39eb9cabf 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs @@ -937,22 +937,13 @@ public virtual CreatedAtRouteResult CreatedAtRoute(string routeName, object rout public virtual ChallengeResult Challenge() => new ChallengeResult(); - /// - /// Creates a with the specified authentication scheme. - /// - /// The authentication scheme to challenge. - /// The created for the response. - [NonAction] - public virtual ChallengeResult Challenge(string authenticationScheme) - => new ChallengeResult(authenticationScheme); - /// /// Creates a with the specified authentication schemes. /// /// The authentication schemes to challenge. /// The created for the response. [NonAction] - public virtual ChallengeResult Challenge(IList authenticationSchemes) + public virtual ChallengeResult Challenge(params string[] authenticationSchemes) => new ChallengeResult(authenticationSchemes); /// @@ -965,30 +956,18 @@ public virtual ChallengeResult Challenge(IList authenticationSchemes) public virtual ChallengeResult Challenge(AuthenticationProperties properties) => new ChallengeResult(properties); - /// - /// Creates a with the specified specified authentication scheme and - /// . - /// - /// The authentication scheme to challenge. - /// used to perform the authentication - /// challenge. - /// The created for the response. - [NonAction] - public virtual ChallengeResult Challenge(string authenticationScheme, AuthenticationProperties properties) - => new ChallengeResult(authenticationScheme, properties); - /// /// Creates a with the specified specified authentication schemes and /// . /// - /// The authentication schemes to challenge. /// used to perform the authentication /// challenge. + /// The authentication schemes to challenge. /// The created for the response. [NonAction] public virtual ChallengeResult Challenge( - IList authenticationSchemes, - AuthenticationProperties properties) + AuthenticationProperties properties, + params string[] authenticationSchemes) => new ChallengeResult(authenticationSchemes, properties); /// @@ -999,22 +978,13 @@ public virtual ChallengeResult Challenge( public virtual ForbidResult Forbid() => new ForbidResult(); - /// - /// Creates a with the specified authentication scheme. - /// - /// The authentication scheme to challenge. - /// The created for the response. - [NonAction] - public virtual ForbidResult Forbid(string authenticationScheme) - => new ForbidResult(authenticationScheme); - /// /// Creates a with the specified authentication schemes. /// /// The authentication schemes to challenge. /// The created for the response. [NonAction] - public virtual ForbidResult Forbid(IList authenticationSchemes) + public virtual ForbidResult Forbid(params string[] authenticationSchemes) => new ForbidResult(authenticationSchemes); /// @@ -1028,28 +998,61 @@ public virtual ForbidResult Forbid(AuthenticationProperties properties) => new ForbidResult(properties); /// - /// Creates a with the specified specified authentication scheme and + /// Creates a with the specified specified authentication schemes and /// . /// - /// The authentication scheme to challenge. /// used to perform the authentication /// challenge. + /// The authentication schemes to challenge. /// The created for the response. [NonAction] - public virtual ForbidResult Forbid(string authenticationScheme, AuthenticationProperties properties) - => new ForbidResult(authenticationScheme, properties); + public virtual ForbidResult Forbid(AuthenticationProperties properties, params string[] authenticationSchemes) + => new ForbidResult(authenticationSchemes, properties); /// - /// Creates a with the specified specified authentication schemes and + /// Creates a with the specified authentication scheme. + /// + /// The containing the user claims. + /// The authentication scheme to use for the sign-in operation. + /// The created for the response. + [NonAction] + public virtual SignInResult SignIn(ClaimsPrincipal principal, string authenticationScheme) + => new SignInResult(authenticationScheme, principal); + + /// + /// Creates a with the specified specified authentication scheme and /// . /// - /// The authentication schemes to challenge. - /// used to perform the authentication - /// challenge. - /// The created for the response. + /// The containing the user claims. + /// used to perform the sign-in operation. + /// The authentication scheme to use for the sign-in operation. + /// The created for the response. [NonAction] - public virtual ForbidResult Forbid(IList authenticationSchemes, AuthenticationProperties properties) - => new ForbidResult(authenticationSchemes, properties); + public virtual SignInResult SignIn( + ClaimsPrincipal principal, + AuthenticationProperties properties, + string authenticationScheme) + => new SignInResult(authenticationScheme, principal, properties); + + /// + /// Creates a with the specified authentication schemes. + /// + /// The authentication schemes to use for the sign-out operation. + /// The created for the response. + [NonAction] + public virtual SignOutResult SignOut(params string[] authenticationSchemes) + => new SignOutResult(authenticationSchemes); + + /// + /// Creates a with the specified specified authentication schemes and + /// . + /// + /// used to perform the sign-out operation. + /// The authentication scheme to use for the sign-out operation. + /// The created for the response. + [NonAction] + public virtual SignOutResult SignOut(AuthenticationProperties properties, params string[] authenticationSchemes) + => new SignOutResult(authenticationSchemes, properties); /// /// Updates the specified instance using values from the controller's current diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ForbidResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/ForbidResult.cs index 6effcb8673..e831976b18 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ForbidResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ForbidResult.cs @@ -101,6 +101,8 @@ public override async Task ExecuteResultAsync(ActionContext context) var loggerFactory = context.HttpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger(); + logger.ForbidResultExecuting(AuthenticationSchemes); + var authentication = context.HttpContext.Authentication; if (AuthenticationSchemes != null && AuthenticationSchemes.Count > 0) @@ -114,8 +116,6 @@ public override async Task ExecuteResultAsync(ActionContext context) { await authentication.ForbidAsync(Properties); } - - logger.ForbidResultExecuting(AuthenticationSchemes); } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs index 26d1b859cf..f99b554886 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Security.Claims; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Mvc.Filters; @@ -40,7 +41,9 @@ internal static class MvcCoreLoggerExtensions private static readonly Action _actionFilterShortCircuit; private static readonly Action _exceptionFilterShortCircuit; - private static readonly Action _resultExecuting; + private static readonly Action _forbidResultExecuting; + private static readonly Action _signInResultExecuting; + private static readonly Action _signOutResultExecuting; private static readonly Action _httpStatusCodeResultExecuting; @@ -126,11 +129,21 @@ static MvcCoreLoggerExtensions() 4, "Request was short circuited at exception filter '{ExceptionFilter}'."); - _resultExecuting = LoggerMessage.Define( + _forbidResultExecuting = LoggerMessage.Define( LogLevel.Information, eventId: 1, formatString: $"Executing {nameof(ForbidResult)} with authentication schemes ({{Schemes}})."); + _signInResultExecuting = LoggerMessage.Define( + LogLevel.Information, + eventId: 1, + formatString: $"Executing {nameof(SignInResult)} with authentication scheme ({{Scheme}}) and the following principal: {{Principal}}."); + + _signOutResultExecuting = LoggerMessage.Define( + LogLevel.Information, + eventId: 1, + formatString: $"Executing {nameof(SignOutResult)} with authentication schemes ({{Schemes}})."); + _httpStatusCodeResultExecuting = LoggerMessage.Define( LogLevel.Information, 1, @@ -305,7 +318,17 @@ public static void ActionFilterShortCircuited( public static void ForbidResultExecuting(this ILogger logger, IList authenticationSchemes) { - _resultExecuting(logger, authenticationSchemes.ToArray(), null); + _forbidResultExecuting(logger, authenticationSchemes.ToArray(), null); + } + + public static void SignInResultExecuting(this ILogger logger, string authenticationScheme, ClaimsPrincipal principal) + { + _signInResultExecuting(logger, authenticationScheme, principal, null); + } + + public static void SignOutResultExecuting(this ILogger logger, IList authenticationSchemes) + { + _signOutResultExecuting(logger, authenticationSchemes.ToArray(), null); } public static void HttpStatusCodeResultExecuting(this ILogger logger, int statusCode) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs index 86d35c8870..3f100b7465 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs @@ -1146,6 +1146,22 @@ internal static string FormatFormatter_NoMediaTypes(object p0, object p1) return string.Format(CultureInfo.CurrentCulture, GetString("Formatter_NoMediaTypes"), p0, p1); } + /// + /// At least one authentication scheme must be specified. + /// + internal static string MustSpecifyAtLeastOneAuthenticationScheme + { + get { return GetString("MustSpecifyAtLeastOneAuthenticationScheme"); } + } + + /// + /// At least one authentication scheme must be specified. + /// + internal static string FormatMustSpecifyAtLeastOneAuthenticationScheme() + { + return string.Format(CultureInfo.CurrentCulture, GetString("MustSpecifyAtLeastOneAuthenticationScheme")); + } + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx b/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx index a73517b6bc..7d1d3c176b 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx +++ b/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx @@ -340,4 +340,7 @@ No media types found in '{0}.{1}'. Add at least one media type to the list of supported media types. + + At least one authentication scheme must be specified. + \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/SignInResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/SignInResult.cs new file mode 100644 index 0000000000..875a8f34e1 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/SignInResult.cs @@ -0,0 +1,95 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Authentication; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// An that on execution invokes . + /// + public class SignInResult : ActionResult + { + /// + /// Initializes a new instance of with the + /// specified authentication scheme. + /// + /// The authentication scheme to use when signing in the user. + /// The claims principal containing the user claims. + public SignInResult(string authenticationScheme, ClaimsPrincipal principal) + : this(authenticationScheme, principal, properties: null) + { + } + + /// + /// Initializes a new instance of with the + /// specified authentication scheme and . + /// + /// The authentication schemes to use when signing in the user. + /// The claims principal containing the user claims. + /// used to perform the sign-in operation. + public SignInResult(string authenticationScheme, ClaimsPrincipal principal, AuthenticationProperties properties) + { + if (authenticationScheme == null) + { + throw new ArgumentNullException(nameof(authenticationScheme)); + } + + if (principal == null) + { + throw new ArgumentNullException(nameof(principal)); + } + + AuthenticationScheme = authenticationScheme; + Principal = principal; + Properties = properties; + } + + /// + /// Gets or sets the authentication scheme that is used to perform the sign-in operation. + /// + public string AuthenticationScheme { get; set; } + + /// + /// Gets or sets the containing the user claims. + /// + public ClaimsPrincipal Principal { get; set; } + + /// + /// Gets or sets the used to perform the sign-in operation. + /// + public AuthenticationProperties Properties { get; set; } + + /// + public override async Task ExecuteResultAsync(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (AuthenticationScheme == null) + { + throw new InvalidOperationException( + Resources.FormatPropertyOfTypeCannotBeNull( + /* property: */ nameof(AuthenticationScheme), + /* type: */ nameof(SignInResult))); + } + + var loggerFactory = context.HttpContext.RequestServices.GetRequiredService(); + var logger = loggerFactory.CreateLogger(); + + logger.SignInResultExecuting(AuthenticationScheme, Principal); + + var authentication = context.HttpContext.Authentication; + await authentication.SignInAsync(AuthenticationScheme, Principal, Properties); + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/SignOutResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/SignOutResult.cs new file mode 100644 index 0000000000..fff394a911 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/SignOutResult.cs @@ -0,0 +1,117 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Authentication; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// An that on execution invokes . + /// + public class SignOutResult : ActionResult + { + /// + /// Initializes a new instance of with the + /// specified authentication scheme. + /// + /// The authentication scheme to use when signing out the user. + public SignOutResult(string authenticationScheme) + : this(new[] { authenticationScheme }) + { + } + + /// + /// Initializes a new instance of with the + /// specified authentication schemes. + /// + /// The authentication schemes to use when signing out the user. + public SignOutResult(IList authenticationSchemes) + : this(authenticationSchemes, properties: null) + { + } + + /// + /// Initializes a new instance of with the + /// specified authentication scheme and . + /// + /// The authentication schemes to use when signing out the user. + /// used to perform the sign-out operation. + public SignOutResult(string authenticationScheme, AuthenticationProperties properties) + : this(new[] { authenticationScheme }, properties) + { + } + + /// + /// Initializes a new instance of with the + /// specified authentication schemes and . + /// + /// The authentication scheme to use when signing out the user. + /// used to perform the sign-out operation. + public SignOutResult(IList authenticationSchemes, AuthenticationProperties properties) + { + if (authenticationSchemes == null) + { + throw new ArgumentNullException(nameof(authenticationSchemes)); + } + + if (authenticationSchemes.Count == 0) + { + throw new ArgumentException(Resources.MustSpecifyAtLeastOneAuthenticationScheme, nameof(authenticationSchemes)); + } + + AuthenticationSchemes = authenticationSchemes; + Properties = properties; + } + + /// + /// Gets or sets the authentication schemes that are challenged. + /// + public IList AuthenticationSchemes { get; set; } + + /// + /// Gets or sets the used to perform the sign-out operation. + /// + public AuthenticationProperties Properties { get; set; } + + /// + public override async Task ExecuteResultAsync(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (AuthenticationSchemes == null) + { + throw new InvalidOperationException( + Resources.FormatPropertyOfTypeCannotBeNull( + /* property: */ nameof(AuthenticationSchemes), + /* type: */ nameof(SignOutResult))); + } + + if (AuthenticationSchemes.Count == 0) + { + throw new ArgumentException(Resources.MustSpecifyAtLeastOneAuthenticationScheme, nameof(AuthenticationSchemes)); + } + + var loggerFactory = context.HttpContext.RequestServices.GetRequiredService(); + var logger = loggerFactory.CreateLogger(); + + logger.SignOutResultExecuting(AuthenticationSchemes); + + var authentication = context.HttpContext.Authentication; + + for (var i = 0; i < AuthenticationSchemes.Count; i++) + { + await authentication.SignOutAsync(AuthenticationSchemes[i], Properties); + } + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ForbidResultTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ForbidResultTest.cs index cf0f808dcc..e9a2b143ba 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ForbidResultTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ForbidResultTest.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Mvc public class ForbidResultTest { [Fact] - public async Task ExecuteResultAsync_InvokesForbiddenAsyncOnAuthenticationManager() + public async Task ExecuteResultAsync_InvokesForbidAsyncOnAuthenticationManager() { // Arrange var authenticationManager = new Mock(); @@ -46,7 +46,7 @@ public async Task ExecuteResultAsync_InvokesForbiddenAsyncOnAuthenticationManage } [Fact] - public async Task ExecuteResultAsync_InvokesForbiddenAsyncOnAllConfiguredSchemes() + public async Task ExecuteResultAsync_InvokesForbidAsyncOnAllConfiguredSchemes() { // Arrange var authProperties = new AuthenticationProperties(); @@ -77,7 +77,7 @@ public async Task ExecuteResultAsync_InvokesForbiddenAsyncOnAllConfiguredSchemes authenticationManager.Verify(); } - public static TheoryData ExecuteResultAsync_InvokesForbiddenAsyncWithAuthPropertiesData => + public static TheoryData ExecuteResultAsync_InvokesForbidAsyncWithAuthPropertiesData => new TheoryData { null, @@ -85,8 +85,8 @@ public async Task ExecuteResultAsync_InvokesForbiddenAsyncOnAllConfiguredSchemes }; [Theory] - [MemberData(nameof(ExecuteResultAsync_InvokesForbiddenAsyncWithAuthPropertiesData))] - public async Task ExecuteResultAsync_InvokesForbiddenAsyncWithAuthProperties(AuthenticationProperties expected) + [MemberData(nameof(ExecuteResultAsync_InvokesForbidAsyncWithAuthPropertiesData))] + public async Task ExecuteResultAsync_InvokesForbidAsyncWithAuthProperties(AuthenticationProperties expected) { // Arrange var authenticationManager = new Mock(); @@ -113,8 +113,8 @@ public async Task ExecuteResultAsync_InvokesForbiddenAsyncWithAuthProperties(Aut } [Theory] - [MemberData(nameof(ExecuteResultAsync_InvokesForbiddenAsyncWithAuthPropertiesData))] - public async Task ExecuteResultAsync_InvokesForbiddenAsyncWithAuthProperties_WhenAuthenticationSchemesIsEmpty( + [MemberData(nameof(ExecuteResultAsync_InvokesForbidAsyncWithAuthPropertiesData))] + public async Task ExecuteResultAsync_InvokesForbidAsyncWithAuthProperties_WhenAuthenticationSchemesIsEmpty( AuthenticationProperties expected) { // Arrange diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/SignInResultTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/SignInResultTest.cs new file mode 100644 index 0000000000..b129eeee05 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/SignInResultTest.cs @@ -0,0 +1,86 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Authentication; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Testing; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc +{ + public class SignInResultTest + { + [Fact] + public async Task ExecuteResultAsync_InvokesSignInAsyncOnAuthenticationManager() + { + // Arrange + var principal = new ClaimsPrincipal(); + var authenticationManager = new Mock(); + authenticationManager + .Setup(c => c.SignInAsync("", principal, null)) + .Returns(TaskCache.CompletedTask) + .Verifiable(); + var httpContext = new Mock(); + httpContext.Setup(c => c.RequestServices).Returns(CreateServices()); + httpContext.Setup(c => c.Authentication).Returns(authenticationManager.Object); + var result = new SignInResult("", principal, null); + var routeData = new RouteData(); + + var actionContext = new ActionContext( + httpContext.Object, + routeData, + new ActionDescriptor()); + + // Act + await result.ExecuteResultAsync(actionContext); + + // Assert + authenticationManager.Verify(); + } + + [Fact] + public async Task ExecuteResultAsync_InvokesSignInAsyncOnConfiguredScheme() + { + // Arrange + var principal = new ClaimsPrincipal(); + var authProperties = new AuthenticationProperties(); + var authenticationManager = new Mock(); + authenticationManager + .Setup(c => c.SignInAsync("Scheme1", principal, authProperties)) + .Returns(TaskCache.CompletedTask) + .Verifiable(); + var httpContext = new Mock(); + httpContext.Setup(c => c.RequestServices).Returns(CreateServices()); + httpContext.Setup(c => c.Authentication).Returns(authenticationManager.Object); + var result = new SignInResult("Scheme1", principal, authProperties); + var routeData = new RouteData(); + + var actionContext = new ActionContext( + httpContext.Object, + routeData, + new ActionDescriptor()); + + // Act + await result.ExecuteResultAsync(actionContext); + + // Assert + authenticationManager.Verify(); + } + + private static IServiceProvider CreateServices() + { + return new ServiceCollection() + .AddSingleton(NullLoggerFactory.Instance) + .BuildServiceProvider(); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/SignOutResultTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/SignOutResultTest.cs new file mode 100644 index 0000000000..f9bc7a94bd --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/SignOutResultTest.cs @@ -0,0 +1,87 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Authentication; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Testing; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc +{ + public class SignOutResultTest + { + [Fact] + public async Task ExecuteResultAsync_InvokesSignOutAsyncOnAuthenticationManager() + { + // Arrange + var authenticationManager = new Mock(); + authenticationManager + .Setup(c => c.SignOutAsync("", null)) + .Returns(TaskCache.CompletedTask) + .Verifiable(); + var httpContext = new Mock(); + httpContext.Setup(c => c.RequestServices).Returns(CreateServices()); + httpContext.Setup(c => c.Authentication).Returns(authenticationManager.Object); + var result = new SignOutResult("", null); + var routeData = new RouteData(); + + var actionContext = new ActionContext( + httpContext.Object, + routeData, + new ActionDescriptor()); + + // Act + await result.ExecuteResultAsync(actionContext); + + // Assert + authenticationManager.Verify(); + } + + [Fact] + public async Task ExecuteResultAsync_InvokesSignOutAsyncOnAllConfiguredSchemes() + { + // Arrange + var authProperties = new AuthenticationProperties(); + var authenticationManager = new Mock(); + authenticationManager + .Setup(c => c.SignOutAsync("Scheme1", authProperties)) + .Returns(TaskCache.CompletedTask) + .Verifiable(); + authenticationManager + .Setup(c => c.SignOutAsync("Scheme2", authProperties)) + .Returns(TaskCache.CompletedTask) + .Verifiable(); + var httpContext = new Mock(); + httpContext.Setup(c => c.RequestServices).Returns(CreateServices()); + httpContext.Setup(c => c.Authentication).Returns(authenticationManager.Object); + var result = new SignOutResult(new[] { "Scheme1", "Scheme2" }, authProperties); + var routeData = new RouteData(); + + var actionContext = new ActionContext( + httpContext.Object, + routeData, + new ActionDescriptor()); + + // Act + await result.ExecuteResultAsync(actionContext); + + // Assert + authenticationManager.Verify(); + } + + private static IServiceProvider CreateServices() + { + return new ServiceCollection() + .AddSingleton(NullLoggerFactory.Instance) + .BuildServiceProvider(); + } + } +} \ No newline at end of file