Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions AspNet.Security.OpenId.Providers.sln
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mvc.Client", "samples\Mvc.C
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNet.Security.OpenId.Steam", "src\AspNet.Security.OpenId.Steam\AspNet.Security.OpenId.Steam.csproj", "{49400532-7B7B-4C8F-877F-FDB21D3717CC}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{9830BBC8-5A1C-4FC7-AFC8-C3F5247B2151}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNet.Security.OpenId.Providers.Tests", "test\AspNet.Security.OpenId.Providers.Tests\AspNet.Security.OpenId.Providers.Tests.csproj", "{F25AB0AB-F046-4D4B-97DD-634C2725D050}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -40,6 +44,10 @@ Global
{49400532-7B7B-4C8F-877F-FDB21D3717CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{49400532-7B7B-4C8F-877F-FDB21D3717CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{49400532-7B7B-4C8F-877F-FDB21D3717CC}.Release|Any CPU.Build.0 = Release|Any CPU
{F25AB0AB-F046-4D4B-97DD-634C2725D050}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F25AB0AB-F046-4D4B-97DD-634C2725D050}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F25AB0AB-F046-4D4B-97DD-634C2725D050}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F25AB0AB-F046-4D4B-97DD-634C2725D050}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -48,6 +56,7 @@ Global
{9254C4F1-2305-457F-A7DB-9BEE8B2A76B7} = {EFBB5EFD-334C-487E-8C31-9F21FB106FFF}
{6FD2F651-A074-4C07-93A3-0527EB19F3BF} = {8D5ED59A-C3FC-4E35-9E73-A8FF5492DFCD}
{49400532-7B7B-4C8F-877F-FDB21D3717CC} = {EFBB5EFD-334C-487E-8C31-9F21FB106FFF}
{F25AB0AB-F046-4D4B-97DD-634C2725D050} = {9830BBC8-5A1C-4FC7-AFC8-C3F5247B2151}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5B7EB520-AC1E-4B28-96B6-EC550256D338}
Expand Down
7 changes: 6 additions & 1 deletion build/dependencies.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@
<PropertyGroup Label="Package Versions">
<AngleSharpVersion>0.9.9</AngleSharpVersion>
<AspNetCoreVersion>2.0.0</AspNetCoreVersion>
<DotNetTestSdkVersion>16.1.0</DotNetTestSdkVersion>
<IdentityModelCoreVersion>2.1.4</IdentityModelCoreVersion>
<JetBrainsVersion>11.0.0</JetBrainsVersion>
<JetBrainsVersion>2019.1.1</JetBrainsVersion>
<JsonNetVersion>10.0.3</JsonNetVersion>
<JustEatHttpClientInterceptionVersion>2.0.1</JustEatHttpClientInterceptionVersion>
<MartinCostelloLoggingXUnitVersion>0.1.0</MartinCostelloLoggingXUnitVersion>
<ShouldlyVersion>3.0.2</ShouldlyVersion>
<XunitVersion>2.4.1</XunitVersion>
</PropertyGroup>

</Project>
4 changes: 2 additions & 2 deletions build/version.props
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<Project>

<PropertyGroup>
<VersionPrefix>2.0.0</VersionPrefix>
<VersionSuffix>rtm</VersionSuffix>
<VersionPrefix>2.0.1</VersionPrefix>
<VersionSuffix>preview</VersionSuffix>
<VersionSuffix Condition=" '$(VersionSuffix)' != '' AND '$(BuildNumber)' != '' ">$(VersionSuffix)-$(BuildNumber)</VersionSuffix>
</PropertyGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\build\common.props" />
<PropertyGroup>
<AspNetCoreVersion>2.2.0</AspNetCoreVersion>
<RootNamespace>AspNet.Security.OpenId</RootNamespace>
<TargetFramework>netcoreapp2.2</TargetFramework>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<Content Include="xunit.runner.json;**\bundle.json" Exclude="bin\**\bundle.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\**\*.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" Version="$(AspNetCoreVersion)" />
<PackageReference Include="JustEat.HttpClientInterception" Version="$(JustEatHttpClientInterceptionVersion)" />
<PackageReference Include="MartinCostello.Logging.XUnit" Version="$(MartinCostelloLoggingXUnitVersion)" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(DotNetTestSdkVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Shouldly" Version="$(ShouldlyVersion)" />
<PackageReference Include="xunit" Version="$(XunitVersion)" />
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitVersion)" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/aspnet-contrib/AspNet.Security.OpenId.Providers
* for more information concerning the license and the contributors participating to this project.
*/

using System;
using System.Security.Claims;
using System.Text;
using System.Xml.Linq;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Http;
using Microsoft.Extensions.Logging;

namespace AspNet.Security.OpenId.Infrastructure
{
/// <summary>
/// Represents a factory that creates a test application for remote OpenID Connect-based authentication providers.
/// </summary>
public static class ApplicationFactory
{
/// <summary>
/// Creates a test application for the specified type of authentication.
/// </summary>
/// <typeparam name="TOptions">The type of the configuration options for the authentication provider.</typeparam>
/// <param name="tests">The test class to configure the application for.</param>
/// <param name="configureServices">An optional delegate to configure additional application services.</param>
/// <returns>
/// The test application to use for the authentication provider.
/// </returns>
public static WebApplicationFactory<Program> CreateApplication<TOptions>(OpenIdTests<TOptions> tests, Action<IServiceCollection> configureServices = null)
where TOptions : OpenIdAuthenticationOptions
{
return new TestApplicationFactory()
.WithWebHostBuilder(builder =>
{
Configure(builder, tests);

if (configureServices != null)
{
builder.ConfigureServices(configureServices);
}
});
}

private static void Configure<TOptions>(IWebHostBuilder builder, OpenIdTests<TOptions> tests)
where TOptions : OpenIdAuthenticationOptions
{
// Use a dummy content root
builder.UseContentRoot(".");

// Route application logs to xunit output for debugging
builder.ConfigureLogging(logging =>
{
logging.AddXUnit(tests)
.SetMinimumLevel(LogLevel.Information);
});

// Configure the test application
builder.Configure(ConfigureApplication)
.ConfigureServices(services =>
{
// Allow HTTP requests to external services to be intercepted
services.AddHttpClient();
services.AddSingleton<IHttpMessageHandlerBuilderFilter, HttpRequestInterceptionFilter>(
(_) => new HttpRequestInterceptionFilter(tests.Interceptor));

// Set up the test endpoint
services.AddRouting();

// Configure authentication
var authentication = services
.AddAuthentication("External")
.AddCookie("External", o => o.ForwardChallenge = tests.DefaultScheme);

tests.RegisterAuthentication(authentication);
});
}

private static void ConfigureApplication(IApplicationBuilder app)
{
// Configure a single HTTP resource that challenges the client if unauthenticated
// or returns the logged in user's claims as XML if the request is authenticated.
app.UseAuthentication();

app.Map("/me", childApp => childApp.Run(
async context =>
{
if (context.User.Identity.IsAuthenticated)
{
string xml = IdentityToXmlString(context.User);
byte[] buffer = Encoding.UTF8.GetBytes(xml.ToString());

context.Response.StatusCode = 200;
context.Response.ContentType = "text/xml";

await context.Response.Body.WriteAsync(buffer, 0, buffer.Length);
}
else
{
await context.ChallengeAsync();
}
}));
}

private static string IdentityToXmlString(ClaimsPrincipal user)
{
var element = new XElement("claims");

foreach (var identity in user.Identities)
{
foreach (var claim in identity.Claims)
{
var node = new XElement(
"claim",
new XAttribute("type", claim.Type),
new XAttribute("value", claim.Value),
new XAttribute("valueType", claim.ValueType),
new XAttribute("issuer", claim.Issuer));

element.Add(node);
}
}

return element.ToString();
}

private sealed class TestApplicationFactory : WebApplicationFactory<Program>
{
protected override IWebHostBuilder CreateWebHostBuilder()
=> new WebHostBuilder();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/aspnet-contrib/AspNet.Security.OpenId.Providers
* for more information concerning the license and the contributors participating to this project.
*/

using System;
using JustEat.HttpClientInterception;
using Microsoft.Extensions.Http;

namespace AspNet.Security.OpenId.Infrastructure
{
/// <summary>
/// Registers an delegating handler to intercept HTTP requests made by the test application.
/// </summary>
internal sealed class HttpRequestInterceptionFilter : IHttpMessageHandlerBuilderFilter
{
private readonly HttpClientInterceptorOptions _options;

internal HttpRequestInterceptionFilter(HttpClientInterceptorOptions options)
{
_options = options;
}

public Action<HttpMessageHandlerBuilder> Configure(Action<HttpMessageHandlerBuilder> next)
{
return builder =>
{
next(builder);
builder.AdditionalHandlers.Add(_options.CreateHttpMessageHandler());
};
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/aspnet-contrib/AspNet.Security.OpenId.Providers
* for more information concerning the license and the contributors participating to this project.
*/

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web;

namespace AspNet.Security.OpenId.Infrastructure
{
/// <summary>
/// A delegating HTTP handler that loops back HTTP requests to external login providers to the local sign-in endpoint.
/// </summary>
internal sealed class LoopbackRedirectHandler : DelegatingHandler
{
internal string UserIdentity { get; set; }

internal IDictionary<string, string> UserAttributes { get; set; }

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var result = await base.SendAsync(request, cancellationToken);

// Follow the redirects to external services, assuming they are OpenID Connect-based
if (result.StatusCode == System.Net.HttpStatusCode.Found &&
!string.Equals(result.Headers.Location?.Host, "localhost", StringComparison.OrdinalIgnoreCase))
{
// Rewrite the URI to loop back to the redirected URL to simulate the user having
// successfully authenticated with the external login page they were redirected to.
var queryStringServer = HttpUtility.ParseQueryString(result.Headers.Location.Query);

string location = queryStringServer["openid.return_to"];

var locationUri = new Uri(location, UriKind.Absolute);
var queryStringSelf = HttpUtility.ParseQueryString(locationUri.Query);

queryStringSelf.Add("code", "a6ed8e7f-471f-44f1-903b-65946475f351");

foreach (var key in queryStringServer.AllKeys)
{
if (key.StartsWith("openid.", StringComparison.Ordinal) &&
!string.Equals(key, "openid.return_to", StringComparison.Ordinal))
{
queryStringSelf.Add(key, queryStringServer[key]);
}
}

// Add the appropriate parameters to satisfy the validation when looping back
queryStringSelf["openid.mode"] = "id_res";
queryStringSelf["openid.return_to"] = location;

if (!string.IsNullOrEmpty(UserIdentity))
{
queryStringSelf["openid.claimed_id"] = UserIdentity;
queryStringSelf["openid.identity"] = UserIdentity;
}

if (UserAttributes != null)
{
foreach (var pair in UserAttributes)
{
queryStringSelf[$"openid.ax.value.{pair.Key}"] = pair.Value;
}
}

var builder = new UriBuilder(location)
{
Query = queryStringSelf.ToString(),
};

var redirectRequest = new HttpRequestMessage(request.Method, builder.Uri);

// Forward on the headers and cookies
foreach (var header in result.Headers)
{
redirectRequest.Headers.Add(header.Key, header.Value);
}

redirectRequest.Headers.Add("Cookie", result.Headers.GetValues("Set-Cookie"));

// Follow the redirect URI
return await base.SendAsync(redirectRequest, cancellationToken);
}

return result;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/aspnet-contrib/AspNet.Security.OpenId.Providers
* for more information concerning the license and the contributors participating to this project.
*/

namespace AspNet.Security.OpenId.Infrastructure
{
/// <summary>
/// A stub class representing the program class for the test application.
/// </summary>
public sealed class Program
{
}
}
Loading