#43 Add Microsoft.AspNetCore.Authentication.WsFederation, samples, and tests.
#1443 Block unsolicited wsfed logins by default. #1520 Update WsFed to use the 2.0 event structure #1425 Implement WsFed remote signout cleanup Rework WsFed RemoteSignOutPath logic to work with ADFS #1581 Update versions, dependencies.
This commit is contained in:
parent
2b1dab2efe
commit
d95109c96d
40
Security.sln
40
Security.sln
|
|
@ -1,6 +1,6 @@
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio 15
|
# Visual Studio 15
|
||||||
VisualStudioVersion = 15.0.27004.2002
|
VisualStudioVersion = 15.0.27130.2027
|
||||||
MinimumVisualStudioVersion = 15.0.26730.03
|
MinimumVisualStudioVersion = 15.0.26730.03
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4D2B6A51-2F9F-44F5-8131-EA5CAC053652}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4D2B6A51-2F9F-44F5-8131-EA5CAC053652}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
|
@ -75,6 +75,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Author
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CookiePolicySample", "samples\CookiePolicySample\CookiePolicySample.csproj", "{24A28F5D-E5A9-4CA8-B0D2-924A1F8BE14E}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CookiePolicySample", "samples\CookiePolicySample\CookiePolicySample.csproj", "{24A28F5D-E5A9-4CA8-B0D2-924A1F8BE14E}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Authentication.WsFederation", "src\Microsoft.AspNetCore.Authentication.WsFederation\Microsoft.AspNetCore.Authentication.WsFederation.csproj", "{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WsFedSample", "samples\WsFedSample\WsFedSample.csproj", "{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
|
@ -481,6 +485,38 @@ Global
|
||||||
{24A28F5D-E5A9-4CA8-B0D2-924A1F8BE14E}.Release|x64.Build.0 = Release|Any CPU
|
{24A28F5D-E5A9-4CA8-B0D2-924A1F8BE14E}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{24A28F5D-E5A9-4CA8-B0D2-924A1F8BE14E}.Release|x86.ActiveCfg = Release|Any CPU
|
{24A28F5D-E5A9-4CA8-B0D2-924A1F8BE14E}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{24A28F5D-E5A9-4CA8-B0D2-924A1F8BE14E}.Release|x86.Build.0 = Release|Any CPU
|
{24A28F5D-E5A9-4CA8-B0D2-924A1F8BE14E}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
|
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||||
|
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
|
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||||
|
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
@ -511,6 +547,8 @@ Global
|
||||||
{51563775-C659-4907-9BAF-9995BAB87D01} = {7BF11F3A-60B6-4796-B504-579C67FFBA34}
|
{51563775-C659-4907-9BAF-9995BAB87D01} = {7BF11F3A-60B6-4796-B504-579C67FFBA34}
|
||||||
{58194599-F07D-47A3-9DF2-E21A22C5EF9E} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652}
|
{58194599-F07D-47A3-9DF2-E21A22C5EF9E} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652}
|
||||||
{24A28F5D-E5A9-4CA8-B0D2-924A1F8BE14E} = {F8C0AA27-F3FB-4286-8E4C-47EF86B539FF}
|
{24A28F5D-E5A9-4CA8-B0D2-924A1F8BE14E} = {F8C0AA27-F3FB-4286-8E4C-47EF86B539FF}
|
||||||
|
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652}
|
||||||
|
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800} = {F8C0AA27-F3FB-4286-8E4C-47EF86B539FF}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {ABF8089E-43D0-4010-84A7-7A9DCFE49357}
|
SolutionGuid = {ABF8089E-43D0-4010-84A7-7A9DCFE49357}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@
|
||||||
<MicrosoftExtensionsWebEncodersPackageVersion>2.1.0-preview2-30187</MicrosoftExtensionsWebEncodersPackageVersion>
|
<MicrosoftExtensionsWebEncodersPackageVersion>2.1.0-preview2-30187</MicrosoftExtensionsWebEncodersPackageVersion>
|
||||||
<MicrosoftIdentityModelClientsActiveDirectoryPackageVersion>3.14.2</MicrosoftIdentityModelClientsActiveDirectoryPackageVersion>
|
<MicrosoftIdentityModelClientsActiveDirectoryPackageVersion>3.14.2</MicrosoftIdentityModelClientsActiveDirectoryPackageVersion>
|
||||||
<MicrosoftIdentityModelProtocolsOpenIdConnectPackageVersion>5.2.0</MicrosoftIdentityModelProtocolsOpenIdConnectPackageVersion>
|
<MicrosoftIdentityModelProtocolsOpenIdConnectPackageVersion>5.2.0</MicrosoftIdentityModelProtocolsOpenIdConnectPackageVersion>
|
||||||
|
<MicrosoftIdentityModelProtocolsWsFederationPackageVersion>5.2.0</MicrosoftIdentityModelProtocolsWsFederationPackageVersion>
|
||||||
<MicrosoftNETCoreApp20PackageVersion>2.0.0</MicrosoftNETCoreApp20PackageVersion>
|
<MicrosoftNETCoreApp20PackageVersion>2.0.0</MicrosoftNETCoreApp20PackageVersion>
|
||||||
<MicrosoftNETCoreApp21PackageVersion>2.1.0-preview2-26130-04</MicrosoftNETCoreApp21PackageVersion>
|
<MicrosoftNETCoreApp21PackageVersion>2.1.0-preview2-26130-04</MicrosoftNETCoreApp21PackageVersion>
|
||||||
<MicrosoftNETTestSdkPackageVersion>15.6.0</MicrosoftNETTestSdkPackageVersion>
|
<MicrosoftNETTestSdkPackageVersion>15.6.0</MicrosoftNETTestSdkPackageVersion>
|
||||||
|
|
@ -39,6 +40,7 @@
|
||||||
<MicrosoftOwinSecurityPackageVersion>3.0.1</MicrosoftOwinSecurityPackageVersion>
|
<MicrosoftOwinSecurityPackageVersion>3.0.1</MicrosoftOwinSecurityPackageVersion>
|
||||||
<MicrosoftOwinTestingPackageVersion>3.0.1</MicrosoftOwinTestingPackageVersion>
|
<MicrosoftOwinTestingPackageVersion>3.0.1</MicrosoftOwinTestingPackageVersion>
|
||||||
<NewtonsoftJsonPackageVersion>10.0.1</NewtonsoftJsonPackageVersion>
|
<NewtonsoftJsonPackageVersion>10.0.1</NewtonsoftJsonPackageVersion>
|
||||||
|
<SystemIdentityModelTokensJwtPackageVersion>5.2.0</SystemIdentityModelTokensJwtPackageVersion>
|
||||||
<XunitAnalyzersPackageVersion>0.8.0</XunitAnalyzersPackageVersion>
|
<XunitAnalyzersPackageVersion>0.8.0</XunitAnalyzersPackageVersion>
|
||||||
<XunitPackageVersion>2.3.1</XunitPackageVersion>
|
<XunitPackageVersion>2.3.1</XunitPackageVersion>
|
||||||
<XunitRunnerVisualStudioPackageVersion>2.4.0-beta.1.build3945</XunitRunnerVisualStudioPackageVersion>
|
<XunitRunnerVisualStudioPackageVersion>2.4.0-beta.1.build3945</XunitRunnerVisualStudioPackageVersion>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace WsFedSample
|
||||||
|
{
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
var host = new WebHostBuilder()
|
||||||
|
.ConfigureLogging(factory =>
|
||||||
|
{
|
||||||
|
factory.AddConsole();
|
||||||
|
factory.AddDebug();
|
||||||
|
factory.AddFilter("Console", level => level >= LogLevel.Information);
|
||||||
|
factory.AddFilter("Debug", level => level >= LogLevel.Information);
|
||||||
|
})
|
||||||
|
.UseKestrel(options =>
|
||||||
|
{
|
||||||
|
options.Listen(IPAddress.Loopback, 44307, listenOptions =>
|
||||||
|
{
|
||||||
|
// Configure SSL
|
||||||
|
var serverCertificate = LoadCertificate();
|
||||||
|
listenOptions.UseHttps(serverCertificate);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
|
.UseIISIntegration()
|
||||||
|
.UseStartup<Startup>()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
host.Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static X509Certificate2 LoadCertificate()
|
||||||
|
{
|
||||||
|
var assembly = typeof(Startup).GetTypeInfo().Assembly;
|
||||||
|
var embeddedFileProvider = new EmbeddedFileProvider(assembly, "WsFedSample");
|
||||||
|
var certificateFileInfo = embeddedFileProvider.GetFileInfo("compiler/resources/cert.pfx");
|
||||||
|
using (var certificateStream = certificateFileInfo.CreateReadStream())
|
||||||
|
{
|
||||||
|
byte[] certificatePayload;
|
||||||
|
using (var memoryStream = new MemoryStream())
|
||||||
|
{
|
||||||
|
certificateStream.CopyTo(memoryStream);
|
||||||
|
certificatePayload = memoryStream.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new X509Certificate2(certificatePayload, "testPassword");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "https://localhost:44307/",
|
||||||
|
"sslPort": 44318
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "https://localhost:44307/",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"WsFedSample": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"applicationUrl": "https://localhost:44307/",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,168 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
|
using Microsoft.AspNetCore.Authentication.WsFederation;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace WsFedSample
|
||||||
|
{
|
||||||
|
public class Startup
|
||||||
|
{
|
||||||
|
public Startup(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
Configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IConfiguration Configuration { get; }
|
||||||
|
|
||||||
|
// This method gets called by the runtime. Use this method to add services to the container.
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddAuthentication(sharedOptions =>
|
||||||
|
{
|
||||||
|
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
|
||||||
|
sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
|
||||||
|
sharedOptions.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
|
||||||
|
})
|
||||||
|
.AddWsFederation(options =>
|
||||||
|
{
|
||||||
|
options.Wtrealm = "https://Tratcheroutlook.onmicrosoft.com/WsFedSample";
|
||||||
|
options.MetadataAddress = "https://login.windows.net/cdc690f9-b6b8-4023-813a-bae7143d1f87/FederationMetadata/2007-06/FederationMetadata.xml";
|
||||||
|
// options.CallbackPath = "/";
|
||||||
|
// options.SkipUnrecognizedRequests = true;
|
||||||
|
})
|
||||||
|
.AddCookie();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Configure(IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
app.UseDeveloperExceptionPage();
|
||||||
|
app.UseAuthentication();
|
||||||
|
|
||||||
|
app.Run(async context =>
|
||||||
|
{
|
||||||
|
if (context.Request.Path.Equals("/signedout"))
|
||||||
|
{
|
||||||
|
await WriteHtmlAsync(context.Response, async res =>
|
||||||
|
{
|
||||||
|
await res.WriteAsync($"<h1>You have been signed out.</h1>");
|
||||||
|
await res.WriteAsync("<a class=\"btn btn-link\" href=\"/\">Sign In</a>");
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.Request.Path.Equals("/signout"))
|
||||||
|
{
|
||||||
|
await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||||
|
await WriteHtmlAsync(context.Response, async res =>
|
||||||
|
{
|
||||||
|
await context.Response.WriteAsync($"<h1>Signed out {HtmlEncode(context.User.Identity.Name)}</h1>");
|
||||||
|
await context.Response.WriteAsync("<a class=\"btn btn-link\" href=\"/\">Sign In</a>");
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.Request.Path.Equals("/signout-remote"))
|
||||||
|
{
|
||||||
|
// Redirects
|
||||||
|
await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||||
|
await context.SignOutAsync(WsFederationDefaults.AuthenticationScheme, new AuthenticationProperties()
|
||||||
|
{
|
||||||
|
RedirectUri = "/signedout"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.Request.Path.Equals("/Account/AccessDenied"))
|
||||||
|
{
|
||||||
|
await WriteHtmlAsync(context.Response, async res =>
|
||||||
|
{
|
||||||
|
await context.Response.WriteAsync($"<h1>Access Denied for user {HtmlEncode(context.User.Identity.Name)} to resource '{HtmlEncode(context.Request.Query["ReturnUrl"])}'</h1>");
|
||||||
|
await context.Response.WriteAsync("<a class=\"btn btn-link\" href=\"/signout\">Sign Out</a>");
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultAuthenticateScheme causes User to be set
|
||||||
|
var user = context.User;
|
||||||
|
|
||||||
|
// This is what [Authorize] calls
|
||||||
|
// var user = await context.AuthenticateAsync();
|
||||||
|
|
||||||
|
// This is what [Authorize(ActiveAuthenticationSchemes = WsFederationDefaults.AuthenticationScheme)] calls
|
||||||
|
// var user = await context.AuthenticateAsync(WsFederationDefaults.AuthenticationScheme);
|
||||||
|
|
||||||
|
// Not authenticated
|
||||||
|
if (user == null || !user.Identities.Any(identity => identity.IsAuthenticated))
|
||||||
|
{
|
||||||
|
// This is what [Authorize] calls
|
||||||
|
await context.ChallengeAsync();
|
||||||
|
|
||||||
|
// This is what [Authorize(ActiveAuthenticationSchemes = WsFederationDefaults.AuthenticationScheme)] calls
|
||||||
|
// await context.ChallengeAsync(WsFederationDefaults.AuthenticationScheme);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticated, but not authorized
|
||||||
|
if (context.Request.Path.Equals("/restricted") && !user.Identities.Any(identity => identity.HasClaim("special", "true")))
|
||||||
|
{
|
||||||
|
await context.ForbidAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await WriteHtmlAsync(context.Response, async response =>
|
||||||
|
{
|
||||||
|
await response.WriteAsync($"<h1>Hello Authenticated User {HtmlEncode(user.Identity.Name)}</h1>");
|
||||||
|
await response.WriteAsync("<a class=\"btn btn-default\" href=\"/restricted\">Restricted</a>");
|
||||||
|
await response.WriteAsync("<a class=\"btn btn-default\" href=\"/signout\">Sign Out</a>");
|
||||||
|
await response.WriteAsync("<a class=\"btn btn-default\" href=\"/signout-remote\">Sign Out Remote</a>");
|
||||||
|
|
||||||
|
await response.WriteAsync("<h2>Claims:</h2>");
|
||||||
|
await WriteTableHeader(response, new string[] { "Claim Type", "Value" }, context.User.Claims.Select(c => new string[] { c.Type, c.Value }));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task WriteHtmlAsync(HttpResponse response, Func<HttpResponse, Task> writeContent)
|
||||||
|
{
|
||||||
|
var bootstrap = "<link rel=\"stylesheet\" href=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css\" integrity=\"sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u\" crossorigin=\"anonymous\">";
|
||||||
|
|
||||||
|
response.ContentType = "text/html";
|
||||||
|
await response.WriteAsync($"<html><head>{bootstrap}</head><body><div class=\"container\">");
|
||||||
|
await writeContent(response);
|
||||||
|
await response.WriteAsync("</div></body></html>");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task WriteTableHeader(HttpResponse response, IEnumerable<string> columns, IEnumerable<IEnumerable<string>> data)
|
||||||
|
{
|
||||||
|
await response.WriteAsync("<table class=\"table table-condensed\">");
|
||||||
|
await response.WriteAsync("<tr>");
|
||||||
|
foreach (var column in columns)
|
||||||
|
{
|
||||||
|
await response.WriteAsync($"<th>{HtmlEncode(column)}</th>");
|
||||||
|
}
|
||||||
|
await response.WriteAsync("</tr>");
|
||||||
|
foreach (var row in data)
|
||||||
|
{
|
||||||
|
await response.WriteAsync("<tr>");
|
||||||
|
foreach (var column in row)
|
||||||
|
{
|
||||||
|
await response.WriteAsync($"<td>{HtmlEncode(column)}</td>");
|
||||||
|
}
|
||||||
|
await response.WriteAsync("</tr>");
|
||||||
|
}
|
||||||
|
await response.WriteAsync("</table>");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string HtmlEncode(string content) =>
|
||||||
|
string.IsNullOrEmpty(content) ? string.Empty : HtmlEncoder.Default.Encode(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFrameworks>net461;netcoreapp2.0</TargetFrameworks>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Authentication.Cookies\Microsoft.AspNetCore.Authentication.Cookies.csproj" />
|
||||||
|
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Authentication.WsFederation\Microsoft.AspNetCore.Authentication.WsFederation.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="$(MicrosoftAspNetCoreServerIISIntegrationPackageVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Https" Version="$(MicrosoftAspNetCoreServerKestrelHttpsPackageVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="$(MicrosoftExtensionsConfigurationEnvironmentVariablesPackageVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="$(MicrosoftAspNetCoreDiagnosticsPackageVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="$(MicrosoftExtensionsFileProvidersEmbeddedPackageVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="$(MicrosoftExtensionsLoggingDebugPackageVersion)" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Include="compiler\resources\cert.pfx" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,35 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.IdentityModel.Protocols.WsFederation;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Authentication.WsFederation
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The context object used in for <see cref="WsFederationEvents.AuthenticationFailed"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class AuthenticationFailedContext : RemoteAuthenticationContext<WsFederationOptions>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new context object
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context"></param>
|
||||||
|
/// <param name="scheme"></param>
|
||||||
|
/// <param name="options"></param>
|
||||||
|
public AuthenticationFailedContext(HttpContext context, AuthenticationScheme scheme, WsFederationOptions options)
|
||||||
|
: base(context, scheme, options, new AuthenticationProperties())
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="WsFederationMessage"/> from the request, if any.
|
||||||
|
/// </summary>
|
||||||
|
public WsFederationMessage ProtocolMessage { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="Exception"/> that triggered this event.
|
||||||
|
/// </summary>
|
||||||
|
public Exception Exception { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
// 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.Http;
|
||||||
|
using Microsoft.IdentityModel.Protocols.WsFederation;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Authentication.WsFederation
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The context object used for <see cref="WsFederationEvents.MessageReceived"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class MessageReceivedContext : RemoteAuthenticationContext<WsFederationOptions>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new context object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context"></param>
|
||||||
|
/// <param name="scheme"></param>
|
||||||
|
/// <param name="options"></param>
|
||||||
|
/// <param name="properties"></param>
|
||||||
|
public MessageReceivedContext(
|
||||||
|
HttpContext context,
|
||||||
|
AuthenticationScheme scheme,
|
||||||
|
WsFederationOptions options,
|
||||||
|
AuthenticationProperties properties)
|
||||||
|
: base(context, scheme, options, properties) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="WsFederationMessage"/> received on this request.
|
||||||
|
/// </summary>
|
||||||
|
public WsFederationMessage ProtocolMessage { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
// 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.Http;
|
||||||
|
using Microsoft.IdentityModel.Protocols.WsFederation;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Authentication.WsFederation
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// When a user configures the <see cref="WsFederationHandler"/> to be notified prior to redirecting to an IdentityProvider
|
||||||
|
/// an instance of <see cref="RedirectContext"/> is passed to the 'RedirectToAuthenticationEndpoint' or 'RedirectToEndSessionEndpoint' events.
|
||||||
|
/// </summary>
|
||||||
|
public class RedirectContext : PropertiesContext<WsFederationOptions>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new context object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context"></param>
|
||||||
|
/// <param name="scheme"></param>
|
||||||
|
/// <param name="options"></param>
|
||||||
|
/// <param name="properties"></param>
|
||||||
|
public RedirectContext(
|
||||||
|
HttpContext context,
|
||||||
|
AuthenticationScheme scheme,
|
||||||
|
WsFederationOptions options,
|
||||||
|
AuthenticationProperties properties)
|
||||||
|
: base(context, scheme, options, properties) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="WsFederationMessage"/> used to compose the redirect.
|
||||||
|
/// </summary>
|
||||||
|
public WsFederationMessage ProtocolMessage { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If true, will skip any default logic for this redirect.
|
||||||
|
/// </summary>
|
||||||
|
public bool Handled { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Skips any default logic for this redirect.
|
||||||
|
/// </summary>
|
||||||
|
public void HandleResponse() => Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
// 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.Http;
|
||||||
|
using Microsoft.IdentityModel.Protocols.WsFederation;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Authentication.WsFederation
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An event context for RemoteSignOut.
|
||||||
|
/// </summary>
|
||||||
|
public class RemoteSignOutContext : RemoteAuthenticationContext<WsFederationOptions>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context"></param>
|
||||||
|
/// <param name="scheme"></param>
|
||||||
|
/// <param name="options"></param>
|
||||||
|
/// <param name="message"></param>
|
||||||
|
public RemoteSignOutContext(HttpContext context, AuthenticationScheme scheme, WsFederationOptions options, WsFederationMessage message)
|
||||||
|
: base(context, scheme, options, new AuthenticationProperties())
|
||||||
|
=> ProtocolMessage = message;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The signout message.
|
||||||
|
/// </summary>
|
||||||
|
public WsFederationMessage ProtocolMessage { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Security.Claims;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.IdentityModel.Protocols.WsFederation;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Authentication.WsFederation
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This Context can be used to be informed when an 'AuthorizationCode' is redeemed for tokens at the token endpoint.
|
||||||
|
/// </summary>
|
||||||
|
public class SecurityTokenReceivedContext : RemoteAuthenticationContext<WsFederationOptions>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a <see cref="SecurityTokenReceivedContext"/>
|
||||||
|
/// </summary>
|
||||||
|
public SecurityTokenReceivedContext(HttpContext context, AuthenticationScheme scheme, WsFederationOptions options, AuthenticationProperties properties)
|
||||||
|
: base(context, scheme, options, properties)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="WsFederationMessage"/> received on this request.
|
||||||
|
/// </summary>
|
||||||
|
public WsFederationMessage ProtocolMessage { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.IdentityModel.Protocols.WsFederation;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Authentication.WsFederation
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The context object used for <see cref="WsFederationEvents.SecurityTokenValidated"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class SecurityTokenValidatedContext : RemoteAuthenticationContext<WsFederationOptions>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a <see cref="SecurityTokenValidatedContext"/>
|
||||||
|
/// </summary>
|
||||||
|
public SecurityTokenValidatedContext(HttpContext context, AuthenticationScheme scheme, WsFederationOptions options, ClaimsPrincipal principal, AuthenticationProperties properties)
|
||||||
|
: base(context, scheme, options, properties)
|
||||||
|
=> Principal = principal;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="WsFederationMessage"/> received on this request.
|
||||||
|
/// </summary>
|
||||||
|
public WsFederationMessage ProtocolMessage { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="SecurityToken"/> that was validated.
|
||||||
|
/// </summary>
|
||||||
|
public SecurityToken SecurityToken { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Authentication.WsFederation
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies events which the <see cref="WsFederationHandler"></see> invokes to enable developer control over the authentication process. />
|
||||||
|
/// </summary>
|
||||||
|
public class WsFederationEvents : RemoteAuthenticationEvents
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed.
|
||||||
|
/// </summary>
|
||||||
|
public Func<AuthenticationFailedContext, Task> OnAuthenticationFailed { get; set; } = context => Task.CompletedTask;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked when a protocol message is first received.
|
||||||
|
/// </summary>
|
||||||
|
public Func<MessageReceivedContext, Task> OnMessageReceived { get; set; } = context => Task.CompletedTask;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked to manipulate redirects to the identity provider for SignIn, SignOut, or Challenge.
|
||||||
|
/// </summary>
|
||||||
|
public Func<RedirectContext, Task> OnRedirectToIdentityProvider { get; set; } = context => Task.CompletedTask;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked when a wsignoutcleanup request is received at the RemoteSignOutPath endpoint.
|
||||||
|
/// </summary>
|
||||||
|
public Func<RemoteSignOutContext, Task> OnRemoteSignOut { get; set; } = context => Task.CompletedTask;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked with the security token that has been extracted from the protocol message.
|
||||||
|
/// </summary>
|
||||||
|
public Func<SecurityTokenReceivedContext, Task> OnSecurityTokenReceived { get; set; } = context => Task.CompletedTask;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked after the security token has passed validation and a ClaimsIdentity has been generated.
|
||||||
|
/// </summary>
|
||||||
|
public Func<SecurityTokenValidatedContext, Task> OnSecurityTokenValidated { get; set; } = context => Task.CompletedTask;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed.
|
||||||
|
/// </summary>
|
||||||
|
public virtual Task AuthenticationFailed(AuthenticationFailedContext context) => OnAuthenticationFailed(context);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked when a protocol message is first received.
|
||||||
|
/// </summary>
|
||||||
|
public virtual Task MessageReceived(MessageReceivedContext context) => OnMessageReceived(context);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked to manipulate redirects to the identity provider for SignIn, SignOut, or Challenge.
|
||||||
|
/// </summary>
|
||||||
|
public virtual Task RedirectToIdentityProvider(RedirectContext context) => OnRedirectToIdentityProvider(context);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked when a wsignoutcleanup request is received at the RemoteSignOutPath endpoint.
|
||||||
|
/// </summary>
|
||||||
|
public virtual Task RemoteSignOut(RemoteSignOutContext context) => OnRemoteSignOut(context);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked with the security token that has been extracted from the protocol message.
|
||||||
|
/// </summary>
|
||||||
|
public virtual Task SecurityTokenReceived(SecurityTokenReceivedContext context) => OnSecurityTokenReceived(context);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked after the security token has passed validation and a ClaimsIdentity has been generated.
|
||||||
|
/// </summary>
|
||||||
|
public virtual Task SecurityTokenValidated(SecurityTokenValidatedContext context) => OnSecurityTokenValidated(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.Logging
|
||||||
|
{
|
||||||
|
internal static class LoggingExtensions
|
||||||
|
{
|
||||||
|
private static Action<ILogger, Exception> _signInWithoutWresult;
|
||||||
|
private static Action<ILogger, Exception> _signInWithoutToken;
|
||||||
|
private static Action<ILogger, Exception> _exceptionProcessingMessage;
|
||||||
|
private static Action<ILogger, string, Exception> _malformedRedirectUri;
|
||||||
|
private static Action<ILogger, Exception> _remoteSignOutHandledResponse;
|
||||||
|
private static Action<ILogger, Exception> _remoteSignOutSkipped;
|
||||||
|
private static Action<ILogger, Exception> _remoteSignOut;
|
||||||
|
|
||||||
|
static LoggingExtensions()
|
||||||
|
{
|
||||||
|
_signInWithoutWresult = LoggerMessage.Define(
|
||||||
|
eventId: 1,
|
||||||
|
logLevel: LogLevel.Debug,
|
||||||
|
formatString: "Received a sign-in message without a WResult.");
|
||||||
|
_signInWithoutToken = LoggerMessage.Define(
|
||||||
|
eventId: 2,
|
||||||
|
logLevel: LogLevel.Debug,
|
||||||
|
formatString: "Received a sign-in message without a token.");
|
||||||
|
_exceptionProcessingMessage = LoggerMessage.Define(
|
||||||
|
eventId: 3,
|
||||||
|
logLevel: LogLevel.Error,
|
||||||
|
formatString: "Exception occurred while processing message.");
|
||||||
|
_malformedRedirectUri = LoggerMessage.Define<string>(
|
||||||
|
eventId: 4,
|
||||||
|
logLevel: LogLevel.Warning,
|
||||||
|
formatString: "The sign-out redirect URI '{0}' is malformed.");
|
||||||
|
_remoteSignOutHandledResponse = LoggerMessage.Define(
|
||||||
|
eventId: 5,
|
||||||
|
logLevel: LogLevel.Debug,
|
||||||
|
formatString: "RemoteSignOutContext.HandledResponse");
|
||||||
|
_remoteSignOutSkipped = LoggerMessage.Define(
|
||||||
|
eventId: 6,
|
||||||
|
logLevel: LogLevel.Debug,
|
||||||
|
formatString: "RemoteSignOutContext.Skipped");
|
||||||
|
_remoteSignOut = LoggerMessage.Define(
|
||||||
|
eventId: 7,
|
||||||
|
logLevel: LogLevel.Information,
|
||||||
|
formatString: "Remote signout request processed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SignInWithoutWresult(this ILogger logger)
|
||||||
|
{
|
||||||
|
_signInWithoutWresult(logger, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SignInWithoutToken(this ILogger logger)
|
||||||
|
{
|
||||||
|
_signInWithoutToken(logger, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ExceptionProcessingMessage(this ILogger logger, Exception ex)
|
||||||
|
{
|
||||||
|
_exceptionProcessingMessage(logger, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void MalformedRedirectUri(this ILogger logger, string uri)
|
||||||
|
{
|
||||||
|
_malformedRedirectUri(logger, uri, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RemoteSignOutHandledResponse(this ILogger logger)
|
||||||
|
{
|
||||||
|
_remoteSignOutHandledResponse(logger, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RemoteSignOutSkipped(this ILogger logger)
|
||||||
|
{
|
||||||
|
_remoteSignOutSkipped(logger, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RemoteSignOut(this ILogger logger)
|
||||||
|
{
|
||||||
|
_remoteSignOut(logger, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<Description>ASP.NET Core middleware that enables an application to support the WsFederation authentication workflow.</Description>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<PackageTags>aspnetcore;authentication;security</PackageTags>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Microsoft.AspNetCore.Authentication\Microsoft.AspNetCore.Authentication.csproj" />
|
||||||
|
<PackageReference Include="Microsoft.IdentityModel.Protocols.WsFederation" Version="$(MicrosoftIdentityModelProtocolsWsFederationPackageVersion)" />
|
||||||
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="$(SystemIdentityModelTokensJwtPackageVersion)" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
114
src/Microsoft.AspNetCore.Authentication.WsFederation/Properties/Resources.Designer.cs
generated
Normal file
114
src/Microsoft.AspNetCore.Authentication.WsFederation/Properties/Resources.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
// <auto-generated />
|
||||||
|
namespace Microsoft.AspNetCore.Authentication.WsFederation
|
||||||
|
{
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Resources;
|
||||||
|
|
||||||
|
internal static class Resources
|
||||||
|
{
|
||||||
|
private static readonly ResourceManager _resourceManager
|
||||||
|
= new ResourceManager("Microsoft.AspNetCore.Authentication.WsFederation.Resources", typeof(Resources).GetTypeInfo().Assembly);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The service descriptor is missing.
|
||||||
|
/// </summary>
|
||||||
|
internal static string Exception_MissingDescriptor
|
||||||
|
{
|
||||||
|
get => GetString("Exception_MissingDescriptor");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The service descriptor is missing.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatException_MissingDescriptor()
|
||||||
|
=> GetString("Exception_MissingDescriptor");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// No token validator was found for the given token.
|
||||||
|
/// </summary>
|
||||||
|
internal static string Exception_NoTokenValidatorFound
|
||||||
|
{
|
||||||
|
get => GetString("Exception_NoTokenValidatorFound");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// No token validator was found for the given token.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatException_NoTokenValidatorFound()
|
||||||
|
=> GetString("Exception_NoTokenValidatorFound");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The '{0}' option must be provided.
|
||||||
|
/// </summary>
|
||||||
|
internal static string Exception_OptionMustBeProvided
|
||||||
|
{
|
||||||
|
get => GetString("Exception_OptionMustBeProvided");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The '{0}' option must be provided.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatException_OptionMustBeProvided(object p0)
|
||||||
|
=> string.Format(CultureInfo.CurrentCulture, GetString("Exception_OptionMustBeProvided"), p0);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.
|
||||||
|
/// </summary>
|
||||||
|
internal static string Exception_ValidatorHandlerMismatch
|
||||||
|
{
|
||||||
|
get => GetString("Exception_ValidatorHandlerMismatch");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatException_ValidatorHandlerMismatch()
|
||||||
|
=> GetString("Exception_ValidatorHandlerMismatch");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The sign in message does not contain a required token.
|
||||||
|
/// </summary>
|
||||||
|
internal static string SignInMessageTokenIsMissing
|
||||||
|
{
|
||||||
|
get => GetString("SignInMessageTokenIsMissing");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The sign in message does not contain a required token.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatSignInMessageTokenIsMissing()
|
||||||
|
=> GetString("SignInMessageTokenIsMissing");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The sign in message does not contain a required wresult.
|
||||||
|
/// </summary>
|
||||||
|
internal static string SignInMessageWresultIsMissing
|
||||||
|
{
|
||||||
|
get => GetString("SignInMessageWresultIsMissing");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The sign in message does not contain a required wresult.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatSignInMessageWresultIsMissing()
|
||||||
|
=> GetString("SignInMessageWresultIsMissing");
|
||||||
|
|
||||||
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
|
{
|
||||||
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
||||||
|
System.Diagnostics.Debug.Assert(value != null);
|
||||||
|
|
||||||
|
if (formatterNames != null)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < formatterNames.Length; i++)
|
||||||
|
{
|
||||||
|
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="Exception_MissingDescriptor" xml:space="preserve">
|
||||||
|
<value>The service descriptor is missing.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Exception_NoTokenValidatorFound" xml:space="preserve">
|
||||||
|
<value>No token validator was found for the given token.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Exception_OptionMustBeProvided" xml:space="preserve">
|
||||||
|
<value>The '{0}' option must be provided.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Exception_ValidatorHandlerMismatch" xml:space="preserve">
|
||||||
|
<value>An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.</value>
|
||||||
|
</data>
|
||||||
|
<data name="SignInMessageTokenIsMissing" xml:space="preserve">
|
||||||
|
<value>The sign in message does not contain a required token.</value>
|
||||||
|
</data>
|
||||||
|
<data name="SignInMessageWresultIsMissing" xml:space="preserve">
|
||||||
|
<value>The sign in message does not contain a required wresult.</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Authentication.WsFederation
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default values related to WsFederation authentication handler
|
||||||
|
/// </summary>
|
||||||
|
public static class WsFederationDefaults
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The default authentication type used when registering the WsFederationHandler.
|
||||||
|
/// </summary>
|
||||||
|
public const string AuthenticationScheme = "WsFederation";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The default display name used when registering the WsFederationHandler.
|
||||||
|
/// </summary>
|
||||||
|
public const string DisplayName = "WsFederation";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constant used to identify userstate inside AuthenticationProperties that have been serialized in the 'wctx' parameter.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string UserstatePropertiesKey = "WsFederation.Userstate";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Authentication.WsFederation;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.DependencyInjection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extensions for registering the <see cref="WsFederationHandler"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class WsFederationExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Registers the <see cref="WsFederationHandler"/> using the default authentication scheme, display name, and options.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static AuthenticationBuilder AddWsFederation(this AuthenticationBuilder builder)
|
||||||
|
=> builder.AddWsFederation(WsFederationDefaults.AuthenticationScheme, _ => { });
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers the <see cref="WsFederationHandler"/> using the default authentication scheme, display name, and the given options configuration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder"></param>
|
||||||
|
/// <param name="configureOptions">A delegate that configures the <see cref="WsFederationOptions"/>.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static AuthenticationBuilder AddWsFederation(this AuthenticationBuilder builder, Action<WsFederationOptions> configureOptions)
|
||||||
|
=> builder.AddWsFederation(WsFederationDefaults.AuthenticationScheme, configureOptions);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers the <see cref="WsFederationHandler"/> using the given authentication scheme, default display name, and the given options configuration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder"></param>
|
||||||
|
/// <param name="authenticationScheme"></param>
|
||||||
|
/// <param name="configureOptions">A delegate that configures the <see cref="WsFederationOptions"/>.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static AuthenticationBuilder AddWsFederation(this AuthenticationBuilder builder, string authenticationScheme, Action<WsFederationOptions> configureOptions)
|
||||||
|
=> builder.AddWsFederation(authenticationScheme, WsFederationDefaults.DisplayName, configureOptions);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers the <see cref="WsFederationHandler"/> using the given authentication scheme, display name, and options configuration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder"></param>
|
||||||
|
/// <param name="authenticationScheme"></param>
|
||||||
|
/// <param name="displayName"></param>
|
||||||
|
/// <param name="configureOptions">A delegate that configures the <see cref="WsFederationOptions"/>.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static AuthenticationBuilder AddWsFederation(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action<WsFederationOptions> configureOptions)
|
||||||
|
{
|
||||||
|
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<WsFederationOptions>, WsFederationPostConfigureOptions>());
|
||||||
|
return builder.AddRemoteScheme<WsFederationOptions, WsFederationHandler>(authenticationScheme, displayName, configureOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,425 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Microsoft.IdentityModel.Protocols.WsFederation;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Authentication.WsFederation
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A per-request authentication handler for the WsFederation.
|
||||||
|
/// </summary>
|
||||||
|
public class WsFederationHandler : RemoteAuthenticationHandler<WsFederationOptions>, IAuthenticationSignOutHandler
|
||||||
|
{
|
||||||
|
private const string CorrelationProperty = ".xsrf";
|
||||||
|
private WsFederationConfiguration _configuration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new WsFederationAuthenticationHandler
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="options"></param>
|
||||||
|
/// <param name="encoder"></param>
|
||||||
|
/// <param name="clock"></param>
|
||||||
|
/// <param name="logger"></param>
|
||||||
|
public WsFederationHandler(IOptionsMonitor<WsFederationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
|
||||||
|
: base(options, logger, encoder, clock)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The handler calls methods on the events which give the application control at certain points where processing is occurring.
|
||||||
|
/// If it is not provided a default instance is supplied which does nothing when the methods are called.
|
||||||
|
/// </summary>
|
||||||
|
protected new WsFederationEvents Events
|
||||||
|
{
|
||||||
|
get { return (WsFederationEvents)base.Events; }
|
||||||
|
set { base.Events = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the events instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A new instance of the events instance.</returns>
|
||||||
|
protected override Task<object> CreateEventsAsync() => Task.FromResult<object>(new WsFederationEvents());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Overridden to handle remote signout requests
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public override Task<bool> HandleRequestAsync()
|
||||||
|
{
|
||||||
|
// RemoteSignOutPath and CallbackPath may be the same, fall through if the message doesn't match.
|
||||||
|
if (Options.RemoteSignOutPath.HasValue && Options.RemoteSignOutPath == Request.Path && HttpMethods.IsGet(Request.Method)
|
||||||
|
&& string.Equals(Request.Query[WsFederationConstants.WsFederationParameterNames.Wa],
|
||||||
|
WsFederationConstants.WsFederationActions.SignOutCleanup, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
// We've received a remote sign-out request
|
||||||
|
return HandleRemoteSignOutAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.HandleRequestAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles Challenge
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
|
||||||
|
{
|
||||||
|
if (_configuration == null)
|
||||||
|
{
|
||||||
|
_configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the original challenge URI so we can redirect back to it when we're done.
|
||||||
|
if (string.IsNullOrEmpty(properties.RedirectUri))
|
||||||
|
{
|
||||||
|
properties.RedirectUri = CurrentUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
var wsFederationMessage = new WsFederationMessage()
|
||||||
|
{
|
||||||
|
IssuerAddress = _configuration.TokenEndpoint ?? string.Empty,
|
||||||
|
Wtrealm = Options.Wtrealm,
|
||||||
|
Wa = WsFederationConstants.WsFederationActions.SignIn,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(Options.Wreply))
|
||||||
|
{
|
||||||
|
wsFederationMessage.Wreply = Options.Wreply;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
wsFederationMessage.Wreply = BuildRedirectUri(Options.CallbackPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
GenerateCorrelationId(properties);
|
||||||
|
|
||||||
|
var redirectContext = new RedirectContext(Context, Scheme, Options, properties)
|
||||||
|
{
|
||||||
|
ProtocolMessage = wsFederationMessage
|
||||||
|
};
|
||||||
|
await Events.RedirectToIdentityProvider(redirectContext);
|
||||||
|
|
||||||
|
if (redirectContext.Handled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wsFederationMessage = redirectContext.ProtocolMessage;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(wsFederationMessage.Wctx))
|
||||||
|
{
|
||||||
|
properties.Items[WsFederationDefaults.UserstatePropertiesKey] = wsFederationMessage.Wctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
wsFederationMessage.Wctx = Uri.EscapeDataString(Options.StateDataFormat.Protect(properties));
|
||||||
|
|
||||||
|
var redirectUri = wsFederationMessage.CreateSignInUrl();
|
||||||
|
if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute))
|
||||||
|
{
|
||||||
|
Logger.MalformedRedirectUri(redirectUri);
|
||||||
|
}
|
||||||
|
Response.Redirect(redirectUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked to process incoming authentication messages.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected override async Task<HandleRequestResult> HandleRemoteAuthenticateAsync()
|
||||||
|
{
|
||||||
|
WsFederationMessage wsFederationMessage = null;
|
||||||
|
AuthenticationProperties properties = null;
|
||||||
|
|
||||||
|
// assumption: if the ContentType is "application/x-www-form-urlencoded" it should be safe to read as it is small.
|
||||||
|
if (HttpMethods.IsPost(Request.Method)
|
||||||
|
&& !string.IsNullOrEmpty(Request.ContentType)
|
||||||
|
// May have media/type; charset=utf-8, allow partial match.
|
||||||
|
&& Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& Request.Body.CanRead)
|
||||||
|
{
|
||||||
|
var form = await Request.ReadFormAsync();
|
||||||
|
|
||||||
|
wsFederationMessage = new WsFederationMessage(form.Select(pair => new KeyValuePair<string, string[]>(pair.Key, pair.Value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wsFederationMessage == null || !wsFederationMessage.IsSignInMessage)
|
||||||
|
{
|
||||||
|
if (Options.SkipUnrecognizedRequests)
|
||||||
|
{
|
||||||
|
// Not for us?
|
||||||
|
return HandleRequestResult.SkipHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
return HandleRequestResult.Fail("No message.");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Retrieve our cached redirect uri
|
||||||
|
var state = wsFederationMessage.Wctx;
|
||||||
|
// WsFed allows for uninitiated logins, state may be missing. See AllowUnsolicitedLogins.
|
||||||
|
properties = Options.StateDataFormat.Unprotect(state);
|
||||||
|
|
||||||
|
if (properties == null)
|
||||||
|
{
|
||||||
|
if (!Options.AllowUnsolicitedLogins)
|
||||||
|
{
|
||||||
|
return HandleRequestResult.Fail("Unsolicited logins are not allowed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Extract the user state from properties and reset.
|
||||||
|
properties.Items.TryGetValue(WsFederationDefaults.UserstatePropertiesKey, out var userState);
|
||||||
|
wsFederationMessage.Wctx = userState;
|
||||||
|
}
|
||||||
|
|
||||||
|
var messageReceivedContext = new MessageReceivedContext(Context, Scheme, Options, properties)
|
||||||
|
{
|
||||||
|
ProtocolMessage = wsFederationMessage
|
||||||
|
};
|
||||||
|
await Events.MessageReceived(messageReceivedContext);
|
||||||
|
if (messageReceivedContext.Result != null)
|
||||||
|
{
|
||||||
|
return messageReceivedContext.Result;
|
||||||
|
}
|
||||||
|
wsFederationMessage = messageReceivedContext.ProtocolMessage;
|
||||||
|
properties = messageReceivedContext.Properties; // Provides a new instance if not set.
|
||||||
|
|
||||||
|
// If state did flow from the challenge then validate it. See AllowUnsolicitedLogins above.
|
||||||
|
if (properties.Items.TryGetValue(CorrelationProperty, out string correlationId)
|
||||||
|
&& !ValidateCorrelationId(properties))
|
||||||
|
{
|
||||||
|
return HandleRequestResult.Fail("Correlation failed.", properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wsFederationMessage.Wresult == null)
|
||||||
|
{
|
||||||
|
Logger.SignInWithoutWresult();
|
||||||
|
return HandleRequestResult.Fail(Resources.SignInMessageWresultIsMissing, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
var token = wsFederationMessage.GetToken();
|
||||||
|
if (string.IsNullOrEmpty(token))
|
||||||
|
{
|
||||||
|
Logger.SignInWithoutToken();
|
||||||
|
return HandleRequestResult.Fail(Resources.SignInMessageTokenIsMissing, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
var securityTokenReceivedContext = new SecurityTokenReceivedContext(Context, Scheme, Options, properties)
|
||||||
|
{
|
||||||
|
ProtocolMessage = wsFederationMessage
|
||||||
|
};
|
||||||
|
await Events.SecurityTokenReceived(securityTokenReceivedContext);
|
||||||
|
if (securityTokenReceivedContext.Result != null)
|
||||||
|
{
|
||||||
|
return securityTokenReceivedContext.Result;
|
||||||
|
}
|
||||||
|
wsFederationMessage = securityTokenReceivedContext.ProtocolMessage;
|
||||||
|
properties = messageReceivedContext.Properties;
|
||||||
|
|
||||||
|
if (_configuration == null)
|
||||||
|
{
|
||||||
|
_configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy and augment to avoid cross request race conditions for updated configurations.
|
||||||
|
var tvp = Options.TokenValidationParameters.Clone();
|
||||||
|
var issuers = new[] { _configuration.Issuer };
|
||||||
|
tvp.ValidIssuers = (tvp.ValidIssuers == null ? issuers : tvp.ValidIssuers.Concat(issuers));
|
||||||
|
tvp.IssuerSigningKeys = (tvp.IssuerSigningKeys == null ? _configuration.SigningKeys : tvp.IssuerSigningKeys.Concat(_configuration.SigningKeys));
|
||||||
|
|
||||||
|
ClaimsPrincipal principal = null;
|
||||||
|
SecurityToken parsedToken = null;
|
||||||
|
foreach (var validator in Options.SecurityTokenHandlers)
|
||||||
|
{
|
||||||
|
if (validator.CanReadToken(token))
|
||||||
|
{
|
||||||
|
principal = validator.ValidateToken(token, tvp, out parsedToken);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (principal == null)
|
||||||
|
{
|
||||||
|
throw new SecurityTokenException(Resources.Exception_NoTokenValidatorFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Options.UseTokenLifetime && parsedToken != null)
|
||||||
|
{
|
||||||
|
// Override any session persistence to match the token lifetime.
|
||||||
|
var issued = parsedToken.ValidFrom;
|
||||||
|
if (issued != DateTime.MinValue)
|
||||||
|
{
|
||||||
|
properties.IssuedUtc = issued.ToUniversalTime();
|
||||||
|
}
|
||||||
|
var expires = parsedToken.ValidTo;
|
||||||
|
if (expires != DateTime.MinValue)
|
||||||
|
{
|
||||||
|
properties.ExpiresUtc = expires.ToUniversalTime();
|
||||||
|
}
|
||||||
|
properties.AllowRefresh = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var securityTokenValidatedContext = new SecurityTokenValidatedContext(Context, Scheme, Options, principal, properties)
|
||||||
|
{
|
||||||
|
ProtocolMessage = wsFederationMessage,
|
||||||
|
SecurityToken = parsedToken,
|
||||||
|
};
|
||||||
|
|
||||||
|
await Events.SecurityTokenValidated(securityTokenValidatedContext);
|
||||||
|
if (securityTokenValidatedContext.Result != null)
|
||||||
|
{
|
||||||
|
return securityTokenValidatedContext.Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flow possible changes
|
||||||
|
principal = securityTokenValidatedContext.Principal;
|
||||||
|
properties = securityTokenValidatedContext.Properties;
|
||||||
|
|
||||||
|
return HandleRequestResult.Success(new AuthenticationTicket(principal, properties, Scheme.Name));
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
Logger.ExceptionProcessingMessage(exception);
|
||||||
|
|
||||||
|
// Refresh the configuration for exceptions that may be caused by key rollovers. The user can also request a refresh in the notification.
|
||||||
|
if (Options.RefreshOnIssuerKeyNotFound && exception.GetType().Equals(typeof(SecurityTokenSignatureKeyNotFoundException)))
|
||||||
|
{
|
||||||
|
Options.ConfigurationManager.RequestRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options)
|
||||||
|
{
|
||||||
|
ProtocolMessage = wsFederationMessage,
|
||||||
|
Exception = exception
|
||||||
|
};
|
||||||
|
await Events.AuthenticationFailed(authenticationFailedContext);
|
||||||
|
if (authenticationFailedContext.Result != null)
|
||||||
|
{
|
||||||
|
return authenticationFailedContext.Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HandleRequestResult.Fail(exception, properties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles Signout
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async virtual Task SignOutAsync(AuthenticationProperties properties)
|
||||||
|
{
|
||||||
|
var target = ResolveTarget(Options.ForwardSignOut);
|
||||||
|
if (target != null)
|
||||||
|
{
|
||||||
|
await Context.SignOutAsync(target, properties);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_configuration == null)
|
||||||
|
{
|
||||||
|
_configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
|
||||||
|
}
|
||||||
|
|
||||||
|
var wsFederationMessage = new WsFederationMessage()
|
||||||
|
{
|
||||||
|
IssuerAddress = _configuration.TokenEndpoint ?? string.Empty,
|
||||||
|
Wtrealm = Options.Wtrealm,
|
||||||
|
Wa = WsFederationConstants.WsFederationActions.SignOut,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set Wreply in order:
|
||||||
|
// 1. properties.Redirect
|
||||||
|
// 2. Options.SignOutWreply
|
||||||
|
// 3. Options.Wreply
|
||||||
|
if (properties != null && !string.IsNullOrEmpty(properties.RedirectUri))
|
||||||
|
{
|
||||||
|
wsFederationMessage.Wreply = BuildRedirectUriIfRelative(properties.RedirectUri);
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(Options.SignOutWreply))
|
||||||
|
{
|
||||||
|
wsFederationMessage.Wreply = BuildRedirectUriIfRelative(Options.SignOutWreply);
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(Options.Wreply))
|
||||||
|
{
|
||||||
|
wsFederationMessage.Wreply = BuildRedirectUriIfRelative(Options.Wreply);
|
||||||
|
}
|
||||||
|
|
||||||
|
var redirectContext = new RedirectContext(Context, Scheme, Options, properties)
|
||||||
|
{
|
||||||
|
ProtocolMessage = wsFederationMessage
|
||||||
|
};
|
||||||
|
await Events.RedirectToIdentityProvider(redirectContext);
|
||||||
|
|
||||||
|
if (!redirectContext.Handled)
|
||||||
|
{
|
||||||
|
var redirectUri = redirectContext.ProtocolMessage.CreateSignOutUrl();
|
||||||
|
if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute))
|
||||||
|
{
|
||||||
|
Logger.MalformedRedirectUri(redirectUri);
|
||||||
|
}
|
||||||
|
Response.Redirect(redirectUri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles wsignoutcleanup1.0 messages sent to the RemoteSignOutPath
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected virtual async Task<bool> HandleRemoteSignOutAsync()
|
||||||
|
{
|
||||||
|
var message = new WsFederationMessage(Request.Query.Select(pair => new KeyValuePair<string, string[]>(pair.Key, pair.Value)));
|
||||||
|
var remoteSignOutContext = new RemoteSignOutContext(Context, Scheme, Options, message);
|
||||||
|
await Events.RemoteSignOut(remoteSignOutContext);
|
||||||
|
|
||||||
|
if (remoteSignOutContext.Result != null)
|
||||||
|
{
|
||||||
|
if (remoteSignOutContext.Result.Handled)
|
||||||
|
{
|
||||||
|
Logger.RemoteSignOutHandledResponse();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (remoteSignOutContext.Result.Skipped)
|
||||||
|
{
|
||||||
|
Logger.RemoteSignOutSkipped();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.RemoteSignOut();
|
||||||
|
|
||||||
|
await Context.SignOutAsync(Options.SignOutScheme);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Build a redirect path if the given path is a relative path.
|
||||||
|
/// </summary>
|
||||||
|
private string BuildRedirectUriIfRelative(string uri)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(uri))
|
||||||
|
{
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!uri.StartsWith("/", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
return BuildRedirectUri(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,180 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.IdentityModel.Protocols;
|
||||||
|
using Microsoft.IdentityModel.Protocols.WsFederation;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using Microsoft.IdentityModel.Tokens.Saml;
|
||||||
|
using Microsoft.IdentityModel.Tokens.Saml2;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Authentication.WsFederation
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Configuration options for <see cref="WsFederationHandler"/>
|
||||||
|
/// </summary>
|
||||||
|
public class WsFederationOptions : RemoteAuthenticationOptions
|
||||||
|
{
|
||||||
|
private ICollection<ISecurityTokenValidator> _securityTokenHandlers = new Collection<ISecurityTokenValidator>()
|
||||||
|
{
|
||||||
|
new Saml2SecurityTokenHandler(),
|
||||||
|
new SamlSecurityTokenHandler(),
|
||||||
|
new JwtSecurityTokenHandler()
|
||||||
|
};
|
||||||
|
private TokenValidationParameters _tokenValidationParameters = new TokenValidationParameters();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new <see cref="WsFederationOptions"/>
|
||||||
|
/// </summary>
|
||||||
|
public WsFederationOptions()
|
||||||
|
{
|
||||||
|
CallbackPath = "/signin-wsfed";
|
||||||
|
// In ADFS the cleanup messages are sent to the same callback path as the initial login.
|
||||||
|
// In AAD it sends the cleanup message to a random Reply Url and there's no deterministic way to configure it.
|
||||||
|
// If you manage to get it configured, then you can set RemoteSignOutPath accordingly.
|
||||||
|
RemoteSignOutPath = "/signin-wsfed";
|
||||||
|
Events = new WsFederationEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check that the options are valid. Should throw an exception if things are not ok.
|
||||||
|
/// </summary>
|
||||||
|
public override void Validate()
|
||||||
|
{
|
||||||
|
base.Validate();
|
||||||
|
|
||||||
|
if (ConfigurationManager == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Provide {nameof(MetadataAddress)}, "
|
||||||
|
+ $"{nameof(Configuration)}, or {nameof(ConfigurationManager)} to {nameof(WsFederationOptions)}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configuration provided directly by the developer. If provided, then MetadataAddress and the Backchannel properties
|
||||||
|
/// will not be used. This information should not be updated during request processing.
|
||||||
|
/// </summary>
|
||||||
|
public WsFederationConfiguration Configuration { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the address to retrieve the wsFederation metadata
|
||||||
|
/// </summary>
|
||||||
|
public string MetadataAddress { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Responsible for retrieving, caching, and refreshing the configuration from metadata.
|
||||||
|
/// If not provided, then one will be created using the MetadataAddress and Backchannel properties.
|
||||||
|
/// </summary>
|
||||||
|
public IConfigurationManager<WsFederationConfiguration> ConfigurationManager { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets if a metadata refresh should be attempted after a SecurityTokenSignatureKeyNotFoundException. This allows for automatic
|
||||||
|
/// recovery in the event of a signature key rollover. This is enabled by default.
|
||||||
|
/// </summary>
|
||||||
|
public bool RefreshOnIssuerKeyNotFound { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates if requests to the CallbackPath may also be for other components. If enabled the handler will pass
|
||||||
|
/// requests through that do not contain WsFederation authentication responses. Disabling this and setting the
|
||||||
|
/// CallbackPath to a dedicated endpoint may provide better error handling.
|
||||||
|
/// This is disabled by default.
|
||||||
|
/// </summary>
|
||||||
|
public bool SkipUnrecognizedRequests { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the <see cref="WsFederationEvents"/> to call when processing WsFederation messages.
|
||||||
|
/// </summary>
|
||||||
|
public new WsFederationEvents Events
|
||||||
|
{
|
||||||
|
get => (WsFederationEvents)base.Events;
|
||||||
|
set => base.Events = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the collection of <see cref="ISecurityTokenValidator"/> used to read and validate the <see cref="SecurityToken"/>s.
|
||||||
|
/// </summary>
|
||||||
|
public ICollection<ISecurityTokenValidator> SecurityTokenHandlers
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _securityTokenHandlers;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_securityTokenHandlers = value ?? throw new ArgumentNullException(nameof(SecurityTokenHandlers));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the type used to secure data handled by the middleware.
|
||||||
|
/// </summary>
|
||||||
|
public ISecureDataFormat<AuthenticationProperties> StateDataFormat { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the <see cref="TokenValidationParameters"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ArgumentNullException"> if 'TokenValidationParameters' is null.</exception>
|
||||||
|
public TokenValidationParameters TokenValidationParameters
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _tokenValidationParameters;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_tokenValidationParameters = value ?? throw new ArgumentNullException(nameof(TokenValidationParameters));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the 'wreply'. CallbackPath must be set to match or cleared so it can be generated dynamically.
|
||||||
|
/// This field is optional. If not set then it will be generated from the current request and the CallbackPath.
|
||||||
|
/// </summary>
|
||||||
|
public string Wreply { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the 'wreply' value used during sign-out.
|
||||||
|
/// If none is specified then the value from the Wreply field is used.
|
||||||
|
/// </summary>
|
||||||
|
public string SignOutWreply { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the 'wtrealm'.
|
||||||
|
/// </summary>
|
||||||
|
public string Wtrealm { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the authentication session lifetime (e.g. cookies) should match that of the authentication token.
|
||||||
|
/// If the token does not provide lifetime information then normal session lifetimes will be used.
|
||||||
|
/// This is enabled by default.
|
||||||
|
/// </summary>
|
||||||
|
public bool UseTokenLifetime { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets if HTTPS is required for the metadata address or authority.
|
||||||
|
/// The default is true. This should be disabled only in development environments.
|
||||||
|
/// </summary>
|
||||||
|
public bool RequireHttpsMetadata { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Ws-Federation protocol allows the user to initiate logins without contacting the application for a Challenge first.
|
||||||
|
/// However, that flow is susceptible to XSRF and other attacks so it is disabled here by default.
|
||||||
|
/// </summary>
|
||||||
|
public bool AllowUnsolicitedLogins { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Requests received on this path will cause the handler to invoke SignOut using the SignOutScheme.
|
||||||
|
/// </summary>
|
||||||
|
public PathString RemoteSignOutPath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Authentication Scheme to use with SignOutAsync from RemoteSignOutPath. SignInScheme will be used if this
|
||||||
|
/// is not set.
|
||||||
|
/// </summary>
|
||||||
|
public string SignOutScheme { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Net.Http;
|
||||||
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Microsoft.IdentityModel.Protocols;
|
||||||
|
using Microsoft.IdentityModel.Protocols.WsFederation;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Authentication.WsFederation
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used to setup defaults for all <see cref="WsFederationOptions"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class WsFederationPostConfigureOptions : IPostConfigureOptions<WsFederationOptions>
|
||||||
|
{
|
||||||
|
private readonly IDataProtectionProvider _dp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dataProtection"></param>
|
||||||
|
public WsFederationPostConfigureOptions(IDataProtectionProvider dataProtection)
|
||||||
|
{
|
||||||
|
_dp = dataProtection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked to post configure a TOptions instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name of the options instance being configured.</param>
|
||||||
|
/// <param name="options">The options instance to configure.</param>
|
||||||
|
public void PostConfigure(string name, WsFederationOptions options)
|
||||||
|
{
|
||||||
|
options.DataProtectionProvider = options.DataProtectionProvider ?? _dp;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(options.SignOutScheme))
|
||||||
|
{
|
||||||
|
options.SignOutScheme = options.SignInScheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.StateDataFormat == null)
|
||||||
|
{
|
||||||
|
var dataProtector = options.DataProtectionProvider.CreateProtector(
|
||||||
|
typeof(WsFederationHandler).FullName, name, "v1");
|
||||||
|
options.StateDataFormat = new PropertiesDataFormat(dataProtector);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.CallbackPath.HasValue && !string.IsNullOrEmpty(options.Wreply) && Uri.TryCreate(options.Wreply, UriKind.Absolute, out var wreply))
|
||||||
|
{
|
||||||
|
// Wreply must be a very specific, case sensitive value, so we can't generate it. Instead we generate CallbackPath from it.
|
||||||
|
options.CallbackPath = PathString.FromUriComponent(wreply);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(options.TokenValidationParameters.ValidAudience))
|
||||||
|
{
|
||||||
|
options.TokenValidationParameters.ValidAudience = options.Wtrealm;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.Backchannel == null)
|
||||||
|
{
|
||||||
|
options.Backchannel = new HttpClient(options.BackchannelHttpHandler ?? new HttpClientHandler());
|
||||||
|
options.Backchannel.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft ASP.NET Core WsFederation handler");
|
||||||
|
options.Backchannel.Timeout = options.BackchannelTimeout;
|
||||||
|
options.Backchannel.MaxResponseContentBufferSize = 1024 * 1024 * 10; // 10 MB
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.ConfigurationManager == null)
|
||||||
|
{
|
||||||
|
if (options.Configuration != null)
|
||||||
|
{
|
||||||
|
options.ConfigurationManager = new StaticConfigurationManager<WsFederationConfiguration>(options.Configuration);
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(options.MetadataAddress))
|
||||||
|
{
|
||||||
|
if (options.RequireHttpsMetadata && !options.MetadataAddress.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("The MetadataAddress must use HTTPS unless disabled for development by setting RequireHttpsMetadata=false.");
|
||||||
|
}
|
||||||
|
|
||||||
|
options.ConfigurationManager = new ConfigurationManager<WsFederationConfiguration>(options.MetadataAddress, new WsFederationConfigurationRetriever(),
|
||||||
|
new HttpDocumentRetriever(options.Backchannel) { RequireHttps = options.RequireHttpsMetadata });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,24 @@
|
||||||
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
|
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="WsFederation\federationmetadata.xml" />
|
||||||
|
<None Remove="WsFederation\InvalidToken.xml" />
|
||||||
|
<None Remove="WsFederation\ValidToken.xml" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="WsFederation\federationmetadata.xml">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Include="WsFederation\InvalidToken.xml">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Include="WsFederation\ValidToken.xml">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Authentication.Cookies\Microsoft.AspNetCore.Authentication.Cookies.csproj" />
|
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Authentication.Cookies\Microsoft.AspNetCore.Authentication.Cookies.csproj" />
|
||||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Authentication.Facebook\Microsoft.AspNetCore.Authentication.Facebook.csproj" />
|
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Authentication.Facebook\Microsoft.AspNetCore.Authentication.Facebook.csproj" />
|
||||||
|
|
@ -12,6 +30,7 @@
|
||||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Authentication.MicrosoftAccount\Microsoft.AspNetCore.Authentication.MicrosoftAccount.csproj" />
|
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Authentication.MicrosoftAccount\Microsoft.AspNetCore.Authentication.MicrosoftAccount.csproj" />
|
||||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Authentication.OpenIdConnect\Microsoft.AspNetCore.Authentication.OpenIdConnect.csproj" />
|
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Authentication.OpenIdConnect\Microsoft.AspNetCore.Authentication.OpenIdConnect.csproj" />
|
||||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Authentication.Twitter\Microsoft.AspNetCore.Authentication.Twitter.csproj" />
|
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Authentication.Twitter\Microsoft.AspNetCore.Authentication.Twitter.csproj" />
|
||||||
|
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Authentication.WsFederation\Microsoft.AspNetCore.Authentication.WsFederation.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Authentication.WsFederation
|
||||||
|
{
|
||||||
|
public class CustomStateDataFormat : ISecureDataFormat<AuthenticationProperties>
|
||||||
|
{
|
||||||
|
public const string ValidStateData = "ValidStateData";
|
||||||
|
|
||||||
|
private string lastSavedAuthenticationProperties;
|
||||||
|
private DataContractSerializer serializer = new DataContractSerializer(typeof(AuthenticationProperties));
|
||||||
|
|
||||||
|
public string Protect(AuthenticationProperties data)
|
||||||
|
{
|
||||||
|
lastSavedAuthenticationProperties = Serialize(data);
|
||||||
|
return ValidStateData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Protect(AuthenticationProperties data, string purpose)
|
||||||
|
{
|
||||||
|
return Protect(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthenticationProperties Unprotect(string state)
|
||||||
|
{
|
||||||
|
return state == ValidStateData ? DeSerialize(lastSavedAuthenticationProperties) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthenticationProperties Unprotect(string protectedText, string purpose)
|
||||||
|
{
|
||||||
|
return Unprotect(protectedText);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string Serialize(AuthenticationProperties data)
|
||||||
|
{
|
||||||
|
using (MemoryStream memoryStream = new MemoryStream())
|
||||||
|
{
|
||||||
|
serializer.WriteObject(memoryStream, data);
|
||||||
|
memoryStream.Position = 0;
|
||||||
|
return new StreamReader(memoryStream).ReadToEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private AuthenticationProperties DeSerialize(string state)
|
||||||
|
{
|
||||||
|
var stateDataAsBytes = Encoding.UTF8.GetBytes(state);
|
||||||
|
|
||||||
|
using (var ms = new MemoryStream(stateDataAsBytes, false))
|
||||||
|
{
|
||||||
|
return (AuthenticationProperties)serializer.ReadObject(ms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
<t:RequestSecurityTokenResponse Context="WsFedOwinState=AQAAANCMnd8BFdERjHoAwE_Cl-sBAAAAzaTmu3688ESVbKJen1i8YwAAAAACAAAAAAADZgAAwAAAABAAAADoUPrFjHqMTp30emvI0XZ_AAAAAASAAACgAAAAEAAAAGTBC8oT24BI8BSJf4SbwjowAAAAA4ip7JyKg6vyK-PtWTapIASA3XLOXiIj8KFO3cuSd4t4H4o-W_wnQl2FAKMOKNNrFAAAAEoWRHnCSYvPKPo0kU09EciG6TJS" xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
|
||||||
|
<t:Lifetime>
|
||||||
|
<wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2014-04-18T20:21:17.341Z</wsu:Created>
|
||||||
|
<wsu:Expires xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2014-04-19T08:21:17.341Z</wsu:Expires>
|
||||||
|
</t:Lifetime>
|
||||||
|
<wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
|
||||||
|
<EndpointReference xmlns="http://www.w3.org/2005/08/addressing">
|
||||||
|
<Address>http://automation1/</Address>
|
||||||
|
</EndpointReference>
|
||||||
|
</wsp:AppliesTo>
|
||||||
|
<t:RequestedSecurityToken>
|
||||||
|
<Assertion ID="_660ec874-f70a-4997-a9c4-bd591f1c7469" IssueInstant="2014-04-18T20:21:17.450Z" Version="2.0" xmlns="urn:oasis:names:tc:SAML:2.0:assertion">
|
||||||
|
<Issuer>https://sts.windows.net/4afbc689-805b-48cf-a24c-d4aa3248a248/</Issuer>
|
||||||
|
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
|
||||||
|
<ds:SignedInfo>
|
||||||
|
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
|
||||||
|
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" />
|
||||||
|
<ds:Reference URI="#_660ec874-f70a-4997-a9c4-bd591f1c7469">
|
||||||
|
<ds:Transforms>
|
||||||
|
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
|
||||||
|
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
|
||||||
|
</ds:Transforms>
|
||||||
|
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
|
||||||
|
<ds:DigestValue>Lkq0wTyTFxLUU2cyx0XybJqhka5RzRGj6kC4aIpFg+g=</ds:DigestValue>
|
||||||
|
</ds:Reference>
|
||||||
|
</ds:SignedInfo>
|
||||||
|
<ds:SignatureValue>bPwNswOB/B9xcdAljIkin9A2vjq+u94JdyvK03mf8vZFGUYNu9uN/Q6ims1DvW1FnP7SgFBwhIvW5OjZyW8fdYGhC2bq36izkxH6ulkWbciOcyELkyHDACLudvh8kP/Q+IwpicefKzAeI2Qu/5MFq16vFg5YgI+dovg8u1fYPPEPmmptW893RNTHWeh9mLRpLYnHyg7aLG6emNRkEu7w9rzeoICeMFybb9BvJl/q/8MFCW/Z5WemQhCi6YXFSEwCO6zJzCFi/3T6ChU/xYgXbFykDLqulsNOCQxdgutyqxJzugt+3PH5IKHHuoqe7UZNUIyELJ4BgwE1sXCGYIi24rg==</ds:SignatureValue>
|
||||||
|
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
|
||||||
|
<X509Data>
|
||||||
|
<X509Certificate>MIIDPjCCAiqgAwIBAgIQVWmXY/+9RqFA/OG9kFulHDAJBgUrDgMCHQUAMC0xKzApBgNVBAMTImFjY291bnRzLmFjY2Vzc2NvbnRyb2wud2luZG93cy5uZXQwHhcNMTIwNjA3MDcwMDAwWhcNMTQwNjA3MDcwMDAwWjAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArCz8Sn3GGXmikH2MdTeGY1D711EORX/lVXpr+ecGgqfUWF8MPB07XkYuJ54DAuYT318+2XrzMjOtqkT94VkXmxv6dFGhG8YZ8vNMPd4tdj9c0lpvWQdqXtL1TlFRpD/P6UMEigfN0c9oWDg9U7Ilymgei0UXtf1gtcQbc5sSQU0S4vr9YJp2gLFIGK11Iqg4XSGdcI0QWLLkkC6cBukhVnd6BCYbLjTYy3fNs4DzNdemJlxGl8sLexFytBF6YApvSdus3nFXaMCtBGx16HzkK9ne3lobAwL2o79bP4imEGqg+ibvyNmbrwFGnQrBc1jTF9LyQX9q+louxVfHs6ZiVwIDAQABo2IwYDBeBgNVHQEEVzBVgBCxDDsLd8xkfOLKm4Q/SzjtoS8wLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNzY29udHJvbC53aW5kb3dzLm5ldIIQVWmXY/+9RqFA/OG9kFulHDAJBgUrDgMCHQUAA4IBAQAkJtxxm/ErgySlNk69+1odTMP8Oy6L0H17z7XGG3w4TqvTUSWaxD4hSFJ0e7mHLQLQD7oV/erACXwSZn2pMoZ89MBDjOMQA+e6QzGB7jmSzPTNmQgMLA8fWCfqPrz6zgH+1F1gNp8hJY57kfeVPBiyjuBmlTEBsBlzolY9dd/55qqfQk6cgSeCbHCy/RU/iep0+UsRMlSgPNNmqhj5gmN2AFVCN96zF694LwuPae5CeR2ZcVknexOWHYjFM0MgUSw0ubnGl0h9AJgGyhvNGcjQqu9vd1xkupFgaN+f7P3p3EVN5csBg5H94jEcQZT7EKeTiZ6bTrpDAnrr8tDCy8ng</X509Certificate>
|
||||||
|
</X509Data>
|
||||||
|
</KeyInfo>
|
||||||
|
</ds:Signature>
|
||||||
|
<Subject>
|
||||||
|
<NameID>t0ch1TsP0pi5VoW8q5CGWsCXVZoNtpsg0mbMZPOYb4I</NameID>
|
||||||
|
<SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer" />
|
||||||
|
</Subject>
|
||||||
|
<Conditions NotBefore="2014-04-18T20:21:17.341Z" NotOnOrAfter="2014-04-19T08:21:17.341Z">
|
||||||
|
<AudienceRestriction>
|
||||||
|
<Audience>http://Automation1</Audience>
|
||||||
|
</AudienceRestriction>
|
||||||
|
</Conditions>
|
||||||
|
<AttributeStatement>
|
||||||
|
<Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname">
|
||||||
|
<AttributeValue>Test</AttributeValue>
|
||||||
|
</Attribute>
|
||||||
|
<Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname">
|
||||||
|
<AttributeValue>Test</AttributeValue>
|
||||||
|
</Attribute>
|
||||||
|
<Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name">
|
||||||
|
<AttributeValue>user1@praburajgmail.onmicrosoft.com</AttributeValue>
|
||||||
|
</Attribute>
|
||||||
|
<Attribute Name="http://schemas.microsoft.com/identity/claims/tenantid">
|
||||||
|
<AttributeValue>4afbc689-805b-48cf-a24c-d4aa3248a248</AttributeValue>
|
||||||
|
</Attribute>
|
||||||
|
<Attribute Name="http://schemas.microsoft.com/identity/claims/objectidentifier">
|
||||||
|
<AttributeValue>c2f0cd49-5e53-4520-8ed9-4e178dc488c5</AttributeValue>
|
||||||
|
</Attribute>
|
||||||
|
<Attribute Name="http://schemas.microsoft.com/identity/claims/identityprovider">
|
||||||
|
<AttributeValue>https://sts.windows.net/4afbc689-805b-48cf-a24c-d4aa3248a248/</AttributeValue>
|
||||||
|
</Attribute>
|
||||||
|
</AttributeStatement>
|
||||||
|
<AuthnStatement AuthnInstant="2014-04-18T20:21:14.000Z">
|
||||||
|
<AuthnContext>
|
||||||
|
<AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</AuthnContextClassRef>
|
||||||
|
</AuthnContext>
|
||||||
|
</AuthnStatement>
|
||||||
|
</Assertion>
|
||||||
|
</t:RequestedSecurityToken>
|
||||||
|
<t:RequestedAttachedReference>
|
||||||
|
<SecurityTokenReference d3p1:TokenType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0" xmlns:d3p1="http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
|
||||||
|
<KeyIdentifier ValueType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLID">_660ec874-f70a-4997-a9c4-bd591f1c7469</KeyIdentifier>
|
||||||
|
</SecurityTokenReference>
|
||||||
|
</t:RequestedAttachedReference>
|
||||||
|
<t:RequestedUnattachedReference>
|
||||||
|
<SecurityTokenReference d3p1:TokenType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0" xmlns:d3p1="http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
|
||||||
|
<KeyIdentifier ValueType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLID">_660ec874-f70a-4997-a9c4-bd591f1c7469</KeyIdentifier>
|
||||||
|
</SecurityTokenReference>
|
||||||
|
</t:RequestedUnattachedReference>
|
||||||
|
<t:TokenType>http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0</t:TokenType>
|
||||||
|
<t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>
|
||||||
|
<t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType>
|
||||||
|
</t:RequestSecurityTokenResponse>
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Authentication.WsFederation
|
||||||
|
{
|
||||||
|
internal class TestSecurityToken : SecurityToken
|
||||||
|
{
|
||||||
|
public override string Id => "id";
|
||||||
|
|
||||||
|
public override string Issuer => "issuer";
|
||||||
|
|
||||||
|
public override SecurityKey SecurityKey => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public override SecurityKey SigningKey
|
||||||
|
{
|
||||||
|
get => throw new NotImplementedException();
|
||||||
|
set => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override DateTime ValidFrom => new DateTime(2008, 3, 22);
|
||||||
|
|
||||||
|
public override DateTime ValidTo => new DateTime(2017, 3, 22);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 System.Security.Claims;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Authentication.WsFederation
|
||||||
|
{
|
||||||
|
internal class TestSecurityTokenValidator : ISecurityTokenValidator
|
||||||
|
{
|
||||||
|
public bool CanValidateToken => true;
|
||||||
|
|
||||||
|
public int MaximumTokenSizeInBytes { get; set; } = 1024 * 5;
|
||||||
|
|
||||||
|
public bool CanReadToken(string securityToken)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(securityToken) && securityToken.Contains("ThisIsAValidToken"))
|
||||||
|
{
|
||||||
|
validatedToken = new TestSecurityToken();
|
||||||
|
return new ClaimsPrincipal(new ClaimsIdentity("Test"));
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new SecurityTokenException("The security token did not contain ThisIsAValidToken");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
<t:RequestSecurityTokenResponse Context="WsFedOwinState=AQAAANCMnd8BFdERjHoAwE_Cl-sBAAAAzaTmu3688ESVbKJen1i8YwAAAAACAAAAAAADZgAAwAAAABAAAADoUPrFjHqMTp30emvI0XZ_AAAAAASAAACgAAAAEAAAAGTBC8oT24BI8BSJf4SbwjowAAAAA4ip7JyKg6vyK-PtWTapIASA3XLOXiIj8KFO3cuSd4t4H4o-W_wnQl2FAKMOKNNrFAAAAEoWRHnCSYvPKPo0kU09EciG6TJS" xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
|
||||||
|
<t:Lifetime>
|
||||||
|
<wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2014-04-18T20:21:17.341Z</wsu:Created>
|
||||||
|
<wsu:Expires xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2014-04-19T08:21:17.341Z</wsu:Expires>
|
||||||
|
</t:Lifetime>
|
||||||
|
<wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
|
||||||
|
<EndpointReference xmlns="http://www.w3.org/2005/08/addressing">
|
||||||
|
<Address>http://automation1/</Address>
|
||||||
|
</EndpointReference>
|
||||||
|
</wsp:AppliesTo>
|
||||||
|
<t:RequestedSecurityToken>
|
||||||
|
<Assertion ID="_660ec874-f70a-4997-a9c4-bd591f1c7469" IssueInstant="2014-04-18T20:21:17.450Z" Version="2.0" xmlns="urn:oasis:names:tc:SAML:2.0:assertion">
|
||||||
|
<Issuer>https://sts.windows.net/4afbc689-805b-48cf-a24c-d4aa3248a248/</Issuer>
|
||||||
|
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
|
||||||
|
<ds:SignedInfo>
|
||||||
|
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
|
||||||
|
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" />
|
||||||
|
<ds:Reference URI="#_660ec874-f70a-4997-a9c4-bd591f1c7469">
|
||||||
|
<ds:Transforms>
|
||||||
|
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
|
||||||
|
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
|
||||||
|
</ds:Transforms>
|
||||||
|
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
|
||||||
|
<ds:DigestValue>Lkq0wTyTFxLUU2cyx0XybJqhka5RzRGj6kC4aIpFg+g=</ds:DigestValue>
|
||||||
|
</ds:Reference>
|
||||||
|
</ds:SignedInfo>
|
||||||
|
<ds:SignatureValue>bPwNswOB/B9xcdAljIkin9A2vjq+u94JdyvK03mf8vZFGUYNu9uN/Q6ims1DvW1FnP7SgFBwhIvW5OjZyW8fdYGhC2bq36izkxH6ulkWbciOcyELkyHDACLudvh8kP/Q+IwpicefKzAeI2Qu/5MFq16vFg5YgI+dovg8u1fYPPEPmmptW893RNTHWeh9mLRpLYnHyg7aLG6emNRkEu7w9rzeoICeMFybb9BvJl/q/8MFCW/Z5WemQhCi6YXFSEwCO6zJzCFi/3T6ChU/xYgXbFykDLqulsNOCQxdgutyqxJzugt+3PH5IKHHuoqe7UZNUIyELJ4BgwE1sXCGYIi24rg==</ds:SignatureValue>
|
||||||
|
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
|
||||||
|
<X509Data>
|
||||||
|
<X509Certificate>ThisIsAValidToken</X509Certificate>
|
||||||
|
</X509Data>
|
||||||
|
</KeyInfo>
|
||||||
|
</ds:Signature>
|
||||||
|
<Subject>
|
||||||
|
<NameID>t0ch1TsP0pi5VoW8q5CGWsCXVZoNtpsg0mbMZPOYb4I</NameID>
|
||||||
|
<SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer" />
|
||||||
|
</Subject>
|
||||||
|
<Conditions NotBefore="2014-04-18T20:21:17.341Z" NotOnOrAfter="2014-04-19T08:21:17.341Z">
|
||||||
|
<AudienceRestriction>
|
||||||
|
<Audience>http://Automation1</Audience>
|
||||||
|
</AudienceRestriction>
|
||||||
|
</Conditions>
|
||||||
|
<AttributeStatement>
|
||||||
|
<Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname">
|
||||||
|
<AttributeValue>Test</AttributeValue>
|
||||||
|
</Attribute>
|
||||||
|
<Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname">
|
||||||
|
<AttributeValue>Test</AttributeValue>
|
||||||
|
</Attribute>
|
||||||
|
<Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name">
|
||||||
|
<AttributeValue>user1@praburajgmail.onmicrosoft.com</AttributeValue>
|
||||||
|
</Attribute>
|
||||||
|
<Attribute Name="http://schemas.microsoft.com/identity/claims/tenantid">
|
||||||
|
<AttributeValue>4afbc689-805b-48cf-a24c-d4aa3248a248</AttributeValue>
|
||||||
|
</Attribute>
|
||||||
|
<Attribute Name="http://schemas.microsoft.com/identity/claims/objectidentifier">
|
||||||
|
<AttributeValue>c2f0cd49-5e53-4520-8ed9-4e178dc488c5</AttributeValue>
|
||||||
|
</Attribute>
|
||||||
|
<Attribute Name="http://schemas.microsoft.com/identity/claims/identityprovider">
|
||||||
|
<AttributeValue>https://sts.windows.net/4afbc689-805b-48cf-a24c-d4aa3248a248/</AttributeValue>
|
||||||
|
</Attribute>
|
||||||
|
</AttributeStatement>
|
||||||
|
<AuthnStatement AuthnInstant="2014-04-18T20:21:14.000Z">
|
||||||
|
<AuthnContext>
|
||||||
|
<AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</AuthnContextClassRef>
|
||||||
|
</AuthnContext>
|
||||||
|
</AuthnStatement>
|
||||||
|
</Assertion>
|
||||||
|
</t:RequestedSecurityToken>
|
||||||
|
<t:RequestedAttachedReference>
|
||||||
|
<SecurityTokenReference d3p1:TokenType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0" xmlns:d3p1="http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
|
||||||
|
<KeyIdentifier ValueType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLID">_660ec874-f70a-4997-a9c4-bd591f1c7469</KeyIdentifier>
|
||||||
|
</SecurityTokenReference>
|
||||||
|
</t:RequestedAttachedReference>
|
||||||
|
<t:RequestedUnattachedReference>
|
||||||
|
<SecurityTokenReference d3p1:TokenType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0" xmlns:d3p1="http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
|
||||||
|
<KeyIdentifier ValueType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLID">_660ec874-f70a-4997-a9c4-bd591f1c7469</KeyIdentifier>
|
||||||
|
</SecurityTokenReference>
|
||||||
|
</t:RequestedUnattachedReference>
|
||||||
|
<t:TokenType>http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0</t:TokenType>
|
||||||
|
<t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>
|
||||||
|
<t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType>
|
||||||
|
</t:RequestSecurityTokenResponse>
|
||||||
|
|
@ -0,0 +1,443 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Http.Extensions;
|
||||||
|
using Microsoft.AspNetCore.TestHost;
|
||||||
|
using Microsoft.AspNetCore.WebUtilities;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Authentication.WsFederation
|
||||||
|
{
|
||||||
|
public class WsFederationTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task VerifySchemeDefaults()
|
||||||
|
{
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
services.AddAuthentication().AddWsFederation();
|
||||||
|
var sp = services.BuildServiceProvider();
|
||||||
|
var schemeProvider = sp.GetRequiredService<IAuthenticationSchemeProvider>();
|
||||||
|
var scheme = await schemeProvider.GetSchemeAsync(WsFederationDefaults.AuthenticationScheme);
|
||||||
|
Assert.NotNull(scheme);
|
||||||
|
Assert.Equal("WsFederationHandler", scheme.HandlerType.Name);
|
||||||
|
Assert.Equal(WsFederationDefaults.AuthenticationScheme, scheme.DisplayName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task MissingConfigurationThrows()
|
||||||
|
{
|
||||||
|
var builder = new WebHostBuilder()
|
||||||
|
.Configure(ConfigureApp)
|
||||||
|
.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
services.AddAuthentication(sharedOptions =>
|
||||||
|
{
|
||||||
|
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
|
||||||
|
sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
|
||||||
|
sharedOptions.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
|
||||||
|
})
|
||||||
|
.AddCookie()
|
||||||
|
.AddWsFederation();
|
||||||
|
});
|
||||||
|
var server = new TestServer(builder);
|
||||||
|
var httpClient = server.CreateClient();
|
||||||
|
|
||||||
|
// Verify if the request is redirected to STS with right parameters
|
||||||
|
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => httpClient.GetAsync("/"));
|
||||||
|
Assert.Equal("Provide MetadataAddress, Configuration, or ConfigurationManager to WsFederationOptions", exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ChallengeRedirects()
|
||||||
|
{
|
||||||
|
var httpClient = CreateClient();
|
||||||
|
|
||||||
|
// Verify if the request is redirected to STS with right parameters
|
||||||
|
var response = await httpClient.GetAsync("/");
|
||||||
|
Assert.Equal("https://login.windows.net/4afbc689-805b-48cf-a24c-d4aa3248a248/wsfed", response.Headers.Location.GetLeftPart(System.UriPartial.Path));
|
||||||
|
var queryItems = QueryHelpers.ParseQuery(response.Headers.Location.Query);
|
||||||
|
|
||||||
|
Assert.Equal("http://Automation1", queryItems["wtrealm"]);
|
||||||
|
Assert.True(queryItems["wctx"].ToString().Equals(CustomStateDataFormat.ValidStateData), "wctx does not equal ValidStateData");
|
||||||
|
Assert.Equal(httpClient.BaseAddress + "signin-wsfed", queryItems["wreply"]);
|
||||||
|
Assert.Equal("wsignin1.0", queryItems["wa"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task MapWillNotAffectRedirect()
|
||||||
|
{
|
||||||
|
var httpClient = CreateClient();
|
||||||
|
|
||||||
|
// Verify if the request is redirected to STS with right parameters
|
||||||
|
var response = await httpClient.GetAsync("/mapped-challenge");
|
||||||
|
Assert.Equal("https://login.windows.net/4afbc689-805b-48cf-a24c-d4aa3248a248/wsfed", response.Headers.Location.GetLeftPart(System.UriPartial.Path));
|
||||||
|
var queryItems = QueryHelpers.ParseQuery(response.Headers.Location.Query);
|
||||||
|
|
||||||
|
Assert.Equal("http://Automation1", queryItems["wtrealm"]);
|
||||||
|
Assert.True(queryItems["wctx"].ToString().Equals(CustomStateDataFormat.ValidStateData), "wctx does not equal ValidStateData");
|
||||||
|
Assert.Equal(httpClient.BaseAddress + "signin-wsfed", queryItems["wreply"]);
|
||||||
|
Assert.Equal("wsignin1.0", queryItems["wa"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task PreMappedWillAffectRedirect()
|
||||||
|
{
|
||||||
|
var httpClient = CreateClient();
|
||||||
|
|
||||||
|
// Verify if the request is redirected to STS with right parameters
|
||||||
|
var response = await httpClient.GetAsync("/premapped-challenge");
|
||||||
|
Assert.Equal("https://login.windows.net/4afbc689-805b-48cf-a24c-d4aa3248a248/wsfed", response.Headers.Location.GetLeftPart(System.UriPartial.Path));
|
||||||
|
var queryItems = QueryHelpers.ParseQuery(response.Headers.Location.Query);
|
||||||
|
|
||||||
|
Assert.Equal("http://Automation1", queryItems["wtrealm"]);
|
||||||
|
Assert.True(queryItems["wctx"].ToString().Equals(CustomStateDataFormat.ValidStateData), "wctx does not equal ValidStateData");
|
||||||
|
Assert.Equal(httpClient.BaseAddress + "premapped-challenge/signin-wsfed", queryItems["wreply"]);
|
||||||
|
Assert.Equal("wsignin1.0", queryItems["wa"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ValidTokenIsAccepted()
|
||||||
|
{
|
||||||
|
var httpClient = CreateClient();
|
||||||
|
|
||||||
|
// Verify if the request is redirected to STS with right parameters
|
||||||
|
var response = await httpClient.GetAsync("/");
|
||||||
|
var queryItems = QueryHelpers.ParseQuery(response.Headers.Location.Query);
|
||||||
|
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Post, queryItems["wreply"]);
|
||||||
|
CopyCookies(response, request);
|
||||||
|
request.Content = CreateSignInContent("WsFederation/ValidToken.xml", queryItems["wctx"]);
|
||||||
|
response = await httpClient.SendAsync(request);
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.Found, response.StatusCode);
|
||||||
|
|
||||||
|
request = new HttpRequestMessage(HttpMethod.Get, response.Headers.Location);
|
||||||
|
CopyCookies(response, request);
|
||||||
|
response = await httpClient.SendAsync(request);
|
||||||
|
|
||||||
|
// Did the request end in the actual resource requested for
|
||||||
|
Assert.Equal(WsFederationDefaults.AuthenticationScheme, await response.Content.ReadAsStringAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ValidUnsolicitedTokenIsRefused()
|
||||||
|
{
|
||||||
|
var httpClient = CreateClient();
|
||||||
|
var form = CreateSignInContent("WsFederation/ValidToken.xml", suppressWctx: true);
|
||||||
|
var exception = await Assert.ThrowsAsync<Exception>(() => httpClient.PostAsync(httpClient.BaseAddress + "signin-wsfed", form));
|
||||||
|
Assert.Contains("Unsolicited logins are not allowed.", exception.InnerException.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ValidUnsolicitedTokenIsAcceptedWhenAllowed()
|
||||||
|
{
|
||||||
|
var httpClient = CreateClient(allowUnsolicited: true);
|
||||||
|
|
||||||
|
var form = CreateSignInContent("WsFederation/ValidToken.xml", suppressWctx: true);
|
||||||
|
var response = await httpClient.PostAsync(httpClient.BaseAddress + "signin-wsfed", form);
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.Found, response.StatusCode);
|
||||||
|
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, response.Headers.Location);
|
||||||
|
CopyCookies(response, request);
|
||||||
|
response = await httpClient.SendAsync(request);
|
||||||
|
|
||||||
|
// Did the request end in the actual resource requested for
|
||||||
|
Assert.Equal(WsFederationDefaults.AuthenticationScheme, await response.Content.ReadAsStringAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task InvalidTokenIsRejected()
|
||||||
|
{
|
||||||
|
var httpClient = CreateClient();
|
||||||
|
|
||||||
|
// Verify if the request is redirected to STS with right parameters
|
||||||
|
var response = await httpClient.GetAsync("/");
|
||||||
|
var queryItems = QueryHelpers.ParseQuery(response.Headers.Location.Query);
|
||||||
|
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Post, queryItems["wreply"]);
|
||||||
|
CopyCookies(response, request);
|
||||||
|
request.Content = CreateSignInContent("WsFederation/InvalidToken.xml", queryItems["wctx"]);
|
||||||
|
response = await httpClient.SendAsync(request);
|
||||||
|
|
||||||
|
// Did the request end in the actual resource requested for
|
||||||
|
Assert.Equal("AuthenticationFailed", await response.Content.ReadAsStringAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RemoteSignoutRequestTriggersSignout()
|
||||||
|
{
|
||||||
|
var httpClient = CreateClient();
|
||||||
|
|
||||||
|
var response = await httpClient.GetAsync("/signin-wsfed?wa=wsignoutcleanup1.0");
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
var cookie = response.Headers.GetValues(HeaderNames.SetCookie).Single();
|
||||||
|
Assert.Equal(".AspNetCore.Cookies=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; samesite=lax", cookie);
|
||||||
|
Assert.Equal("OnRemoteSignOut", response.Headers.GetValues("EventHeader").Single());
|
||||||
|
Assert.Equal("", await response.Content.ReadAsStringAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task EventsResolvedFromDI()
|
||||||
|
{
|
||||||
|
var builder = new WebHostBuilder()
|
||||||
|
.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
services.AddSingleton<MyWsFedEvents>();
|
||||||
|
services.AddAuthentication(sharedOptions =>
|
||||||
|
{
|
||||||
|
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
|
||||||
|
sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
|
||||||
|
sharedOptions.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
|
||||||
|
})
|
||||||
|
.AddCookie()
|
||||||
|
.AddWsFederation(options =>
|
||||||
|
{
|
||||||
|
options.Wtrealm = "http://Automation1";
|
||||||
|
options.MetadataAddress = "https://login.windows.net/4afbc689-805b-48cf-a24c-d4aa3248a248/federationmetadata/2007-06/federationmetadata.xml";
|
||||||
|
options.BackchannelHttpHandler = new WaadMetadataDocumentHandler();
|
||||||
|
options.EventsType = typeof(MyWsFedEvents);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.Run(context => context.ChallengeAsync());
|
||||||
|
});
|
||||||
|
var server = new TestServer(builder);
|
||||||
|
|
||||||
|
var result = await server.CreateClient().GetAsync("");
|
||||||
|
Assert.Contains("CustomKey=CustomValue", result.Headers.Location.Query);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MyWsFedEvents : WsFederationEvents
|
||||||
|
{
|
||||||
|
public override Task RedirectToIdentityProvider(RedirectContext context)
|
||||||
|
{
|
||||||
|
context.ProtocolMessage.SetParameter("CustomKey", "CustomValue");
|
||||||
|
return base.RedirectToIdentityProvider(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private FormUrlEncodedContent CreateSignInContent(string tokenFile, string wctx = null, bool suppressWctx = false)
|
||||||
|
{
|
||||||
|
var kvps = new List<KeyValuePair<string, string>>();
|
||||||
|
kvps.Add(new KeyValuePair<string, string>("wa", "wsignin1.0"));
|
||||||
|
kvps.Add(new KeyValuePair<string, string>("wresult", File.ReadAllText(tokenFile)));
|
||||||
|
if (!string.IsNullOrEmpty(wctx))
|
||||||
|
{
|
||||||
|
kvps.Add(new KeyValuePair<string, string>("wctx", wctx));
|
||||||
|
}
|
||||||
|
if (suppressWctx)
|
||||||
|
{
|
||||||
|
kvps.Add(new KeyValuePair<string, string>("suppressWctx", "true"));
|
||||||
|
}
|
||||||
|
return new FormUrlEncodedContent(kvps);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CopyCookies(HttpResponseMessage response, HttpRequestMessage request)
|
||||||
|
{
|
||||||
|
var cookies = SetCookieHeaderValue.ParseList(response.Headers.GetValues(HeaderNames.SetCookie).ToList());
|
||||||
|
foreach (var cookie in cookies)
|
||||||
|
{
|
||||||
|
if (cookie.Value.HasValue)
|
||||||
|
{
|
||||||
|
request.Headers.Add(HeaderNames.Cookie, new CookieHeaderValue(cookie.Name, cookie.Value).ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpClient CreateClient(bool allowUnsolicited = false)
|
||||||
|
{
|
||||||
|
var builder = new WebHostBuilder()
|
||||||
|
.Configure(ConfigureApp)
|
||||||
|
.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
services.AddAuthentication(sharedOptions =>
|
||||||
|
{
|
||||||
|
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
|
||||||
|
sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
|
||||||
|
sharedOptions.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
|
||||||
|
})
|
||||||
|
.AddCookie()
|
||||||
|
.AddWsFederation(options =>
|
||||||
|
{
|
||||||
|
options.Wtrealm = "http://Automation1";
|
||||||
|
options.MetadataAddress = "https://login.windows.net/4afbc689-805b-48cf-a24c-d4aa3248a248/federationmetadata/2007-06/federationmetadata.xml";
|
||||||
|
options.BackchannelHttpHandler = new WaadMetadataDocumentHandler();
|
||||||
|
options.StateDataFormat = new CustomStateDataFormat();
|
||||||
|
options.SecurityTokenHandlers = new List<ISecurityTokenValidator>() { new TestSecurityTokenValidator() };
|
||||||
|
options.UseTokenLifetime = false;
|
||||||
|
options.AllowUnsolicitedLogins = allowUnsolicited;
|
||||||
|
options.Events = new WsFederationEvents()
|
||||||
|
{
|
||||||
|
OnMessageReceived = context =>
|
||||||
|
{
|
||||||
|
if (!context.ProtocolMessage.Parameters.TryGetValue("suppressWctx", out var suppress))
|
||||||
|
{
|
||||||
|
Assert.True(context.ProtocolMessage.Wctx.Equals("customValue"), "wctx is not my custom value");
|
||||||
|
}
|
||||||
|
context.HttpContext.Items["MessageReceived"] = true;
|
||||||
|
return Task.FromResult(0);
|
||||||
|
},
|
||||||
|
OnRedirectToIdentityProvider = context =>
|
||||||
|
{
|
||||||
|
if (context.ProtocolMessage.IsSignInMessage)
|
||||||
|
{
|
||||||
|
// Sign in message
|
||||||
|
context.ProtocolMessage.Wctx = "customValue";
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(0);
|
||||||
|
},
|
||||||
|
OnSecurityTokenReceived = context =>
|
||||||
|
{
|
||||||
|
context.HttpContext.Items["SecurityTokenReceived"] = true;
|
||||||
|
return Task.FromResult(0);
|
||||||
|
},
|
||||||
|
OnSecurityTokenValidated = context =>
|
||||||
|
{
|
||||||
|
Assert.True((bool)context.HttpContext.Items["MessageReceived"], "MessageReceived notification not invoked");
|
||||||
|
Assert.True((bool)context.HttpContext.Items["SecurityTokenReceived"], "SecurityTokenReceived notification not invoked");
|
||||||
|
|
||||||
|
if (context.Principal != null)
|
||||||
|
{
|
||||||
|
var identity = context.Principal.Identities.Single();
|
||||||
|
identity.AddClaim(new Claim("ReturnEndpoint", "true"));
|
||||||
|
identity.AddClaim(new Claim("Authenticated", "true"));
|
||||||
|
identity.AddClaim(new Claim(identity.RoleClaimType, "Guest", ClaimValueTypes.String));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(0);
|
||||||
|
},
|
||||||
|
OnAuthenticationFailed = context =>
|
||||||
|
{
|
||||||
|
context.HttpContext.Items["AuthenticationFailed"] = true;
|
||||||
|
//Change the request url to something different and skip Wsfed. This new url will handle the request and let us know if this notification was invoked.
|
||||||
|
context.HttpContext.Request.Path = new PathString("/AuthenticationFailed");
|
||||||
|
context.SkipHandler();
|
||||||
|
return Task.FromResult(0);
|
||||||
|
},
|
||||||
|
OnRemoteSignOut = context =>
|
||||||
|
{
|
||||||
|
context.Response.Headers["EventHeader"] = "OnRemoteSignOut";
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
var server = new TestServer(builder);
|
||||||
|
return server.CreateClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfigureApp(IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
app.Map("/PreMapped-Challenge", mapped =>
|
||||||
|
{
|
||||||
|
mapped.UseAuthentication();
|
||||||
|
mapped.Run(async context =>
|
||||||
|
{
|
||||||
|
await context.ChallengeAsync(WsFederationDefaults.AuthenticationScheme);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.UseAuthentication();
|
||||||
|
|
||||||
|
app.Map("/Logout", subApp =>
|
||||||
|
{
|
||||||
|
subApp.Run(async context =>
|
||||||
|
{
|
||||||
|
if (context.User.Identity.IsAuthenticated)
|
||||||
|
{
|
||||||
|
var authProperties = new AuthenticationProperties() { RedirectUri = context.Request.GetEncodedUrl() };
|
||||||
|
await context.SignOutAsync(WsFederationDefaults.AuthenticationScheme, authProperties);
|
||||||
|
await context.Response.WriteAsync("Signing out...");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await context.Response.WriteAsync("SignedOut");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.Map("/AuthenticationFailed", subApp =>
|
||||||
|
{
|
||||||
|
subApp.Run(async context =>
|
||||||
|
{
|
||||||
|
await context.Response.WriteAsync("AuthenticationFailed");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.Map("/signout-wsfed", subApp =>
|
||||||
|
{
|
||||||
|
subApp.Run(async context =>
|
||||||
|
{
|
||||||
|
await context.Response.WriteAsync("signout-wsfed");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.Map("/mapped-challenge", subApp =>
|
||||||
|
{
|
||||||
|
subApp.Run(async context =>
|
||||||
|
{
|
||||||
|
await context.ChallengeAsync(WsFederationDefaults.AuthenticationScheme);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.Run(async context =>
|
||||||
|
{
|
||||||
|
var result = context.AuthenticateAsync();
|
||||||
|
if (context.User == null || !context.User.Identity.IsAuthenticated)
|
||||||
|
{
|
||||||
|
await context.ChallengeAsync(WsFederationDefaults.AuthenticationScheme);
|
||||||
|
await context.Response.WriteAsync("Unauthorized");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var identity = context.User.Identities.Single();
|
||||||
|
if (identity.NameClaimType == "Name_Failed" && identity.RoleClaimType == "Role_Failed")
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = 500;
|
||||||
|
await context.Response.WriteAsync("SignIn_Failed");
|
||||||
|
}
|
||||||
|
else if (!identity.HasClaim("Authenticated", "true") || !identity.HasClaim("ReturnEndpoint", "true") || !identity.HasClaim(identity.RoleClaimType, "Guest"))
|
||||||
|
{
|
||||||
|
await context.Response.WriteAsync("Provider not invoked");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await context.Response.WriteAsync(WsFederationDefaults.AuthenticationScheme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private class WaadMetadataDocumentHandler : HttpMessageHandler
|
||||||
|
{
|
||||||
|
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var metadata = File.ReadAllText(@"WsFederation/federationmetadata.xml");
|
||||||
|
var newResponse = new HttpResponseMessage() { Content = new StringContent(metadata, Encoding.UTF8, "text/xml") };
|
||||||
|
return Task.FromResult<HttpResponseMessage>(newResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,132 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<EntityDescriptor ID="_4d16ee22-bedb-4eca-a532-1e5551c7d66e" entityID="https://sts.windows.net/4afbc689-805b-48cf-a24c-d4aa3248a248/" xmlns="urn:oasis:names:tc:SAML:2.0:metadata">
|
||||||
|
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
|
||||||
|
<ds:SignedInfo>
|
||||||
|
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
|
||||||
|
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" />
|
||||||
|
<ds:Reference URI="#_4d16ee22-bedb-4eca-a532-1e5551c7d66e">
|
||||||
|
<ds:Transforms>
|
||||||
|
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
|
||||||
|
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
|
||||||
|
</ds:Transforms>
|
||||||
|
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
|
||||||
|
<ds:DigestValue>wFJy/A1QstqtLHauYGcqwwHvn3HUW25DcWI/XLOmXOM=</ds:DigestValue>
|
||||||
|
</ds:Reference>
|
||||||
|
</ds:SignedInfo>
|
||||||
|
<ds:SignatureValue>R6fPw+BiFS9XYdkhwNJRjGxVftA2j9TdkF5d5jgR8uG1QMyuEA/Eizeq1HnnUj2Yi+sqNG+HzaZQclECeiJfi88Ry+keorDCo9KgdnjlZZc+WFzrJZeHjaDIvFD6B4OAN0mTq5kbpwr7+idzSbvyRXAnpvJxOrViZKE4HpwltGAZGDTkjsVkd8Z/wfoN7ehN4Ei7u/mOAiEU4FkWYFU/BfSVRVIUDyyQ7DGfQFJvCwHWFvsq+M1wfOUzQO5K+M9EU2m4VEP1qqbexXaZMAbcjqyUn4eN7doWjWE59jkXGbn+GR8qgCJqLOaYwXnH5XD0pMjy71aKGyLNaUb3wCwjkA==</ds:SignatureValue>
|
||||||
|
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
|
||||||
|
<X509Data>
|
||||||
|
<X509Certificate>MIIDPjCCAiqgAwIBAgIQVWmXY/+9RqFA/OG9kFulHDAJBgUrDgMCHQUAMC0xKzApBgNVBAMTImFjY291bnRzLmFjY2Vzc2NvbnRyb2wud2luZG93cy5uZXQwHhcNMTIwNjA3MDcwMDAwWhcNMTQwNjA3MDcwMDAwWjAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArCz8Sn3GGXmikH2MdTeGY1D711EORX/lVXpr+ecGgqfUWF8MPB07XkYuJ54DAuYT318+2XrzMjOtqkT94VkXmxv6dFGhG8YZ8vNMPd4tdj9c0lpvWQdqXtL1TlFRpD/P6UMEigfN0c9oWDg9U7Ilymgei0UXtf1gtcQbc5sSQU0S4vr9YJp2gLFIGK11Iqg4XSGdcI0QWLLkkC6cBukhVnd6BCYbLjTYy3fNs4DzNdemJlxGl8sLexFytBF6YApvSdus3nFXaMCtBGx16HzkK9ne3lobAwL2o79bP4imEGqg+ibvyNmbrwFGnQrBc1jTF9LyQX9q+louxVfHs6ZiVwIDAQABo2IwYDBeBgNVHQEEVzBVgBCxDDsLd8xkfOLKm4Q/SzjtoS8wLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNzY29udHJvbC53aW5kb3dzLm5ldIIQVWmXY/+9RqFA/OG9kFulHDAJBgUrDgMCHQUAA4IBAQAkJtxxm/ErgySlNk69+1odTMP8Oy6L0H17z7XGG3w4TqvTUSWaxD4hSFJ0e7mHLQLQD7oV/erACXwSZn2pMoZ89MBDjOMQA+e6QzGB7jmSzPTNmQgMLA8fWCfqPrz6zgH+1F1gNp8hJY57kfeVPBiyjuBmlTEBsBlzolY9dd/55qqfQk6cgSeCbHCy/RU/iep0+UsRMlSgPNNmqhj5gmN2AFVCN96zF694LwuPae5CeR2ZcVknexOWHYjFM0MgUSw0ubnGl0h9AJgGyhvNGcjQqu9vd1xkupFgaN+f7P3p3EVN5csBg5H94jEcQZT7EKeTiZ6bTrpDAnrr8tDCy8ng</X509Certificate>
|
||||||
|
</X509Data>
|
||||||
|
</KeyInfo>
|
||||||
|
</ds:Signature>
|
||||||
|
<RoleDescriptor xsi:type="fed:SecurityTokenServiceType" protocolSupportEnumeration="http://docs.oasis-open.org/wsfed/federation/200706" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:fed="http://docs.oasis-open.org/wsfed/federation/200706">
|
||||||
|
<KeyDescriptor use="signing">
|
||||||
|
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
|
||||||
|
<X509Data>
|
||||||
|
<X509Certificate>MIIDPjCCAiqgAwIBAgIQsRiM0jheFZhKk49YD0SK1TAJBgUrDgMCHQUAMC0xKzApBgNVBAMTImFjY291bnRzLmFjY2Vzc2NvbnRyb2wud2luZG93cy5uZXQwHhcNMTQwMTAxMDcwMDAwWhcNMTYwMTAxMDcwMDAwWjAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkSCWg6q9iYxvJE2NIhSyOiKvqoWCO2GFipgH0sTSAs5FalHQosk9ZNTztX0ywS/AHsBeQPqYygfYVJL6/EgzVuwRk5txr9e3n1uml94fLyq/AXbwo9yAduf4dCHTP8CWR1dnDR+Qnz/4PYlWVEuuHHONOw/blbfdMjhY+C/BYM2E3pRxbohBb3x//CfueV7ddz2LYiH3wjz0QS/7kjPiNCsXcNyKQEOTkbHFi3mu0u13SQwNddhcynd/GTgWN8A+6SN1r4hzpjFKFLbZnBt77ACSiYx+IHK4Mp+NaVEi5wQtSsjQtI++XsokxRDqYLwus1I1SihgbV/STTg5enufuwIDAQABo2IwYDBeBgNVHQEEVzBVgBDLebM6bK3BjWGqIBrBNFeNoS8wLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNzY29udHJvbC53aW5kb3dzLm5ldIIQsRiM0jheFZhKk49YD0SK1TAJBgUrDgMCHQUAA4IBAQCJ4JApryF77EKC4zF5bUaBLQHQ1PNtA1uMDbdNVGKCmSf8M65b8h0NwlIjGGGy/unK8P6jWFdm5IlZ0YPTOgzcRZguXDPj7ajyvlVEQ2K2ICvTYiRQqrOhEhZMSSZsTKXFVwNfW6ADDkN3bvVOVbtpty+nBY5UqnI7xbcoHLZ4wYD251uj5+lo13YLnsVrmQ16NCBYq2nQFNPuNJw6t3XUbwBHXpF46aLT1/eGf/7Xx6iy8yPJX4DyrpFTutDz882RWofGEO5t4Cw+zZg70dJ/hH/ODYRMorfXEW+8uKmXMKmX2wyxMKvfiPbTy5LmAU8Jvjs2tLg4rOBcXWLAIarZ</X509Certificate>
|
||||||
|
</X509Data>
|
||||||
|
</KeyInfo>
|
||||||
|
</KeyDescriptor>
|
||||||
|
<KeyDescriptor use="signing">
|
||||||
|
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
|
||||||
|
<X509Data>
|
||||||
|
<X509Certificate>MIIDPjCCAiqgAwIBAgIQVWmXY/+9RqFA/OG9kFulHDAJBgUrDgMCHQUAMC0xKzApBgNVBAMTImFjY291bnRzLmFjY2Vzc2NvbnRyb2wud2luZG93cy5uZXQwHhcNMTIwNjA3MDcwMDAwWhcNMTQwNjA3MDcwMDAwWjAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArCz8Sn3GGXmikH2MdTeGY1D711EORX/lVXpr+ecGgqfUWF8MPB07XkYuJ54DAuYT318+2XrzMjOtqkT94VkXmxv6dFGhG8YZ8vNMPd4tdj9c0lpvWQdqXtL1TlFRpD/P6UMEigfN0c9oWDg9U7Ilymgei0UXtf1gtcQbc5sSQU0S4vr9YJp2gLFIGK11Iqg4XSGdcI0QWLLkkC6cBukhVnd6BCYbLjTYy3fNs4DzNdemJlxGl8sLexFytBF6YApvSdus3nFXaMCtBGx16HzkK9ne3lobAwL2o79bP4imEGqg+ibvyNmbrwFGnQrBc1jTF9LyQX9q+louxVfHs6ZiVwIDAQABo2IwYDBeBgNVHQEEVzBVgBCxDDsLd8xkfOLKm4Q/SzjtoS8wLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNzY29udHJvbC53aW5kb3dzLm5ldIIQVWmXY/+9RqFA/OG9kFulHDAJBgUrDgMCHQUAA4IBAQAkJtxxm/ErgySlNk69+1odTMP8Oy6L0H17z7XGG3w4TqvTUSWaxD4hSFJ0e7mHLQLQD7oV/erACXwSZn2pMoZ89MBDjOMQA+e6QzGB7jmSzPTNmQgMLA8fWCfqPrz6zgH+1F1gNp8hJY57kfeVPBiyjuBmlTEBsBlzolY9dd/55qqfQk6cgSeCbHCy/RU/iep0+UsRMlSgPNNmqhj5gmN2AFVCN96zF694LwuPae5CeR2ZcVknexOWHYjFM0MgUSw0ubnGl0h9AJgGyhvNGcjQqu9vd1xkupFgaN+f7P3p3EVN5csBg5H94jEcQZT7EKeTiZ6bTrpDAnrr8tDCy8ng</X509Certificate>
|
||||||
|
</X509Data>
|
||||||
|
</KeyInfo>
|
||||||
|
</KeyDescriptor>
|
||||||
|
<fed:ClaimTypesOffered>
|
||||||
|
<auth:ClaimType Uri="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn" Optional="true" xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706">
|
||||||
|
<auth:DisplayName>UPN</auth:DisplayName>
|
||||||
|
<auth:Description>UPN of the user</auth:Description>
|
||||||
|
</auth:ClaimType>
|
||||||
|
<auth:ClaimType Uri="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" Optional="true" xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706">
|
||||||
|
<auth:DisplayName>Name</auth:DisplayName>
|
||||||
|
<auth:Description>The display name for the user</auth:Description>
|
||||||
|
</auth:ClaimType>
|
||||||
|
<auth:ClaimType Uri="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname" Optional="true" xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706">
|
||||||
|
<auth:DisplayName>Given Name</auth:DisplayName>
|
||||||
|
<auth:Description>First name of the user</auth:Description>
|
||||||
|
</auth:ClaimType>
|
||||||
|
<auth:ClaimType Uri="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname" Optional="true" xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706">
|
||||||
|
<auth:DisplayName>Surname</auth:DisplayName>
|
||||||
|
<auth:Description>Last name of the user</auth:Description>
|
||||||
|
</auth:ClaimType>
|
||||||
|
<auth:ClaimType Uri="http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationinstant" Optional="true" xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706">
|
||||||
|
<auth:DisplayName>Authentication Instant</auth:DisplayName>
|
||||||
|
<auth:Description>The time (UTC) at which the user authenticated to the identity provider</auth:Description>
|
||||||
|
</auth:ClaimType>
|
||||||
|
<auth:ClaimType Uri="http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod" Optional="true" xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706">
|
||||||
|
<auth:DisplayName>Authentication Method</auth:DisplayName>
|
||||||
|
<auth:Description>The method of authentication used by the identity provider</auth:Description>
|
||||||
|
</auth:ClaimType>
|
||||||
|
<auth:ClaimType Uri="http://schemas.microsoft.com/identity/claims/tenantid" Optional="true" xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706">
|
||||||
|
<auth:DisplayName>TenantId</auth:DisplayName>
|
||||||
|
<auth:Description>Identifier for the user's tenant</auth:Description>
|
||||||
|
</auth:ClaimType>
|
||||||
|
<auth:ClaimType Uri="http://schemas.microsoft.com/identity/claims/identityprovider" Optional="true" xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706">
|
||||||
|
<auth:DisplayName>IdentityProvider</auth:DisplayName>
|
||||||
|
<auth:Description>Identity provider for the user.</auth:Description>
|
||||||
|
</auth:ClaimType>
|
||||||
|
</fed:ClaimTypesOffered>
|
||||||
|
<fed:SecurityTokenServiceEndpoint>
|
||||||
|
<EndpointReference xmlns="http://www.w3.org/2005/08/addressing">
|
||||||
|
<Address>https://login.windows.net/4afbc689-805b-48cf-a24c-d4aa3248a248/wsfed</Address>
|
||||||
|
</EndpointReference>
|
||||||
|
</fed:SecurityTokenServiceEndpoint>
|
||||||
|
<fed:PassiveRequestorEndpoint>
|
||||||
|
<EndpointReference xmlns="http://www.w3.org/2005/08/addressing">
|
||||||
|
<Address>https://login.windows.net/4afbc689-805b-48cf-a24c-d4aa3248a248/wsfed</Address>
|
||||||
|
</EndpointReference>
|
||||||
|
</fed:PassiveRequestorEndpoint>
|
||||||
|
</RoleDescriptor>
|
||||||
|
<RoleDescriptor xsi:type="fed:ApplicationServiceType" protocolSupportEnumeration="http://docs.oasis-open.org/ws-sx/ws-trust/200512 http://docs.oasis-open.org/wsfed/federation/200706" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:fed="http://docs.oasis-open.org/wsfed/federation/200706">
|
||||||
|
<KeyDescriptor use="signing">
|
||||||
|
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
|
||||||
|
<X509Data>
|
||||||
|
<X509Certificate>MIIDPjCCAiqgAwIBAgIQsRiM0jheFZhKk49YD0SK1TAJBgUrDgMCHQUAMC0xKzApBgNVBAMTImFjY291bnRzLmFjY2Vzc2NvbnRyb2wud2luZG93cy5uZXQwHhcNMTQwMTAxMDcwMDAwWhcNMTYwMTAxMDcwMDAwWjAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkSCWg6q9iYxvJE2NIhSyOiKvqoWCO2GFipgH0sTSAs5FalHQosk9ZNTztX0ywS/AHsBeQPqYygfYVJL6/EgzVuwRk5txr9e3n1uml94fLyq/AXbwo9yAduf4dCHTP8CWR1dnDR+Qnz/4PYlWVEuuHHONOw/blbfdMjhY+C/BYM2E3pRxbohBb3x//CfueV7ddz2LYiH3wjz0QS/7kjPiNCsXcNyKQEOTkbHFi3mu0u13SQwNddhcynd/GTgWN8A+6SN1r4hzpjFKFLbZnBt77ACSiYx+IHK4Mp+NaVEi5wQtSsjQtI++XsokxRDqYLwus1I1SihgbV/STTg5enufuwIDAQABo2IwYDBeBgNVHQEEVzBVgBDLebM6bK3BjWGqIBrBNFeNoS8wLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNzY29udHJvbC53aW5kb3dzLm5ldIIQsRiM0jheFZhKk49YD0SK1TAJBgUrDgMCHQUAA4IBAQCJ4JApryF77EKC4zF5bUaBLQHQ1PNtA1uMDbdNVGKCmSf8M65b8h0NwlIjGGGy/unK8P6jWFdm5IlZ0YPTOgzcRZguXDPj7ajyvlVEQ2K2ICvTYiRQqrOhEhZMSSZsTKXFVwNfW6ADDkN3bvVOVbtpty+nBY5UqnI7xbcoHLZ4wYD251uj5+lo13YLnsVrmQ16NCBYq2nQFNPuNJw6t3XUbwBHXpF46aLT1/eGf/7Xx6iy8yPJX4DyrpFTutDz882RWofGEO5t4Cw+zZg70dJ/hH/ODYRMorfXEW+8uKmXMKmX2wyxMKvfiPbTy5LmAU8Jvjs2tLg4rOBcXWLAIarZ</X509Certificate>
|
||||||
|
</X509Data>
|
||||||
|
</KeyInfo>
|
||||||
|
</KeyDescriptor>
|
||||||
|
<KeyDescriptor use="signing">
|
||||||
|
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
|
||||||
|
<X509Data>
|
||||||
|
<X509Certificate>MIIDPjCCAiqgAwIBAgIQVWmXY/+9RqFA/OG9kFulHDAJBgUrDgMCHQUAMC0xKzApBgNVBAMTImFjY291bnRzLmFjY2Vzc2NvbnRyb2wud2luZG93cy5uZXQwHhcNMTIwNjA3MDcwMDAwWhcNMTQwNjA3MDcwMDAwWjAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArCz8Sn3GGXmikH2MdTeGY1D711EORX/lVXpr+ecGgqfUWF8MPB07XkYuJ54DAuYT318+2XrzMjOtqkT94VkXmxv6dFGhG8YZ8vNMPd4tdj9c0lpvWQdqXtL1TlFRpD/P6UMEigfN0c9oWDg9U7Ilymgei0UXtf1gtcQbc5sSQU0S4vr9YJp2gLFIGK11Iqg4XSGdcI0QWLLkkC6cBukhVnd6BCYbLjTYy3fNs4DzNdemJlxGl8sLexFytBF6YApvSdus3nFXaMCtBGx16HzkK9ne3lobAwL2o79bP4imEGqg+ibvyNmbrwFGnQrBc1jTF9LyQX9q+louxVfHs6ZiVwIDAQABo2IwYDBeBgNVHQEEVzBVgBCxDDsLd8xkfOLKm4Q/SzjtoS8wLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNzY29udHJvbC53aW5kb3dzLm5ldIIQVWmXY/+9RqFA/OG9kFulHDAJBgUrDgMCHQUAA4IBAQAkJtxxm/ErgySlNk69+1odTMP8Oy6L0H17z7XGG3w4TqvTUSWaxD4hSFJ0e7mHLQLQD7oV/erACXwSZn2pMoZ89MBDjOMQA+e6QzGB7jmSzPTNmQgMLA8fWCfqPrz6zgH+1F1gNp8hJY57kfeVPBiyjuBmlTEBsBlzolY9dd/55qqfQk6cgSeCbHCy/RU/iep0+UsRMlSgPNNmqhj5gmN2AFVCN96zF694LwuPae5CeR2ZcVknexOWHYjFM0MgUSw0ubnGl0h9AJgGyhvNGcjQqu9vd1xkupFgaN+f7P3p3EVN5csBg5H94jEcQZT7EKeTiZ6bTrpDAnrr8tDCy8ng</X509Certificate>
|
||||||
|
</X509Data>
|
||||||
|
</KeyInfo>
|
||||||
|
</KeyDescriptor>
|
||||||
|
<fed:TargetScopes>
|
||||||
|
<EndpointReference xmlns="http://www.w3.org/2005/08/addressing">
|
||||||
|
<Address>https://sts.windows.net/4afbc689-805b-48cf-a24c-d4aa3248a248/</Address>
|
||||||
|
</EndpointReference>
|
||||||
|
</fed:TargetScopes>
|
||||||
|
<fed:ApplicationServiceEndpoint>
|
||||||
|
<EndpointReference xmlns="http://www.w3.org/2005/08/addressing">
|
||||||
|
<Address>https://login.windows.net/4afbc689-805b-48cf-a24c-d4aa3248a248/wsfed</Address>
|
||||||
|
</EndpointReference>
|
||||||
|
</fed:ApplicationServiceEndpoint>
|
||||||
|
<fed:PassiveRequestorEndpoint>
|
||||||
|
<EndpointReference xmlns="http://www.w3.org/2005/08/addressing">
|
||||||
|
<Address>https://login.windows.net/4afbc689-805b-48cf-a24c-d4aa3248a248/wsfed</Address>
|
||||||
|
</EndpointReference>
|
||||||
|
</fed:PassiveRequestorEndpoint>
|
||||||
|
</RoleDescriptor>
|
||||||
|
<IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
||||||
|
<KeyDescriptor use="signing">
|
||||||
|
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
|
||||||
|
<X509Data>
|
||||||
|
<X509Certificate>MIIDPjCCAiqgAwIBAgIQsRiM0jheFZhKk49YD0SK1TAJBgUrDgMCHQUAMC0xKzApBgNVBAMTImFjY291bnRzLmFjY2Vzc2NvbnRyb2wud2luZG93cy5uZXQwHhcNMTQwMTAxMDcwMDAwWhcNMTYwMTAxMDcwMDAwWjAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkSCWg6q9iYxvJE2NIhSyOiKvqoWCO2GFipgH0sTSAs5FalHQosk9ZNTztX0ywS/AHsBeQPqYygfYVJL6/EgzVuwRk5txr9e3n1uml94fLyq/AXbwo9yAduf4dCHTP8CWR1dnDR+Qnz/4PYlWVEuuHHONOw/blbfdMjhY+C/BYM2E3pRxbohBb3x//CfueV7ddz2LYiH3wjz0QS/7kjPiNCsXcNyKQEOTkbHFi3mu0u13SQwNddhcynd/GTgWN8A+6SN1r4hzpjFKFLbZnBt77ACSiYx+IHK4Mp+NaVEi5wQtSsjQtI++XsokxRDqYLwus1I1SihgbV/STTg5enufuwIDAQABo2IwYDBeBgNVHQEEVzBVgBDLebM6bK3BjWGqIBrBNFeNoS8wLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNzY29udHJvbC53aW5kb3dzLm5ldIIQsRiM0jheFZhKk49YD0SK1TAJBgUrDgMCHQUAA4IBAQCJ4JApryF77EKC4zF5bUaBLQHQ1PNtA1uMDbdNVGKCmSf8M65b8h0NwlIjGGGy/unK8P6jWFdm5IlZ0YPTOgzcRZguXDPj7ajyvlVEQ2K2ICvTYiRQqrOhEhZMSSZsTKXFVwNfW6ADDkN3bvVOVbtpty+nBY5UqnI7xbcoHLZ4wYD251uj5+lo13YLnsVrmQ16NCBYq2nQFNPuNJw6t3XUbwBHXpF46aLT1/eGf/7Xx6iy8yPJX4DyrpFTutDz882RWofGEO5t4Cw+zZg70dJ/hH/ODYRMorfXEW+8uKmXMKmX2wyxMKvfiPbTy5LmAU8Jvjs2tLg4rOBcXWLAIarZ</X509Certificate>
|
||||||
|
</X509Data>
|
||||||
|
</KeyInfo>
|
||||||
|
</KeyDescriptor>
|
||||||
|
<KeyDescriptor use="signing">
|
||||||
|
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
|
||||||
|
<X509Data>
|
||||||
|
<X509Certificate>MIIDPjCCAiqgAwIBAgIQVWmXY/+9RqFA/OG9kFulHDAJBgUrDgMCHQUAMC0xKzApBgNVBAMTImFjY291bnRzLmFjY2Vzc2NvbnRyb2wud2luZG93cy5uZXQwHhcNMTIwNjA3MDcwMDAwWhcNMTQwNjA3MDcwMDAwWjAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArCz8Sn3GGXmikH2MdTeGY1D711EORX/lVXpr+ecGgqfUWF8MPB07XkYuJ54DAuYT318+2XrzMjOtqkT94VkXmxv6dFGhG8YZ8vNMPd4tdj9c0lpvWQdqXtL1TlFRpD/P6UMEigfN0c9oWDg9U7Ilymgei0UXtf1gtcQbc5sSQU0S4vr9YJp2gLFIGK11Iqg4XSGdcI0QWLLkkC6cBukhVnd6BCYbLjTYy3fNs4DzNdemJlxGl8sLexFytBF6YApvSdus3nFXaMCtBGx16HzkK9ne3lobAwL2o79bP4imEGqg+ibvyNmbrwFGnQrBc1jTF9LyQX9q+louxVfHs6ZiVwIDAQABo2IwYDBeBgNVHQEEVzBVgBCxDDsLd8xkfOLKm4Q/SzjtoS8wLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNzY29udHJvbC53aW5kb3dzLm5ldIIQVWmXY/+9RqFA/OG9kFulHDAJBgUrDgMCHQUAA4IBAQAkJtxxm/ErgySlNk69+1odTMP8Oy6L0H17z7XGG3w4TqvTUSWaxD4hSFJ0e7mHLQLQD7oV/erACXwSZn2pMoZ89MBDjOMQA+e6QzGB7jmSzPTNmQgMLA8fWCfqPrz6zgH+1F1gNp8hJY57kfeVPBiyjuBmlTEBsBlzolY9dd/55qqfQk6cgSeCbHCy/RU/iep0+UsRMlSgPNNmqhj5gmN2AFVCN96zF694LwuPae5CeR2ZcVknexOWHYjFM0MgUSw0ubnGl0h9AJgGyhvNGcjQqu9vd1xkupFgaN+f7P3p3EVN5csBg5H94jEcQZT7EKeTiZ6bTrpDAnrr8tDCy8ng</X509Certificate>
|
||||||
|
</X509Data>
|
||||||
|
</KeyInfo>
|
||||||
|
</KeyDescriptor>
|
||||||
|
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://login.windows.net/4afbc689-805b-48cf-a24c-d4aa3248a248/saml2" />
|
||||||
|
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://login.windows.net/4afbc689-805b-48cf-a24c-d4aa3248a248/saml2" />
|
||||||
|
</IDPSSODescriptor>
|
||||||
|
</EntityDescriptor>
|
||||||
Loading…
Reference in New Issue