-
Notifications
You must be signed in to change notification settings - Fork 587
WsFederation review #1441
WsFederation review #1441
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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"); | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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" | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this just use the empty overload since you are setting the defaults?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could. This just mirrors the template code. |
||
| 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); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk.Web"> | ||
|
|
||
| <PropertyGroup> | ||
| <TargetFrameworks>net461;netcoreapp2.0</TargetFrameworks> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <ProjectReference Include="..\..\src\Microsoft.AspNetCore.Authentication.WsFederation\Microsoft.AspNetCore.Authentication.WsFederation.csproj" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="2.0.0" /> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is going to cause issues. You should always use a ProjectReference to anything that is built from the same repo. cc @JunTaoLuo
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah I see why this fails universe. We will build this for this release without using Universe as I'll build locally instead. The reason we wanted to take this approach is because we wanted to use the release 2.0.0 packages as dependencies. |
||
| <PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="2.0.0" /> | ||
| <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.0.0" /> | ||
| <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Https" Version="2.0.0" /> | ||
| <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.0" /> | ||
| <PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="2.0.0" /> | ||
| <PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="2.0.0" /> | ||
| <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.0" /> | ||
| <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.0" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <EmbeddedResource Include="compiler\resources\cert.pfx" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: doesn't DefaultSignInScheme fallback to DefaultScheme?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not correctly in the 2.0.0 branch we're targeting. That was just fixed in 2.1 and 2.0.x.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ugh, yeah that's too bad