diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index ab09c9c..e23cc5d 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -17,7 +17,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: 5.0.x + dotnet-version: 6.0.x - name: Restore dependencies run: dotnet restore working-directory: .\main @@ -34,9 +34,12 @@ jobs: -p:AssemblyInformationalVersion=${{ steps.nbgv.outputs.AssemblyInformationalVersion }} --no-restore working-directory: .\main - - name: Test + - name: ProxyTests run: dotnet test -c Release --no-build --verbosity normal .\test\ReCode.Cocoon.Proxy.Tests working-directory: .\main + - name: LegacyTests + run: | + ~/.nuget/packages/xunit.runner.console/2.4.1/tools/net472/xunit.console.exe $Env:GITHUB_WORKSPACE\main\test\ReCode.Cocoon.Legacy.Tests\bin\Release\net45\ReCode.Cocoon.Legacy.Tests.dll - name: PackLegacy run: > dotnet pack --configuration Release --no-build diff --git a/README.md b/README.md index 332c07b..1f002cd 100644 --- a/README.md +++ b/README.md @@ -18,30 +18,6 @@ An implementation of the Strangler Fig pattern for ASP.NET Core ``` -### Amend `Startup.Auth.cs` - -In the file `/App_Start/Startup.Auth.cs`, add delegate to the `OnApplyRedirect` property that supresses the redirect to the login URL for `/facadeauth` requests. -```c# -app.UseCookieAuthentication(new CookieAuthenticationOptions -{ - AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, - LoginPath = new PathString("/Account/Login"), - Provider = new CookieAuthenticationProvider - { - OnApplyRedirect = context => - { - /* This prevents the cookie auth model trying to redirect on a 401 */ - if(context.Request.Uri.ToString().Contains("facadeauth") && context.Response.StatusCode == 401) - { - return; - } - - context.Response.Redirect(context.RedirectUri); - } - } -}); -``` - ### Disable MVC routing for the new handlers ``` @@ -151,4 +127,4 @@ dotnet tool install --global Microsoft.Playwright.CLI playwright install ``` -You can now run the tests inside the test/integration folder. \ No newline at end of file +You can now run the tests inside the test/integration folder. diff --git a/main/Cocoon.sln b/main/Cocoon.sln index 67673a7..a19e7b6 100644 --- a/main/Cocoon.sln +++ b/main/Cocoon.sln @@ -26,14 +26,21 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReCode.Cocoon.Proxy.Benchmarks", "benchmarks\ReCode.Cocoon.Proxy.Benchmarks\ReCode.Cocoon.Proxy.Benchmarks.csproj", "{33521FFB-D30A-40E5-8AA8-2E394CD01E76}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{19C51172-6F93-479C-BF81-EDA51D6597AE}" -ProjectSection(SolutionItems) = preProject - Directory.Build.props = Directory.Build.props - version.json = version.json -EndProjectSection + ProjectSection(SolutionItems) = preProject + Directory.Build.props = Directory.Build.props + version.json = version.json + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReCode.Cocoon.Proxy.BlazorCodeGen", "src\ReCode.Cocoon.Proxy.BlazorCodeGen\ReCode.Cocoon.Proxy.BlazorCodeGen.csproj", "{7AFD9FAF-92A5-4EC6-BEA4-EED5C521A6E1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReCode.Cocoon.Proxy.BlazorCodeGen.Tests", "test\ReCode.Cocoon.Proxy.BlazorCodeGen.Tests\ReCode.Cocoon.Proxy.BlazorCodeGen.Tests.csproj", "{E790C7E7-1C7A-47EF-8F6A-895F104C2BDA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReCode.Cocoon.Proxy.CodeGenerator.Tests", "test\ReCode.Cocoon.Proxy.CodeGenerator.Tests\ReCode.Cocoon.Proxy.CodeGenerator.Tests.csproj", "{C198CA0A-D982-4295-BF18-8114F8E887F8}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{6AD14C47-2BE5-4428-AB83-641684162C8A}" ProjectSection(SolutionItems) = preProject ..\.github\workflows\dotnet-develop.yml = ..\.github\workflows\dotnet-develop.yml + ..\.github\workflows\dotnet.yml = ..\.github\workflows\dotnet.yml EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "integration", "integration", "{D59BBE80-D414-4AFD-BE3A-A50659F8E4C7}" diff --git a/main/src/ReCode.Cocoon.Legacy/Session/SessionApiHandler.cs b/main/src/ReCode.Cocoon.Legacy/Session/SessionApiHandler.cs index 57eb3dd..e67a1c5 100644 --- a/main/src/ReCode.Cocoon.Legacy/Session/SessionApiHandler.cs +++ b/main/src/ReCode.Cocoon.Legacy/Session/SessionApiHandler.cs @@ -32,7 +32,7 @@ public void ProcessRequest(HttpContextBase context) else if ("PUT".Equals(context.Request.HttpMethod, StringComparison.OrdinalIgnoreCase)) { var typeName = context.Request.QueryString["type"]; - if (string.IsNullOrEmpty(key)) + if (string.IsNullOrEmpty(typeName)) { // Bad request context.Response.StatusCode = 400; diff --git a/main/src/ReCode.Cocoon.Proxy.BlazorServer/ReCode.Cocoon.Proxy.BlazorServer.csproj b/main/src/ReCode.Cocoon.Proxy.BlazorServer/ReCode.Cocoon.Proxy.BlazorServer.csproj index 5329d4e..0dfa816 100644 --- a/main/src/ReCode.Cocoon.Proxy.BlazorServer/ReCode.Cocoon.Proxy.BlazorServer.csproj +++ b/main/src/ReCode.Cocoon.Proxy.BlazorServer/ReCode.Cocoon.Proxy.BlazorServer.csproj @@ -7,6 +7,9 @@ + diff --git a/main/src/ReCode.Cocoon.Proxy.BlazorWasm/CocoonProxyBlazorWasmRouting.cs b/main/src/ReCode.Cocoon.Proxy.BlazorWasm/CocoonProxyBlazorWasmRouting.cs index 2c6e2b5..99c1117 100644 --- a/main/src/ReCode.Cocoon.Proxy.BlazorWasm/CocoonProxyBlazorWasmRouting.cs +++ b/main/src/ReCode.Cocoon.Proxy.BlazorWasm/CocoonProxyBlazorWasmRouting.cs @@ -10,20 +10,35 @@ namespace Microsoft.AspNetCore.Routing { public static class CocoonProxyBlazorWasmRouting { - public static IEndpointRouteBuilder MapCocoonProxyWithBlazor(this IEndpointRouteBuilder endpoints, Type programType) => - MapCocoonProxyWithBlazor(endpoints, BlazorRouteDiscovery.FindRoutes(programType)); + public static IEndpointRouteBuilder MapCocoonProxyWithBlazor(this IEndpointRouteBuilder endpoints, Type type) + { + Func blazorTestFunc; + if (type.Name == "CocoonBlazorRouteTester") + { + var instance = Activator.CreateInstance(type); + var method = type.GetMethod("IsMatch"); + blazorTestFunc = (Func) method!.CreateDelegate(typeof(Func), instance); + } + else + { + var blazorPaths = BlazorRouteDiscovery.FindRoutes(type); + var blazorRoutes = new BlazorRoutes(blazorPaths); + var method = typeof(BlazorRoutes).GetMethod("Contains"); + blazorTestFunc = (Func) method!.CreateDelegate(typeof(Func), blazorRoutes); + } - public static IEndpointRouteBuilder MapCocoonProxyWithBlazor(this IEndpointRouteBuilder endpoints, IEnumerable blazorPaths) + return MapCocoonProxyWithBlazor(endpoints, blazorTestFunc); + } + + public static IEndpointRouteBuilder MapCocoonProxyWithBlazor(this IEndpointRouteBuilder endpoints, Func blazorRouteTest) { var cocoonProxy = endpoints.ServiceProvider.GetRequiredService(); - var blazorRoutes = new BlazorRoutes(blazorPaths); - var app = endpoints.CreateApplicationBuilder(); app.Use(async (httpContext, next) => { - if (blazorRoutes.Contains(httpContext.Request.Path)) + if (blazorRouteTest(httpContext.Request.Path)) { httpContext.Request.Path = "/index.html"; diff --git a/main/src/ReCode.Cocoon.Proxy.BlazorWasm/RouteTesterAttribute.cs b/main/src/ReCode.Cocoon.Proxy.BlazorWasm/RouteTesterAttribute.cs new file mode 100644 index 0000000..d5a9b88 --- /dev/null +++ b/main/src/ReCode.Cocoon.Proxy.BlazorWasm/RouteTesterAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace ReCode.Cocoon.Proxy.Blazor +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + public sealed class RouteTesterAttribute : Attribute + { + public RouteTesterAttribute(params Type[] types) + { + } + } +} \ No newline at end of file diff --git a/main/src/ReCode.Cocoon.Proxy/Authentication/MessageClaim.cs b/main/src/ReCode.Cocoon.Proxy/Authentication/MessageClaim.cs index bf0f7ca..36e2a2c 100644 --- a/main/src/ReCode.Cocoon.Proxy/Authentication/MessageClaim.cs +++ b/main/src/ReCode.Cocoon.Proxy/Authentication/MessageClaim.cs @@ -11,22 +11,22 @@ namespace ReCode.Cocoon.Proxy.Authentication public class MessageClaim { [Key(0)] - public string Issuer { get; set; } + public string? Issuer { get; set; } [Key(1)] - public string OriginalIssuer { get; set; } + public string? OriginalIssuer { get; set; } [Key(2)] - public IDictionary Properties { get; set; } + public IDictionary? Properties { get; set; } [Key(3)] - public string Type { get; set; } + public string? Type { get; set; } [Key(4)] - public string Value { get; set; } + public string? Value { get; set; } [Key(5)] - public string ValueType { get; set; } + public string? ValueType { get; set; } public Claim ToClaim() { diff --git a/main/src/ReCode.Cocoon.Proxy/Authentication/MessageIdentity.cs b/main/src/ReCode.Cocoon.Proxy/Authentication/MessageIdentity.cs index 79a77f3..3becf04 100644 --- a/main/src/ReCode.Cocoon.Proxy/Authentication/MessageIdentity.cs +++ b/main/src/ReCode.Cocoon.Proxy/Authentication/MessageIdentity.cs @@ -12,25 +12,27 @@ namespace ReCode.Cocoon.Proxy.Authentication public class MessageIdentity { [Key(0)] - public string AuthenticationType { get; set; } + public string? AuthenticationType { get; set; } [Key(1)] public bool IsAuthenticated { get; set; } [Key(2)] - public string Label { get; set; } + public string? Label { get; set; } [Key(3)] - public string NameClaimType { get; set; } + public string? NameClaimType { get; set; } [Key(4)] - public string RoleClaimType { get; set; } + public string? RoleClaimType { get; set; } [Key(5)] - public List Claims { get; set; } + public List? Claims { get; set; } public ClaimsIdentity ToClaimsIdentity() { + if (Claims is null) return new ClaimsIdentity(); + var claims = Claims.Select(c => c.ToClaim()); return new ClaimsIdentity(claims, AuthenticationType, NameClaimType, RoleClaimType); diff --git a/main/src/ReCode.Cocoon.Proxy/Authentication/MessagePrincipal.cs b/main/src/ReCode.Cocoon.Proxy/Authentication/MessagePrincipal.cs index cebe575..42cbdea 100644 --- a/main/src/ReCode.Cocoon.Proxy/Authentication/MessagePrincipal.cs +++ b/main/src/ReCode.Cocoon.Proxy/Authentication/MessagePrincipal.cs @@ -12,10 +12,12 @@ namespace ReCode.Cocoon.Proxy.Authentication public class MessagePrincipal { [Key(0)] - public List Identities { get; set; } + public List? Identities { get; set; } public ClaimsPrincipal ToClaimsPrincipal() { + if (Identities is null) return new ClaimsPrincipal(); + var identities = Identities.Select(i => i.ToClaimsIdentity()); return new ClaimsPrincipal(identities); diff --git a/main/src/ReCode.Cocoon.Proxy/Session/CocoonSession.cs b/main/src/ReCode.Cocoon.Proxy/Session/CocoonSession.cs index d1c2f43..25f950d 100644 --- a/main/src/ReCode.Cocoon.Proxy/Session/CocoonSession.cs +++ b/main/src/ReCode.Cocoon.Proxy/Session/CocoonSession.cs @@ -51,6 +51,8 @@ public void Set(string key, T? value) activity?.AddTag("key", key); var bytes = await _client.GetAsync(key, _context.Request); + if (bytes is null) return default; + var value = SessionValueDeserializer.Deserialize(bytes); CacheValue(key, bytes, value); diff --git a/main/test/ReCode.Cocoon.Proxy.BlazorCodeGen.Tests/Bar.cs b/main/test/ReCode.Cocoon.Proxy.BlazorCodeGen.Tests/Bar.cs new file mode 100644 index 0000000..8c90511 --- /dev/null +++ b/main/test/ReCode.Cocoon.Proxy.BlazorCodeGen.Tests/Bar.cs @@ -0,0 +1,8 @@ +namespace MySourceGenerator.Tests +{ + [Microsoft.AspNetCore.Components.RouteAttribute("bar")] + public class Bar + { + + } +} \ No newline at end of file diff --git a/main/test/ReCode.Cocoon.Proxy.BlazorCodeGen.Tests/Dummy.cs b/main/test/ReCode.Cocoon.Proxy.BlazorCodeGen.Tests/Dummy.cs new file mode 100644 index 0000000..faba2b5 --- /dev/null +++ b/main/test/ReCode.Cocoon.Proxy.BlazorCodeGen.Tests/Dummy.cs @@ -0,0 +1,20 @@ +using System; + +namespace Dummy +{ + [Route("dummy")] + public class Dummy + { + + } + + internal class RouteAttribute : Attribute + { + private readonly string _dummy; + + public RouteAttribute(string dummy) + { + _dummy = dummy; + } + } +} \ No newline at end of file diff --git a/main/test/ReCode.Cocoon.Proxy.BlazorCodeGen.Tests/Foo.cs b/main/test/ReCode.Cocoon.Proxy.BlazorCodeGen.Tests/Foo.cs new file mode 100644 index 0000000..4046935 --- /dev/null +++ b/main/test/ReCode.Cocoon.Proxy.BlazorCodeGen.Tests/Foo.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Components; + +namespace MySourceGenerator.Tests +{ + [Route("foo")] + public class Foo + { + + } + + [Route("Admin/AdminPage")] + public class AdminPage + { + + } +} \ No newline at end of file diff --git a/main/test/ReCode.Cocoon.Proxy.BlazorCodeGen.Tests/Quux.cs b/main/test/ReCode.Cocoon.Proxy.BlazorCodeGen.Tests/Quux.cs new file mode 100644 index 0000000..121f397 --- /dev/null +++ b/main/test/ReCode.Cocoon.Proxy.BlazorCodeGen.Tests/Quux.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Components; + +namespace MySourceGenerator.Tests +{ + [Route("quux")] + public class Quux + { + + } +} \ No newline at end of file diff --git a/main/test/ReCode.Cocoon.Proxy.BlazorCodeGen.Tests/ReCode.Cocoon.Proxy.BlazorCodeGen.Tests.csproj b/main/test/ReCode.Cocoon.Proxy.BlazorCodeGen.Tests/ReCode.Cocoon.Proxy.BlazorCodeGen.Tests.csproj new file mode 100644 index 0000000..7e68479 --- /dev/null +++ b/main/test/ReCode.Cocoon.Proxy.BlazorCodeGen.Tests/ReCode.Cocoon.Proxy.BlazorCodeGen.Tests.csproj @@ -0,0 +1,27 @@ + + + + net5.0 + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/main/test/ReCode.Cocoon.Proxy.BlazorCodeGen.Tests/RouteAttribute.cs b/main/test/ReCode.Cocoon.Proxy.BlazorCodeGen.Tests/RouteAttribute.cs new file mode 100644 index 0000000..bbf4e0b --- /dev/null +++ b/main/test/ReCode.Cocoon.Proxy.BlazorCodeGen.Tests/RouteAttribute.cs @@ -0,0 +1,24 @@ +using System; + +namespace Microsoft.AspNetCore.Components +{ + public class RouteAttribute : Attribute + { + private readonly string _template; + + public RouteAttribute(string template) + { + _template = template; + } + } + + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + public sealed class RouteTesterAttribute : Attribute + { + // See the attribute guidelines at + // http://go.microsoft.com/fwlink/?LinkId=85236 + public RouteTesterAttribute(params Type[] types) + { + } + } +} \ No newline at end of file diff --git a/main/test/ReCode.Cocoon.Proxy.BlazorCodeGen.Tests/UnitTest1.cs b/main/test/ReCode.Cocoon.Proxy.BlazorCodeGen.Tests/UnitTest1.cs new file mode 100644 index 0000000..81a0aee --- /dev/null +++ b/main/test/ReCode.Cocoon.Proxy.BlazorCodeGen.Tests/UnitTest1.cs @@ -0,0 +1,23 @@ +using System; +using ReCode.Cocoon.Proxy.Blazor; +using Xunit; + +namespace ReCode.Cocoon.Proxy.BlazorCodeGen.Tests +{ + public class UnitTest1 + { + [Fact] + public void MatchesFoo() + { + var target = new CocoonBlazorRouteTester(); + Assert.True(target.IsMatch("/foo")); + } + + [Fact] + public void MatchesAdminPage() + { + var target = new CocoonBlazorRouteTester(); + Assert.True(target.IsMatch("/Admin/AdminPage")); + } + } +} diff --git a/main/test/ReCode.Cocoon.Proxy.CodeGenerator.Tests/ReCode.Cocoon.Proxy.CodeGenerator.Tests.csproj b/main/test/ReCode.Cocoon.Proxy.CodeGenerator.Tests/ReCode.Cocoon.Proxy.CodeGenerator.Tests.csproj new file mode 100644 index 0000000..0d56c8b --- /dev/null +++ b/main/test/ReCode.Cocoon.Proxy.CodeGenerator.Tests/ReCode.Cocoon.Proxy.CodeGenerator.Tests.csproj @@ -0,0 +1,26 @@ + + + + net5.0 + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/main/test/ReCode.Cocoon.Proxy.CodeGenerator.Tests/RouteTesterGeneratorTests.cs b/main/test/ReCode.Cocoon.Proxy.CodeGenerator.Tests/RouteTesterGeneratorTests.cs new file mode 100644 index 0000000..0a12459 --- /dev/null +++ b/main/test/ReCode.Cocoon.Proxy.CodeGenerator.Tests/RouteTesterGeneratorTests.cs @@ -0,0 +1,84 @@ +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.Loader; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using ReCode.Cocoon.Proxy.BlazorCodeGen; +using Xunit; +using Xunit.Abstractions; + +namespace RouteTesterGen.Tests +{ + public class RouteTesterGeneratorTests + { + private readonly ITestOutputHelper _output; + private Func _isMatch; + + public RouteTesterGeneratorTests(ITestOutputHelper output) + { + _output = output; + } + + [Theory] + [InlineData("/posts")] + [InlineData("/posts/42")] + [InlineData("/posts/42/edit")] + [InlineData("/people")] + [InlineData("/people/42")] + [InlineData("/people/42/edit")] + [InlineData("/files/foo/bar/quux.pdf")] + [InlineData("/Admin/AdminPage")] + public void MatchesPath(string path) + { + _isMatch ??= Build(); + Assert.True(_isMatch(path)); + } + + private Func Build() + { + var routes = new[] + { + "/posts", + "/posts/{id}", + "/posts/{id}/edit", + "/people", + "/people/{id}", + "/people/{id}/edit", + "/files/{*path}", + "/Admin/AdminPage", + }; + var target = new RouteTesterGenerator(routes); + var actual = target.Generate(); + + var assemblyName = Path.GetRandomFileName(); + var references = new[] + { + typeof(object).Assembly.Location + }.Select(p => MetadataReference.CreateFromFile(p)) + .ToArray(); + var syntax = CSharpSyntaxTree.ParseText(actual); + var compilation = CSharpCompilation.Create(assemblyName, new[] {syntax}, + references, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + using var ms = new MemoryStream(); + var result = compilation.Emit(ms); + if (result.Diagnostics.Length > 0) + { + foreach (var diagnostic in result.Diagnostics) + { + _output.WriteLine(diagnostic.ToString()); + } + } + Assert.True(result.Success); + ms.Position = 0; + var assembly = AssemblyLoadContext.Default.LoadFromStream(ms); + var type = assembly.GetType("ReCode.Cocoon.Proxy.Blazor.CocoonBlazorRouteTester"); + Assert.NotNull(type); + var method = type.GetMethod("IsMatch"); + Assert.NotNull(method); + var obj = Activator.CreateInstance(type); + return method.CreateDelegate>(obj); + } + } +} \ No newline at end of file diff --git a/main/test/ReCode.Cocoon.Proxy.CodeGenerator.Tests/UnitTest1.cs b/main/test/ReCode.Cocoon.Proxy.CodeGenerator.Tests/UnitTest1.cs new file mode 100644 index 0000000..e5992b9 --- /dev/null +++ b/main/test/ReCode.Cocoon.Proxy.CodeGenerator.Tests/UnitTest1.cs @@ -0,0 +1,14 @@ +using System; +using Xunit; + +namespace ReCode.Cocoon.Proxy.CodeGenerator.Tests +{ + public class UnitTest1 + { + [Fact] + public void Test1() + { + + } + } +} diff --git a/samples/BlazorCocoon/Server/BlazorCocoon.Server.csproj b/samples/BlazorCocoon/Server/BlazorCocoon.Server.csproj index be7f7ed..60709b5 100644 --- a/samples/BlazorCocoon/Server/BlazorCocoon.Server.csproj +++ b/samples/BlazorCocoon/Server/BlazorCocoon.Server.csproj @@ -10,15 +10,15 @@ - - - - + + + + - +