Add Azure AD package + tests

This commit is contained in:
Javier Calvarro Nelson 2018-03-22 22:49:34 -07:00
parent 8f42ce8492
commit f49270d9d6
26 changed files with 1380 additions and 0 deletions

View File

@ -15,6 +15,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Authen
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Authentication.AzureADB2C.UI.Test", "test\Microsoft.AspNetCore.Authentication.AzureADB2C.UI.Test\Microsoft.AspNetCore.Authentication.AzureADB2C.UI.Test.csproj", "{454089F9-ED16-4A11-9C52-2BA74DCF5D35}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Authentication.AzureAD.UI", "src\Microsoft.AspNetCore.Authentication.AzureAD.UI\Microsoft.AspNetCore.Authentication.AzureAD.UI.csproj", "{1762840C-A14A-4498-9883-CC671956F0F2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Authentication.AzureAD.UI.Test", "test\Microsoft.AspNetCore.Authentication.AzureAD.UI.Test\Microsoft.AspNetCore.Authentication.AzureAD.UI.Test.csproj", "{3D0CF896-3A9D-4A8F-A343-A2E1A131C861}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -33,6 +37,14 @@ Global
{454089F9-ED16-4A11-9C52-2BA74DCF5D35}.Debug|Any CPU.Build.0 = Debug|Any CPU
{454089F9-ED16-4A11-9C52-2BA74DCF5D35}.Release|Any CPU.ActiveCfg = Release|Any CPU
{454089F9-ED16-4A11-9C52-2BA74DCF5D35}.Release|Any CPU.Build.0 = Release|Any CPU
{1762840C-A14A-4498-9883-CC671956F0F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1762840C-A14A-4498-9883-CC671956F0F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1762840C-A14A-4498-9883-CC671956F0F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1762840C-A14A-4498-9883-CC671956F0F2}.Release|Any CPU.Build.0 = Release|Any CPU
{3D0CF896-3A9D-4A8F-A343-A2E1A131C861}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3D0CF896-3A9D-4A8F-A343-A2E1A131C861}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3D0CF896-3A9D-4A8F-A343-A2E1A131C861}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3D0CF896-3A9D-4A8F-A343-A2E1A131C861}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -41,6 +53,8 @@ Global
{5D2378D4-9DDA-468F-82C6-AE4DAA54E96C} = {8CF63E1D-F9F7-4CB4-AD90-88C4077F7BFF}
{16912327-C9B3-49FC-87E0-49FEDED0A065} = {75A812B0-D98C-45F3-B2A9-357BBDF7331A}
{454089F9-ED16-4A11-9C52-2BA74DCF5D35} = {57F46508-E53D-4F6B-B77C-2EFE95925AEF}
{1762840C-A14A-4498-9883-CC671956F0F2} = {75A812B0-D98C-45F3-B2A9-357BBDF7331A}
{3D0CF896-3A9D-4A8F-A343-A2E1A131C861} = {57F46508-E53D-4F6B-B77C-2EFE95925AEF}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C6DBF56C-E862-46EA-A4E0-993D2950D78D}

View File

@ -10,6 +10,13 @@
"lib/netstandard2.0/Microsoft.AspNetCore.Authentication.AzureADB2C.UI.Views.dll": "This library contains precompiled views."
}
}
},
"Microsoft.AspNetCore.Authentication.AzureAD.UI": {
"Exclusions": {
"DOC_MISSING": {
"lib/netstandard2.0/Microsoft.AspNetCore.Authentication.AzureAD.UI.Views.dll": "This library contains precompiled views."
}
}
}
}
}

View File

@ -0,0 +1,45 @@
// 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 Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Authentication.AzureAD.UI.AzureAD.Controllers.Internal
{
[NonController]
[AllowAnonymous]
[Area("AzureAD")]
[Route("[area]/[controller]/[action]")]
internal class AccountController : Controller
{
public AccountController(IOptionsMonitor<AzureADOptions> options)
{
Options = options;
}
public IOptionsMonitor<AzureADOptions> Options { get; }
[HttpGet("{scheme?}")]
public IActionResult SignIn([FromRoute] string scheme)
{
scheme = scheme ?? AzureADDefaults.AuthenticationScheme;
var redirectUrl = Url.Content("~/");
return Challenge(
new AuthenticationProperties { RedirectUri = redirectUrl },
scheme);
}
[HttpGet("{scheme?}")]
public IActionResult SignOut([FromRoute] string scheme)
{
scheme = scheme ?? AzureADDefaults.AuthenticationScheme;
var options = Options.Get(scheme);
var callbackUrl = Url.Page("/Account/SignedOut", pageHandler: null, values: null, protocol: Request.Scheme);
return SignOut(
new AuthenticationProperties { RedirectUri = callbackUrl },
options.CookieSchemeName,
options.OpenIdConnectSchemeName);
}
}
}

View File

@ -0,0 +1,10 @@
@page
@model AccessDeniedModel
@{
ViewData["Title"] = "Access denied";
}
<header>
<h1 class="text-danger">@ViewData["Title"]</h1>
<p class="text-danger">You do not have access to this resource.</p>
</header>

View File

@ -0,0 +1,24 @@
// 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 Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Microsoft.AspNetCore.Authentication.AzureAD.UI.Internal
{
/// <summary>
/// This API supports infrastructure and is not intended to be used
/// directly from your code.This API may change or be removed in future releases
/// </summary>
[AllowAnonymous]
public class AccessDeniedModel : PageModel
{
/// <summary>
/// This API supports infrastructure and is not intended to be used
/// directly from your code.This API may change or be removed in future releases
/// </summary>
public void OnGet()
{
}
}
}

View File

@ -0,0 +1,23 @@
@page
@model ErrorModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>Development environment should not be enabled in deployed applications</strong>, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>, and restarting the application.
</p>

View File

@ -0,0 +1,40 @@
// 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 Microsoft.AspNetCore.Authorization;
using System.Diagnostics;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Microsoft.AspNetCore.Authentication.AzureAD.UI.Internal
{
/// <summary>
/// This API supports infrastructure and is not intended to be used
/// directly from your code.This API may change or be removed in future releases
/// </summary>
[AllowAnonymous]
public class ErrorModel : PageModel
{
/// <summary>
/// This API supports infrastructure and is not intended to be used
/// directly from your code.This API may change or be removed in future releases
/// </summary>
public string RequestId { get; set; }
/// <summary>
/// This API supports infrastructure and is not intended to be used
/// directly from your code.This API may change or be removed in future releases
/// </summary>
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
/// <summary>
/// This API supports infrastructure and is not intended to be used
/// directly from your code.This API may change or be removed in future releases
/// </summary>
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}
}

View File

@ -0,0 +1,10 @@
@page
@model SignedOutModel
@{
ViewData["Title"] = "Signed out";
}
<h2>@ViewData["Title"]</h2>
<p>
You have successfully signed out.
</p>

View File

@ -0,0 +1,31 @@
// 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 Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Microsoft.AspNetCore.Authentication.AzureAD.UI.Internal
{
/// <summary>
/// This API supports infrastructure and is not intended to be used
/// directly from your code.This API may change or be removed in future releases
/// </summary>
[AllowAnonymous]
public class SignedOutModel : PageModel
{
/// <summary>
/// This API supports infrastructure and is not intended to be used
/// directly from your code.This API may change or be removed in future releases
/// </summary>
public IActionResult OnGet()
{
if (User.Identity.IsAuthenticated)
{
return LocalRedirect("~/");
}
return Page();
}
}
}

View File

@ -0,0 +1,2 @@
@using Microsoft.AspNetCore.Authentication.AzureAD.UI.Internal
@namespace Microsoft.AspNetCore.Authentication.AzureAD.UI.Pages.Internal

View File

@ -0,0 +1,3 @@
@{
Layout = "_Layout";
}

View File

@ -0,0 +1,22 @@
// 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 Microsoft.AspNetCore.Authorization;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.AspNetCore.Authentication.AzureAD.UI.AzureAD.Controllers.Internal;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Controllers;
namespace Microsoft.AspNetCore.Authentication.AzureAD.UI
{
internal class AzureADAccountControllerFeatureProvider : IApplicationFeatureProvider<ControllerFeature>, IApplicationFeatureProvider
{
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
{
if (!feature.Controllers.Contains(typeof(AccountController).GetTypeInfo()))
{
feature.Controllers.Add(typeof(AccountController).GetTypeInfo());
}
}
}
}

View File

@ -0,0 +1,223 @@
// 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 Microsoft.AspNetCore.Authorization;
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Authentication.AzureAD.UI;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Authentication
{
/// <summary>
/// Extension methods to add Azure Active Directory Authentication to your application.
/// </summary>
public static class AzureADAuthenticationBuilderExtensions
{
/// <summary>
/// Adds JWT Bearer authentication to your app for Azure Active Directory Applications.
/// </summary>
/// <param name="builder">The <see cref="AuthenticationBuilder"/>.</param>
/// <param name="configureOptions">The <see cref="Action{AzureADOptions}"/> to configure the
/// <see cref="AzureADOptions"/>.
/// </param>
/// <returns>The <see cref="AuthenticationBuilder"/>.</returns>
public static AuthenticationBuilder AddAzureADBearer(this AuthenticationBuilder builder, Action<AzureADOptions> configureOptions) =>
builder.AddAzureADBearer(
AzureADDefaults.BearerAuthenticationScheme,
AzureADDefaults.JwtBearerAuthenticationScheme,
configureOptions);
/// <summary>
/// Adds JWT Bearer authentication to your app for Azure Active Directory Applications.
/// </summary>
/// <param name="builder">The <see cref="AuthenticationBuilder"/>.</param>
/// <param name="scheme">The identifier for the virtual scheme.</param>
/// <param name="jwtBearerScheme">The identifier for the underlying JWT Bearer scheme.</param>
/// <param name="configureOptions">The <see cref="Action{AzureADOptions}"/> to configure the
/// <see cref="AzureADOptions"/>.
/// </param>
/// <returns>The <see cref="AuthenticationBuilder"/>.</returns>
public static AuthenticationBuilder AddAzureADBearer(
this AuthenticationBuilder builder,
string scheme,
string jwtBearerScheme,
Action<AzureADOptions> configureOptions)
{
builder.AddPolicyScheme(scheme, displayName: null, configureOptions: o =>
{
o.ForwardDefault = jwtBearerScheme;
});
builder.Services.Configure(TryAddJwtBearerSchemeMapping(scheme, jwtBearerScheme));
builder.Services.TryAddSingleton<IConfigureOptions<AzureADOptions>, AzureADOptionsConfiguration>();
builder.Services.TryAddSingleton<IConfigureOptions<JwtBearerOptions>, JwtBearerOptionsConfiguration>();
builder.Services.Configure(scheme, configureOptions);
builder.AddJwtBearer();
return builder;
}
/// <summary>
/// Adds Azure Active Directory Authentication to your application.
/// </summary>
/// <param name="builder">The <see cref="AuthenticationBuilder"/>.</param>
/// <param name="configureOptions">The <see cref="Action{AzureADOptions}"/> to configure the
/// <see cref="AzureADOptions"/>
/// </param>
/// <returns>The <see cref="AuthenticationBuilder"/>.</returns>
public static AuthenticationBuilder AddAzureAD(this AuthenticationBuilder builder, Action<AzureADOptions> configureOptions) =>
builder.AddAzureAD(
AzureADDefaults.AuthenticationScheme,
AzureADDefaults.OpenIdScheme,
AzureADDefaults.CookieScheme,
AzureADDefaults.DisplayName,
configureOptions);
/// <summary>
/// Adds Azure Active Directory Authentication to your application.
/// </summary>
/// <param name="builder">The <see cref="AuthenticationBuilder"/>.</param>
/// <param name="scheme">The identifier for the virtual scheme.</param>
/// <param name="openIdConnectScheme">The identifier for the underlying Open ID Connect scheme.</param>
/// <param name="cookieScheme">The identifier for the underlying cookie scheme.</param>
/// <param name="displayName">The display name for the scheme.</param>
/// <param name="configureOptions">The <see cref="Action{AzureADOptions}"/> to configure the
/// <see cref="AzureADOptions"/>
/// </param>
/// <returns>The <see cref="AuthenticationBuilder"/>.</returns>
public static AuthenticationBuilder AddAzureAD(
this AuthenticationBuilder builder,
string scheme,
string openIdConnectScheme,
string cookieScheme,
string displayName,
Action<AzureADOptions> configureOptions)
{
AddAdditionalMvcApplicationParts(builder.Services);
builder.AddPolicyScheme(scheme, displayName, o =>
{
o.ForwardDefault = cookieScheme;
o.ForwardChallenge = openIdConnectScheme;
});
builder.Services.Configure(TryAddOpenIDCookieSchemeMappings(scheme, openIdConnectScheme, cookieScheme));
builder.Services.TryAddSingleton<IConfigureOptions<AzureADOptions>, AzureADOptionsConfiguration>();
builder.Services.TryAddSingleton<IConfigureOptions<OpenIdConnectOptions>, OpenIdConnectOptionsConfiguration>();
builder.Services.TryAddSingleton<IConfigureOptions<CookieAuthenticationOptions>, CookieOptionsConfiguration>();
builder.Services.Configure(scheme, configureOptions);
builder.AddOpenIdConnect(openIdConnectScheme, null, o => { });
builder.AddCookie(cookieScheme, null, o => { });
return builder;
}
private static Action<AzureADSchemeOptions> TryAddJwtBearerSchemeMapping(string scheme, string jwtBearerScheme)
{
return TryAddMapping;
void TryAddMapping(AzureADSchemeOptions o)
{
if (o.JwtBearerMappings.ContainsKey(scheme))
{
throw new InvalidOperationException($"A scheme with the name '{scheme}' was already added.");
}
foreach (var mapping in o.JwtBearerMappings)
{
if (mapping.Value.JwtBearerScheme == jwtBearerScheme)
{
throw new InvalidOperationException(
$"The JSON Web Token Bearer scheme '{jwtBearerScheme}' can't be associated with the Azure Active Directory scheme '{scheme}'. " +
$"The JSON Web Token Bearer scheme '{jwtBearerScheme}' is already mapped to the Azure Active Directory scheme '{mapping.Key}'");
}
}
o.JwtBearerMappings.Add(scheme, new AzureADSchemeOptions.JwtBearerSchemeMapping
{
JwtBearerScheme = jwtBearerScheme
});
};
}
private static Action<AzureADSchemeOptions> TryAddOpenIDCookieSchemeMappings(string scheme, string openIdConnectScheme, string cookieScheme)
{
return TryAddMapping;
void TryAddMapping(AzureADSchemeOptions o)
{
if (o.OpenIDMappings.ContainsKey(scheme))
{
throw new InvalidOperationException($"A scheme with the name '{scheme}' was already added.");
}
foreach (var mapping in o.OpenIDMappings)
{
if (mapping.Value.CookieScheme == cookieScheme)
{
throw new InvalidOperationException(
$"The cookie scheme '{cookieScheme}' can't be associated with the Azure Active Directory scheme '{scheme}'. " +
$"The cookie scheme '{cookieScheme}' is already mapped to the Azure Active Directory scheme '{mapping.Key}'");
}
if (mapping.Value.OpenIdConnectScheme == openIdConnectScheme)
{
throw new InvalidOperationException(
$"The Open ID Connect scheme '{openIdConnectScheme}' can't be associated with the Azure Active Directory scheme '{scheme}'. " +
$"The Open ID Connect scheme '{openIdConnectScheme}' is already mapped to the Azure Active Directory scheme '{mapping.Key}'");
}
}
o.OpenIDMappings.Add(scheme, new AzureADSchemeOptions.AzureADOpenIDSchemeMapping
{
OpenIdConnectScheme = openIdConnectScheme,
CookieScheme = cookieScheme
});
};
}
private static void AddAdditionalMvcApplicationParts(IServiceCollection services)
{
var additionalParts = GetAdditionalParts();
var mvcBuilder = services
.AddMvc()
.AddRazorPagesOptions(o => o.AllowAreas = true)
.ConfigureApplicationPartManager(apm =>
{
foreach (var part in additionalParts)
{
if (!apm.ApplicationParts.Any(ap => HasSameName(ap.Name, part.Name)))
{
apm.ApplicationParts.Add(part);
}
}
apm.FeatureProviders.Add(new AzureADAccountControllerFeatureProvider());
});
bool HasSameName(string left, string right) => string.Equals(left, right, StringComparison.Ordinal);
}
private static IEnumerable<ApplicationPart> GetAdditionalParts()
{
var thisAssembly = typeof(AzureADAuthenticationBuilderExtensions).Assembly;
var relatedAssemblies = RelatedAssemblyAttribute.GetRelatedAssemblies(thisAssembly, throwOnError: true);
foreach (var reference in relatedAssemblies)
{
yield return new CompiledRazorAssemblyPart(reference);
}
}
}
}

View File

@ -0,0 +1,45 @@
// 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 Microsoft.AspNetCore.Authorization;
namespace Microsoft.AspNetCore.Authentication.AzureAD.UI
{
/// <summary>
/// Constants for different Azure Active Directory authentication components.
/// </summary>
public static class AzureADDefaults
{
/// <summary>
/// The scheme name for Open ID Connect when using
/// <see cref="AzureADAuthenticationBuilderExtensions.AddAzureAD(AuthenticationBuilder, System.Action{AzureADOptions})"/>.
/// </summary>
public static readonly string OpenIdScheme = "AzureADOpenID";
/// <summary>
/// The scheme name for cookies when using
/// <see cref="AzureADAuthenticationBuilderExtensions.AddAzureAD(AuthenticationBuilder, System.Action{AzureADOptions})"/>.
/// </summary>
public static readonly string CookieScheme = "AzureADCookie";
/// <summary>
/// The default scheme for Azure Active Directory Bearer.
/// </summary>
public static readonly string BearerAuthenticationScheme = "AzureADBearer";
/// <summary>
/// The scheme name for JWT Bearer when using
/// <see cref="AzureADAuthenticationBuilderExtensions.AddAzureADBearer(AuthenticationBuilder, System.Action{AzureADOptions})"/>.
/// </summary>
public static readonly string JwtBearerAuthenticationScheme = "AzureADJwtBearer";
/// <summary>
/// The default scheme for Azure Active Directory.
/// </summary>
public static readonly string AuthenticationScheme = "AzureAD";
/// <summary>
/// The display name for Azure Active Directory.
/// </summary>
public static readonly string DisplayName = "Azure Active Directory";
}
}

View File

@ -0,0 +1,67 @@
// 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 Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
namespace Microsoft.AspNetCore.Authentication.AzureAD.UI
{
/// <summary>
/// Options for configuring authentication using Azure Active Directory.
/// </summary>
public class AzureADOptions
{
/// <summary>
/// Gets or sets the OpenID Connect authentication scheme to use for authentication with this instance
/// of Azure Active Directory authentication.
/// </summary>
public string OpenIdConnectSchemeName { get; set; } = OpenIdConnectDefaults.AuthenticationScheme;
/// <summary>
/// Gets or sets the Cookie authentication scheme to use for sign in with this instance of
/// Azure Active Directory authentication.
/// </summary>
public string CookieSchemeName { get; set; } = CookieAuthenticationDefaults.AuthenticationScheme;
/// <summary>
/// Gets or sets the Jwt bearer authentication scheme to use for validating access tokens for this
/// instance of Azure Active Directory Bearer authentication.
/// </summary>
public string JwtBearerSchemeName { get; internal set; }
/// <summary>
/// Gets or sets the client Id.
/// </summary>
public string ClientId { get; set; }
/// <summary>
/// Gets or sets the tenant Id.
/// </summary>
public string TenantId { get; set; }
/// <summary>
/// Gets or sets the Azure Active Directory instance.
/// </summary>
public string Instance { get; set; }
/// <summary>
/// Gets or sets the domain of the Azure Active Directory tennant.
/// </summary>
public string Domain { get; set; }
/// <summary>
/// Gets or sets the sign in callback path.
/// </summary>
public string CallbackPath { get; set; }
/// <summary>
/// Gets or sets the sign out callback path.
/// </summary>
public string SignedOutCallbackPath { get; set; }
/// <summary>
/// Gets all the underlying authentication schemes.
/// </summary>
public string[] AllSchemes => new[] { CookieSchemeName, OpenIdConnectSchemeName };
}
}

View File

@ -0,0 +1,38 @@
// 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 Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Authentication.AzureAD.UI
{
internal class AzureADOptionsConfiguration : IConfigureNamedOptions<AzureADOptions>
{
private readonly IOptions<AzureADSchemeOptions> _schemeOptions;
public AzureADOptionsConfiguration(IOptions<AzureADSchemeOptions> schemeOptions)
{
_schemeOptions = schemeOptions;
}
public void Configure(string name, AzureADOptions options)
{
// This can be called because of someone configuring JWT or someone configuring
// Open ID + Cookie.
if (_schemeOptions.Value.OpenIDMappings.TryGetValue(name, out var webMapping))
{
options.OpenIdConnectSchemeName = webMapping.OpenIdConnectScheme;
options.CookieSchemeName = webMapping.CookieScheme;
return;
}
if (_schemeOptions.Value.JwtBearerMappings.TryGetValue(name, out var mapping))
{
options.JwtBearerSchemeName = mapping.JwtBearerScheme;
return;
}
}
public void Configure(AzureADOptions options)
{
}
}
}

View File

@ -0,0 +1,25 @@
// 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 Microsoft.AspNetCore.Authorization;
using System.Collections.Generic;
namespace Microsoft.AspNetCore.Authentication.AzureAD.UI
{
internal class AzureADSchemeOptions
{
public IDictionary<string, AzureADOpenIDSchemeMapping> OpenIDMappings { get; set; } = new Dictionary<string, AzureADOpenIDSchemeMapping>();
public IDictionary<string, JwtBearerSchemeMapping> JwtBearerMappings { get; set; } = new Dictionary<string, JwtBearerSchemeMapping>();
public class AzureADOpenIDSchemeMapping
{
public string OpenIdConnectScheme { get; set; }
public string CookieScheme { get; set; }
}
public class JwtBearerSchemeMapping
{
public string JwtBearerScheme { get; set; }
}
}
}

View File

@ -0,0 +1,51 @@
// 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 Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Authentication.AzureAD.UI
{
internal class CookieOptionsConfiguration : IConfigureNamedOptions<CookieAuthenticationOptions>
{
private readonly IOptions<AzureADSchemeOptions> _schemeOptions;
private readonly IOptionsMonitor<AzureADOptions> _AzureADOptions;
public CookieOptionsConfiguration(IOptions<AzureADSchemeOptions> schemeOptions, IOptionsMonitor<AzureADOptions> AzureADOptions)
{
_schemeOptions = schemeOptions;
_AzureADOptions = AzureADOptions;
}
public void Configure(string name, CookieAuthenticationOptions options)
{
var AzureADScheme = GetAzureADScheme(name);
var AzureADOptions = _AzureADOptions.Get(AzureADScheme);
if (name != AzureADOptions.CookieSchemeName)
{
return;
}
options.LoginPath = $"/AzureAD/Account/SignIn/{AzureADScheme}";
options.LogoutPath = $"/AzureAD/Account/SignOut/{AzureADScheme}";
options.AccessDeniedPath = "/AzureAD/Account/AccessDenied";
}
public void Configure(CookieAuthenticationOptions options)
{
}
private string GetAzureADScheme(string name)
{
foreach (var mapping in _schemeOptions.Value.OpenIDMappings)
{
if (mapping.Value.OpenIdConnectScheme == name)
{
return mapping.Key;
}
}
return null;
}
}
}

View File

@ -0,0 +1,54 @@
// 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 Microsoft.AspNetCore.Authorization;
using System;
using Microsoft.AspNetCore.Authentication.AzureAD.UI;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Authentication
{
internal class JwtBearerOptionsConfiguration : IConfigureNamedOptions<JwtBearerOptions>
{
private readonly IOptions<AzureADSchemeOptions> _schemeOptions;
private readonly IOptionsMonitor<AzureADOptions> _azureADOptions;
public JwtBearerOptionsConfiguration(
IOptions<AzureADSchemeOptions> schemeOptions,
IOptionsMonitor<AzureADOptions> azureADOptions)
{
_schemeOptions = schemeOptions;
_azureADOptions = azureADOptions;
}
public void Configure(string name, JwtBearerOptions options)
{
var azureADScheme = GetAzureADScheme(name);
var azureADOptions = _azureADOptions.Get(azureADScheme);
if (name != azureADOptions.JwtBearerSchemeName)
{
return;
}
options.Audience = azureADOptions.ClientId;
options.Authority = new Uri(new Uri(azureADOptions.Instance), azureADOptions.TenantId).ToString();
}
public void Configure(JwtBearerOptions options)
{
}
private string GetAzureADScheme(string name)
{
foreach (var mapping in _schemeOptions.Value.JwtBearerMappings)
{
if (mapping.Value.JwtBearerScheme == name)
{
return mapping.Key;
}
}
return null;
}
}
}

View File

@ -0,0 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk;Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<Description>ASP.NET Core Azure Active Directory Integration provides components for easily integrating Azure Active Directory authentication within your ASP.NET Core application.</Description>
<ViewAssemblyDescription>Precompiled views assembly for the ASP.NET Core Azure Active Directory Integration package.</ViewAssemblyDescription>
<TargetFramework>netstandard2.0</TargetFramework>
<PackageTags>aspnetcore;authentication;AzureAD</PackageTags>
<RazorCompileOnBuild>true</RazorCompileOnBuild>
<PrecompiledAssemblyTitle Condition=" '$(PrecompiledAssemblyTitle)' == '' ">$(RazorTargetName).dll</PrecompiledAssemblyTitle>
<PreserveCompilationContext>false</PreserveCompilationContext>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<EnableDefaultCompiledViewAssemblyLoadBehavior>false</EnableDefaultCompiledViewAssemblyLoadBehavior>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="$(MicrosoftAspNetCoreMvcPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="$(MicrosoftAspNetCoreAuthenticationJwtBearerPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="$(MicrosoftAspNetCoreAuthenticationCookiesPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="$(MicrosoftAspNetCoreAuthenticationOpenIdConnectPackageVersion)" />
</ItemGroup>
<ItemGroup>
<RazorAssemblyAttribute Include="System.Reflection.AssemblyDescriptionAttribute">
<_Parameter1>$(ViewAssemblyDescription)</_Parameter1>
</RazorAssemblyAttribute>
<RazorAssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute" Condition="'$(BuildNumber)' != ''">
<_Parameter1>BuildNumber</_Parameter1>
<_Parameter2>$(BuildNumber)</_Parameter2>
</RazorAssemblyAttribute>
<RazorAssemblyAttribute Include="System.Resources.NeutralResourcesLanguageAttribute" Condition="'$(NeutralLanguage)' != '' and '$(GenerateNeutralResourcesLanguageAttribute)' == 'true'">
<_Parameter1>$(NeutralLanguage)</_Parameter1>
</RazorAssemblyAttribute>
</ItemGroup>
<ItemGroup>
<RazorCompile Include="$(GeneratedCommitHashAttributeFile)" />
<RazorCompile Include="$(GeneratedInternalAspNetCoreAttributeFile)" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,55 @@
// 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 Microsoft.AspNetCore.Authorization;
using System;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Authentication.AzureAD.UI
{
internal class OpenIdConnectOptionsConfiguration : IConfigureNamedOptions<OpenIdConnectOptions>
{
private readonly IOptions<AzureADSchemeOptions> _schemeOptions;
private readonly IOptionsMonitor<AzureADOptions> _azureADOptions;
public OpenIdConnectOptionsConfiguration(IOptions<AzureADSchemeOptions> schemeOptions, IOptionsMonitor<AzureADOptions> azureADOptions)
{
_schemeOptions = schemeOptions;
_azureADOptions = azureADOptions;
}
public void Configure(string name, OpenIdConnectOptions options)
{
var azureADScheme = GetAzureADScheme(name);
var azureADOptions = _azureADOptions.Get(azureADScheme);
if (name != azureADOptions.OpenIdConnectSchemeName)
{
return;
}
options.ClientId = azureADOptions.ClientId;
options.Authority = new Uri(new Uri(azureADOptions.Instance), azureADOptions.TenantId).ToString();
options.CallbackPath = azureADOptions.CallbackPath ?? options.CallbackPath;
options.SignedOutCallbackPath = azureADOptions.SignedOutCallbackPath ?? options.SignedOutCallbackPath;
options.SignInScheme = azureADOptions.CookieSchemeName;
options.UseTokenLifetime = true;
}
private string GetAzureADScheme(string name)
{
foreach (var mapping in _schemeOptions.Value.OpenIDMappings)
{
if (mapping.Value.OpenIdConnectScheme == name)
{
return mapping.Key;
}
}
return null;
}
public void Configure(OpenIdConnectOptions options)
{
}
}
}

View File

@ -0,0 +1,6 @@
// 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 Microsoft.AspNetCore.Authorization;
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Authentication.AzureAD.UI.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View File

@ -0,0 +1,243 @@
// 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 Microsoft.AspNetCore.Authorization;
using System;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authentication.AzureAD.UI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Xunit;
namespace Microsoft.AspNetCore.Authentication
{
public class AzureADAuthenticationBuilderExtensionsTests
{
[Fact]
public void AddAzureAD_AddsAllAuthenticationHandlers()
{
// Arrange
var services = new ServiceCollection();
services.AddSingleton<ILoggerFactory>(new NullLoggerFactory());
// Act
services.AddAuthentication()
.AddAzureAD(o => { });
var provider = services.BuildServiceProvider();
// Assert
Assert.NotNull(provider.GetService<OpenIdConnectHandler>());
Assert.NotNull(provider.GetService<CookieAuthenticationHandler>());
Assert.NotNull(provider.GetService<PolicySchemeHandler>());
}
[Fact]
public void AddAzureAD_ConfiguresAllOptions()
{
// Arrange
var services = new ServiceCollection();
services.AddSingleton<ILoggerFactory>(new NullLoggerFactory());
// Act
services.AddAuthentication()
.AddAzureAD(o =>
{
o.Instance = "https://login.microsoftonline.com";
o.ClientId = "ClientId";
o.CallbackPath = "/signin-oidc";
o.Domain = "domain.onmicrosoft.com";
o.TenantId = "Common";
});
var provider = services.BuildServiceProvider();
// Assert
var azureADOptionsMonitor = provider.GetService<IOptionsMonitor<AzureADOptions>>();
Assert.NotNull(azureADOptionsMonitor);
var azureADOptions = azureADOptionsMonitor.Get(AzureADDefaults.AuthenticationScheme);
Assert.Equal(AzureADDefaults.OpenIdScheme, azureADOptions.OpenIdConnectSchemeName);
Assert.Equal(AzureADDefaults.CookieScheme, azureADOptions.CookieSchemeName);
Assert.Equal("https://login.microsoftonline.com", azureADOptions.Instance);
Assert.Equal("ClientId", azureADOptions.ClientId);
Assert.Equal("/signin-oidc", azureADOptions.CallbackPath);
Assert.Equal("domain.onmicrosoft.com", azureADOptions.Domain);
var openIdOptionsMonitor = provider.GetService<IOptionsMonitor<OpenIdConnectOptions>>();
Assert.NotNull(openIdOptionsMonitor);
var openIdOptions = openIdOptionsMonitor.Get(AzureADDefaults.OpenIdScheme);
Assert.Equal("ClientId", openIdOptions.ClientId);
Assert.Equal($"https://login.microsoftonline.com/Common", openIdOptions.Authority);
Assert.True(openIdOptions.UseTokenLifetime);
Assert.Equal("/signin-oidc", openIdOptions.CallbackPath);
Assert.Equal(AzureADDefaults.CookieScheme, openIdOptions.SignInScheme);
}
[Fact]
public void AddAzureAD_ThrowsForDuplicatedSchemes()
{
// Arrange
var services = new ServiceCollection();
services.AddSingleton<ILoggerFactory>(new NullLoggerFactory());
services.AddAuthentication()
.AddAzureAD(o => { })
.AddAzureAD(o => { });
var provider = services.BuildServiceProvider();
var azureADOptionsMonitor = provider.GetService<IOptionsMonitor<AzureADOptions>>();
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(
() => azureADOptionsMonitor.Get(AzureADDefaults.AuthenticationScheme));
Assert.Equal("A scheme with the name 'AzureAD' was already added.", exception.Message);
}
[Fact]
public void AddAzureAD_ThrowsWhenOpenIdSchemeIsAlreadyInUse()
{
// Arrange
var services = new ServiceCollection();
services.AddSingleton<ILoggerFactory>(new NullLoggerFactory());
services.AddAuthentication()
.AddAzureAD(o => { })
.AddAzureAD("Custom", AzureADDefaults.OpenIdScheme, "Cookie", null, o => { });
var provider = services.BuildServiceProvider();
var azureADOptionsMonitor = provider.GetService<IOptionsMonitor<AzureADOptions>>();
var expectedMessage = $"The Open ID Connect scheme 'AzureADOpenID' can't be associated with the Azure Active Directory scheme 'Custom'. " +
"The Open ID Connect scheme 'AzureADOpenID' is already mapped to the Azure Active Directory scheme 'AzureAD'";
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(
() => azureADOptionsMonitor.Get(AzureADDefaults.AuthenticationScheme));
Assert.Equal(expectedMessage, exception.Message);
}
[Fact]
public void AddAzureAD_ThrowsWhenCookieSchemeIsAlreadyInUse()
{
// Arrange
var services = new ServiceCollection();
services.AddSingleton<ILoggerFactory>(new NullLoggerFactory());
services.AddAuthentication()
.AddAzureAD(o => { })
.AddAzureAD("Custom", "OpenID", AzureADDefaults.CookieScheme, null, o => { });
var provider = services.BuildServiceProvider();
var azureADOptionsMonitor = provider.GetService<IOptionsMonitor<AzureADOptions>>();
var expectedMessage = $"The cookie scheme 'AzureADCookie' can't be associated with the Azure Active Directory scheme 'Custom'. " +
"The cookie scheme 'AzureADCookie' is already mapped to the Azure Active Directory scheme 'AzureAD'";
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(
() => azureADOptionsMonitor.Get(AzureADDefaults.AuthenticationScheme));
Assert.Equal(expectedMessage, exception.Message);
}
[Fact]
public void AddAzureADBearer_AddsAllAuthenticationHandlers()
{
// Arrange
var services = new ServiceCollection();
services.AddSingleton<ILoggerFactory>(new NullLoggerFactory());
// Act
services.AddAuthentication()
.AddAzureADBearer(o => { });
var provider = services.BuildServiceProvider();
// Assert
Assert.NotNull(provider.GetService<JwtBearerHandler>());
Assert.NotNull(provider.GetService<PolicySchemeHandler>());
}
[Fact]
public void AddAzureADBearer_ConfiguresAllOptions()
{
// Arrange
var services = new ServiceCollection();
services.AddSingleton<ILoggerFactory>(new NullLoggerFactory());
// Act
services.AddAuthentication()
.AddAzureADBearer(o =>
{
o.Instance = "https://login.microsoftonline.com/";
o.ClientId = "ClientId";
o.CallbackPath = "/signin-oidc";
o.Domain = "domain.onmicrosoft.com";
o.TenantId = "TenantId";
});
var provider = services.BuildServiceProvider();
// Assert
var azureADOptionsMonitor = provider.GetService<IOptionsMonitor<AzureADOptions>>();
Assert.NotNull(azureADOptionsMonitor);
var options = azureADOptionsMonitor.Get(AzureADDefaults.BearerAuthenticationScheme);
Assert.Equal(AzureADDefaults.JwtBearerAuthenticationScheme, options.JwtBearerSchemeName);
Assert.Equal("https://login.microsoftonline.com/", options.Instance);
Assert.Equal("ClientId", options.ClientId);
Assert.Equal("domain.onmicrosoft.com", options.Domain);
var bearerOptionsMonitor = provider.GetService<IOptionsMonitor<JwtBearerOptions>>();
Assert.NotNull(bearerOptionsMonitor);
var bearerOptions = bearerOptionsMonitor.Get(AzureADDefaults.JwtBearerAuthenticationScheme);
Assert.Equal("ClientId", bearerOptions.Audience);
Assert.Equal($"https://login.microsoftonline.com/TenantId", bearerOptions.Authority);
}
[Fact]
public void AddAzureADBearer_ThrowsForDuplicatedSchemes()
{
// Arrange
var services = new ServiceCollection();
services.AddSingleton<ILoggerFactory>(new NullLoggerFactory());
services.AddAuthentication()
.AddAzureADBearer(o => { })
.AddAzureADBearer(o => { });
var provider = services.BuildServiceProvider();
var azureADOptionsMonitor = provider.GetService<IOptionsMonitor<AzureADOptions>>();
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(
() => azureADOptionsMonitor.Get(AzureADDefaults.AuthenticationScheme));
Assert.Equal("A scheme with the name 'AzureADBearer' was already added.", exception.Message);
}
[Fact]
public void AddAzureADBearer_ThrowsWhenBearerSchemeIsAlreadyInUse()
{
// Arrange
var services = new ServiceCollection();
services.AddSingleton<ILoggerFactory>(new NullLoggerFactory());
services.AddAuthentication()
.AddAzureADBearer(o => { })
.AddAzureADBearer("Custom", AzureADDefaults.JwtBearerAuthenticationScheme, o => { });
var provider = services.BuildServiceProvider();
var azureADOptionsMonitor = provider.GetService<IOptionsMonitor<AzureADOptions>>();
var expectedMessage = $"The JSON Web Token Bearer scheme 'AzureADJwtBearer' can't be associated with the Azure Active Directory scheme 'Custom'. " +
"The JSON Web Token Bearer scheme 'AzureADJwtBearer' is already mapped to the Azure Active Directory scheme 'AzureADBearer'";
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(
() => azureADOptionsMonitor.Get(AzureADDefaults.AuthenticationScheme));
Assert.Equal(expectedMessage, exception.Message);
}
}
}

View File

@ -0,0 +1,281 @@
// 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 Microsoft.AspNetCore.Authorization;
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Authentication.AzureAD.UI.AzureAD.Controllers.Internal
{
public class AccountControllerTests
{
[Fact]
public void SignInNoScheme_ChallengesAADAzureADDefaultScheme()
{
// Arrange
var controller = new AccountController(
new OptionsMonitor(AzureADDefaults.AuthenticationScheme, new AzureADOptions()
{
OpenIdConnectSchemeName = AzureADDefaults.OpenIdScheme,
CookieSchemeName = AzureADDefaults.CookieScheme
}))
{
Url = new TestUrlHelper("~/", "https://localhost/")
};
// Act
var result = controller.SignIn(null);
// Assert
var challenge = Assert.IsAssignableFrom<ChallengeResult>(result);
var challengedScheme = Assert.Single(challenge.AuthenticationSchemes);
Assert.Equal(AzureADDefaults.AuthenticationScheme, challengedScheme);
Assert.NotNull(challenge.Properties.RedirectUri);
Assert.Equal("https://localhost/", challenge.Properties.RedirectUri);
}
[Fact]
public void SignInProvidedScheme_ChallengesCustomScheme()
{
// Arrange
var controller = new AccountController(new OptionsMonitor("Custom", new AzureADOptions()));
controller.Url = new TestUrlHelper("~/", "https://localhost/");
// Act
var result = controller.SignIn("Custom");
// Assert
var challenge = Assert.IsAssignableFrom<ChallengeResult>(result);
var challengedScheme = Assert.Single(challenge.AuthenticationSchemes);
Assert.Equal("Custom", challengedScheme);
}
private ClaimsPrincipal CreateAuthenticatedPrincipal(string scheme) =>
new ClaimsPrincipal(new ClaimsIdentity(scheme));
private static ControllerContext CreateControllerContext(ClaimsPrincipal principal = null)
{
principal = principal ?? new ClaimsPrincipal(new ClaimsIdentity());
var mock = new Mock<IAuthenticationService>();
mock.Setup(authS => authS.AuthenticateAsync(It.IsAny<HttpContext>(), It.IsAny<string>()))
.ReturnsAsync<HttpContext, string, IAuthenticationService, AuthenticateResult>(
(ctx, scheme) =>
{
if (principal.Identity.IsAuthenticated)
{
return AuthenticateResult.Success(new AuthenticationTicket(principal, scheme));
}
else
{
return AuthenticateResult.NoResult();
}
});
return new ControllerContext()
{
HttpContext = new DefaultHttpContext()
{
RequestServices = new ServiceCollection()
.AddSingleton(mock.Object)
.BuildServiceProvider()
}
};
}
[Fact]
public void SignOutNoScheme_SignsOutDefaultCookiesAndDefaultOpenIDConnectAADAzureADSchemesAsync()
{
// Arrange
var options = new AzureADOptions()
{
CookieSchemeName = AzureADDefaults.CookieScheme,
OpenIdConnectSchemeName = AzureADDefaults.OpenIdScheme
};
var controllerContext = CreateControllerContext(
CreateAuthenticatedPrincipal(AzureADDefaults.AuthenticationScheme));
var descriptor = new PageActionDescriptor()
{
AttributeRouteInfo = new AttributeRouteInfo()
{
Template = "/Account/SignedOut"
}
};
var controller = new AccountController(new OptionsMonitor(AzureADDefaults.AuthenticationScheme, options))
{
Url = new TestUrlHelper(
controllerContext.HttpContext,
new RouteData(),
descriptor,
"/Account/SignedOut",
"https://localhost/Account/SignedOut"),
ControllerContext = new ControllerContext()
{
HttpContext = controllerContext.HttpContext
}
};
controller.Request.Scheme = "https";
// Act
var result = controller.SignOut(null);
// Assert
var signOut = Assert.IsAssignableFrom<SignOutResult>(result);
Assert.Equal(new[] { AzureADDefaults.CookieScheme, AzureADDefaults.OpenIdScheme }, signOut.AuthenticationSchemes);
Assert.NotNull(signOut.Properties.RedirectUri);
Assert.Equal("https://localhost/Account/SignedOut", signOut.Properties.RedirectUri);
}
[Fact]
public void SignOutProvidedScheme_SignsOutCustomCookiesAndCustomOpenIDConnectAADAzureADSchemesAsync()
{
// Arrange
var options = new AzureADOptions()
{
CookieSchemeName = "Cookie",
OpenIdConnectSchemeName = "OpenID"
};
var controllerContext = CreateControllerContext(
CreateAuthenticatedPrincipal(AzureADDefaults.AuthenticationScheme));
var descriptor = new PageActionDescriptor()
{
AttributeRouteInfo = new AttributeRouteInfo()
{
Template = "/Account/SignedOut"
}
};
var controller = new AccountController(new OptionsMonitor("Custom", options))
{
Url = new TestUrlHelper(
controllerContext.HttpContext,
new RouteData(),
descriptor,
"/Account/SignedOut",
"https://localhost/Account/SignedOut"),
ControllerContext = new ControllerContext()
{
HttpContext = controllerContext.HttpContext
}
};
controller.Request.Scheme = "https";
// Act
var result = controller.SignOut("Custom");
// Assert
var signOut = Assert.IsAssignableFrom<SignOutResult>(result);
Assert.Equal(new[] { "Cookie", "OpenID" }, signOut.AuthenticationSchemes);
}
private class OptionsMonitor : IOptionsMonitor<AzureADOptions>
{
public OptionsMonitor(string scheme, AzureADOptions options)
{
Scheme = scheme;
Options = options;
}
public AzureADOptions CurrentValue => throw new NotImplementedException();
public string Scheme { get; }
public AzureADOptions Options { get; }
public AzureADOptions Get(string name)
{
if (name == Scheme)
{
return Options;
}
return null;
}
public IDisposable OnChange(Action<AzureADOptions, string> listener)
{
throw new NotImplementedException();
}
}
private class TestUrlHelper : IUrlHelper
{
public TestUrlHelper(string contentPath, string url)
{
ContentPath = contentPath;
Url = url;
}
public TestUrlHelper(
HttpContext context,
RouteData routeData,
ActionDescriptor descriptor,
string contentPath,
string url)
{
HttpContext = context;
RouteData = routeData;
ActionDescriptor = descriptor;
ContentPath = contentPath;
Url = url;
}
public ActionContext ActionContext =>
new ActionContext(HttpContext, RouteData, ActionDescriptor);
public string ContentPath { get; }
public string Url { get; }
public HttpContext HttpContext { get; }
public RouteData RouteData { get; }
public ActionDescriptor ActionDescriptor { get; }
public string Action(UrlActionContext actionContext)
{
throw new NotImplementedException();
}
public string Content(string contentPath)
{
if (ContentPath == contentPath)
{
return Url;
}
return "";
}
public bool IsLocalUrl(string url)
{
throw new NotImplementedException();
}
public string Link(string routeName, object values)
{
throw new NotImplementedException();
}
public string RouteUrl(UrlRouteContext routeContext)
{
if (routeContext.Values is RouteValueDictionary dicionary &&
dicionary.TryGetValue("page", out var page) &&
page is string pagePath &&
ContentPath == pagePath)
{
return Url;
}
return null;
}
}
}
}

View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
<RootNamespace>Microsoft.AspNetCore.Authentication.AzureAD.UI</RootNamespace>
</PropertyGroup>
<ItemGroup>
<Content Include="xunit.runner.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Authentication.AzureAD.UI\Microsoft.AspNetCore.Authentication.AzureAD.UI.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,3 @@
{
"shadowCopy": false
}