Add Azure AD package + tests
This commit is contained in:
parent
8f42ce8492
commit
f49270d9d6
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
@page
|
||||
@model SignedOutModel
|
||||
@{
|
||||
ViewData["Title"] = "Signed out";
|
||||
}
|
||||
|
||||
<h2>@ViewData["Title"]</h2>
|
||||
<p>
|
||||
You have successfully signed out.
|
||||
</p>
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
@using Microsoft.AspNetCore.Authentication.AzureAD.UI.Internal
|
||||
@namespace Microsoft.AspNetCore.Authentication.AzureAD.UI.Pages.Internal
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
@{
|
||||
Layout = "_Layout";
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
@ -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 };
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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")]
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"shadowCopy": false
|
||||
}
|
||||
Loading…
Reference in New Issue