diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index f0ca9fb4..1cebf3bc 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -14,15 +14,15 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Install .NET Core 7.0.x + - name: Install .NET Core 8.0.x uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Install Node.js uses: actions/setup-node@v3 with: - node-version: 18 + node-version: 20 - name: Setup Pages uses: actions/configure-pages@v3 diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index c67dada1..56b8d304 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -19,7 +19,7 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Setup .NET + - name: Setup .NET 6 uses: actions/setup-dotnet@v3 with: dotnet-version: 6.0.x @@ -28,6 +28,11 @@ jobs: uses: actions/setup-dotnet@v3 with: dotnet-version: 7.0.x + + - name: Setup .NET 8 + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 8.0.x - name: Test run: ./build.ps1 test diff --git a/docs/guide/nunit.md b/docs/guide/nunit.md index 5b211737..e966d332 100644 --- a/docs/guide/nunit.md +++ b/docs/guide/nunit.md @@ -27,7 +27,7 @@ public class Application } } ``` -snippet source | anchor +snippet source | anchor Then reference the `AlbaHost` in tests like this sample: @@ -48,5 +48,5 @@ public class sample_integration_fixture } } ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/scenarios/formdata.md b/docs/scenarios/formdata.md index 8efd6485..2d710a13 100644 --- a/docs/scenarios/formdata.md +++ b/docs/scenarios/formdata.md @@ -36,7 +36,7 @@ There's a second overload that attempts to use an object and its properties to p [Fact] public async Task can_bind_to_form_data() { - await using var system = AlbaHost.ForStartup(); + await using var system = await AlbaHost.For(); var input = new InputModel { One = "one", diff --git a/src/Alba.Testing/Acceptance/assertions_against_redirects.cs b/src/Alba.Testing/Acceptance/assertions_against_redirects.cs index ece74df5..b216b685 100644 --- a/src/Alba.Testing/Acceptance/assertions_against_redirects.cs +++ b/src/Alba.Testing/Acceptance/assertions_against_redirects.cs @@ -10,7 +10,7 @@ public class assertions_against_redirects [Fact] public async Task redirect() { - await using var system = AlbaHost.ForStartup(); + await using var system = await AlbaHost.For(); await system.Scenario(_ => { _.Get.Url("/auth/redirect"); @@ -24,15 +24,13 @@ public async Task not_redirected() { var result = await Exception.ShouldBeThrownBy(async () => { - using (var system = AlbaHost.ForStartup()) + await using var system = await AlbaHost.For(); + await system.Scenario(_ => { - await system.Scenario(_ => - { - _.Get.Url("/api/values"); + _.Get.Url("/api/values"); - _.RedirectShouldBe("/else"); - }); - } + _.RedirectShouldBe("/else"); + }); }); result.Message.ShouldContain("Expected to be redirected to '/else' but was ''."); @@ -43,7 +41,7 @@ public async Task redirect_wrong_value() { var result = await Exception.ShouldBeThrownBy(async () => { - await using var system = AlbaHost.ForStartup(); + await using var system = await AlbaHost.For(); await system.Scenario(_ => { _.Get.Url("/auth/redirect"); @@ -58,15 +56,13 @@ await system.Scenario(_ => [Fact] public async Task redirect_permanent() { - using (var system = AlbaHost.ForStartup()) + await using var system = await AlbaHost.For(); + await system.Scenario(_ => { - await system.Scenario(_ => - { - _.Get.Url("/auth/redirectpermanent"); + _.Get.Url("/auth/redirectpermanent"); - _.RedirectPermanentShouldBe("/api/values"); - }); - } + _.RedirectPermanentShouldBe("/api/values"); + }); } [Fact] @@ -74,7 +70,7 @@ public async Task redirect_permanent_wrong_value() { var result = await Exception.ShouldBeThrownBy(async () => { - await using var system = AlbaHost.ForStartup(); + await using var system = await AlbaHost.For(); await system.Scenario(_ => { _.Get.Url("/auth/redirectpermanent"); @@ -91,7 +87,7 @@ public async Task redirect_permanent_non_permanent_result() { var result = await Exception.ShouldBeThrownBy(async () => { - await using var system = AlbaHost.ForStartup(); + await using var system = await AlbaHost.For(); await system.Scenario(_ => { _.Get.Url("/auth/redirect"); diff --git a/src/Alba.Testing/Acceptance/customize_before_each_and_after_each.cs b/src/Alba.Testing/Acceptance/customize_before_each_and_after_each.cs index 0baba1ee..6f23879e 100644 --- a/src/Alba.Testing/Acceptance/customize_before_each_and_after_each.cs +++ b/src/Alba.Testing/Acceptance/customize_before_each_and_after_each.cs @@ -11,13 +11,12 @@ public class customize_before_each_and_after_each [Fact] public async Task before_each_and_after_each_is_called() { - await using var host = AlbaHost - .ForStartup() - .BeforeEach(c => - { - BeforeContext = c; - }) - .AfterEach(c => AfterContext = c); + await using var host = await AlbaHost.For(); + host.BeforeEach(c => + { + BeforeContext = c; + }) + .AfterEach(c => AfterContext = c); BeforeContext = AfterContext = null; diff --git a/src/Alba.Testing/Acceptance/data_binding_in_mvc_app.cs b/src/Alba.Testing/Acceptance/data_binding_in_mvc_app.cs index 043c7b56..baf5791a 100644 --- a/src/Alba.Testing/Acceptance/data_binding_in_mvc_app.cs +++ b/src/Alba.Testing/Acceptance/data_binding_in_mvc_app.cs @@ -13,7 +13,7 @@ public class data_binding_in_mvc_app [Fact] public async Task can_bind_to_form_data() { - await using var system = AlbaHost.ForStartup(); + await using var system = await AlbaHost.For(); var input = new InputModel { One = "one", @@ -40,7 +40,7 @@ await system.Scenario(_ => [Fact] public async Task can_bind_to_form_data_as_dictionary() { - await using var system = AlbaHost.ForStartup(); + await using var system = await AlbaHost.For(); var dict = new Dictionary {{"One", "one"}, {"Two", "two"}, {"Three", "three"}}; diff --git a/src/Alba.Testing/Acceptance/specs_against_aspnet_core_app.cs b/src/Alba.Testing/Acceptance/specs_against_aspnet_core_app.cs index 96f3d65b..e2a65d13 100644 --- a/src/Alba.Testing/Acceptance/specs_against_aspnet_core_app.cs +++ b/src/Alba.Testing/Acceptance/specs_against_aspnet_core_app.cs @@ -13,20 +13,15 @@ namespace Alba.Testing.Acceptance { - public class specs_against_aspnet_core_app : IDisposable + public class specs_against_aspnet_core_app : IAsyncLifetime { - private readonly AlbaHost _system; + private IAlbaHost _system; private Task run(Action configuration) { return _system.Scenario(configuration); } - public specs_against_aspnet_core_app() - { - _system = AlbaHost.ForStartup(); - } - public void Dispose() { _system?.Dispose(); @@ -386,5 +381,15 @@ public Task returns_successfully_when_passed_object_is_passed_to_Input() _.ContentShouldContain("somevalue"); }); } + + public async Task InitializeAsync() + { + _system = await AlbaHost.For(); + } + + public async Task DisposeAsync() + { + await _system.DisposeAsync(); + } } } \ No newline at end of file diff --git a/src/Alba.Testing/Acceptance/using_custom_service_registrations.cs b/src/Alba.Testing/Acceptance/using_custom_service_registrations.cs index 191462db..d2c1dfc3 100644 --- a/src/Alba.Testing/Acceptance/using_custom_service_registrations.cs +++ b/src/Alba.Testing/Acceptance/using_custom_service_registrations.cs @@ -14,36 +14,35 @@ public class using_custom_service_registrations [Fact] public async Task override_service_registration_in_bootstrapping() { - ValuesController.LastWidget = new IWidget[0]; + ValuesController.LastWidget = Array.Empty(); - using (var system = AlbaHost.ForStartup(builder => + using var system = await AlbaHost.For(builder => { - return builder.ConfigureServices((c, _) => + builder.ConfigureServices((c, _) => { _.AddTransient(); }); - })) - { - ValuesController.LastWidget = null; + }); - // The default registration is a GreenWidget + ValuesController.LastWidget = null; - await system.Scenario(_ => - { - - _.Put.Url("/api/values/foo").ContentType("application/json"); - }); + // The default registration is a GreenWidget + + await system.Scenario(_ => + { + + _.Put.Url("/api/values/foo").ContentType("application/json"); + }); - ValuesController.LastWidget.Length.ShouldBe(2); - } + ValuesController.LastWidget.Length.ShouldBe(2); } [Fact] - public void can_request_services() + public async Task can_request_services() { - using var system = AlbaHost.ForStartup(builder => + using var system = await AlbaHost.For(builder => { - return builder.ConfigureServices((c, _) => { _.AddHttpContextAccessor(); }); + builder.ConfigureServices((c, _) => { _.AddHttpContextAccessor(); }); }); var accessor1 = system.Services.GetService(); diff --git a/src/Alba.Testing/ActivityTests.cs b/src/Alba.Testing/ActivityTests.cs index f7217275..113d2a76 100644 --- a/src/Alba.Testing/ActivityTests.cs +++ b/src/Alba.Testing/ActivityTests.cs @@ -7,7 +7,15 @@ namespace Alba.Testing; -public class ActivityTests +// activity listener cannot be tested in parallel with other tests +[CollectionDefinition(nameof(ActivityCollection), DisableParallelization = true)] +public class ActivityCollection +{ + +} + +[Collection(nameof(ActivityCollection))] +public class ActivityTests { [Fact] public async Task ActivityTagged_AsExpected() diff --git a/src/Alba.Testing/Alba.Testing.csproj b/src/Alba.Testing/Alba.Testing.csproj index 58161e9e..a6fe8c98 100644 --- a/src/Alba.Testing/Alba.Testing.csproj +++ b/src/Alba.Testing/Alba.Testing.csproj @@ -1,6 +1,6 @@ - + - net6.0;net7.0 + net6.0;net8.0 portable Alba.Testing Alba.Testing @@ -8,7 +8,7 @@ - + @@ -17,12 +17,11 @@ - - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Alba.Testing/Assertions/RedirectAssertionTests.cs b/src/Alba.Testing/Assertions/RedirectAssertionTests.cs index e4502a97..25cb7b43 100644 --- a/src/Alba.Testing/Assertions/RedirectAssertionTests.cs +++ b/src/Alba.Testing/Assertions/RedirectAssertionTests.cs @@ -1,6 +1,5 @@ using System.Linq; using Alba.Assertions; -using Baseline; using Shouldly; using Xunit; diff --git a/src/Alba.Testing/CrudeRouter.cs b/src/Alba.Testing/CrudeRouter.cs index 0618c367..b2b411df 100644 --- a/src/Alba.Testing/CrudeRouter.cs +++ b/src/Alba.Testing/CrudeRouter.cs @@ -4,8 +4,8 @@ using System.Linq.Expressions; using System.Reflection; using System.Threading.Tasks; -using Baseline; -using Baseline.Reflection; +using JasperFx.Core; +using JasperFx.Core.Reflection; using Microsoft.AspNetCore.Http; namespace Alba.Testing diff --git a/src/Alba.Testing/FormDataExtensionsTests.cs b/src/Alba.Testing/FormDataExtensionsTests.cs index 70fdd6ca..8b63eacb 100644 --- a/src/Alba.Testing/FormDataExtensionsTests.cs +++ b/src/Alba.Testing/FormDataExtensionsTests.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.IO; using System.Net; -using Baseline; +using JasperFx.Core; using Microsoft.AspNetCore.Http; using Shouldly; using Xunit; diff --git a/src/Alba.Testing/Samples/Quickstart.cs b/src/Alba.Testing/Samples/Quickstart.cs index 19ba5576..06ec93e5 100644 --- a/src/Alba.Testing/Samples/Quickstart.cs +++ b/src/Alba.Testing/Samples/Quickstart.cs @@ -1,7 +1,7 @@ using System; using System.IO; using System.Threading.Tasks; -using Baseline; +using JasperFx.Core; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; diff --git a/src/Alba.Testing/Security/IdentityServerFixture.cs b/src/Alba.Testing/Security/IdentityServerFixture.cs index ea8cfc50..aa933197 100644 --- a/src/Alba.Testing/Security/IdentityServerFixture.cs +++ b/src/Alba.Testing/Security/IdentityServerFixture.cs @@ -20,7 +20,7 @@ public class IdentityServerFixture : IAsyncLifetime public TestServer IdentityServer { get; set; } public Task InitializeAsync() { - IdentityServer = new WebApplicationFactory().Server; + IdentityServer = new WebApplicationFactory().Server; return Task.CompletedTask; } diff --git a/src/Alba.Testing/Security/OpenConnectClientCredentialsTests.cs b/src/Alba.Testing/Security/OpenConnectClientCredentialsTests.cs index 84f588ca..5ec01f14 100644 --- a/src/Alba.Testing/Security/OpenConnectClientCredentialsTests.cs +++ b/src/Alba.Testing/Security/OpenConnectClientCredentialsTests.cs @@ -1,7 +1,7 @@ using System; using System.Threading.Tasks; using Alba.Security; -using IdentityServer; +using IdentityServer.New; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Extensions.DependencyInjection; using Shouldly; diff --git a/src/Alba.Testing/Security/OpenConnectUserPasswordTests.cs b/src/Alba.Testing/Security/OpenConnectUserPasswordTests.cs index 73593d92..d218325e 100644 --- a/src/Alba.Testing/Security/OpenConnectUserPasswordTests.cs +++ b/src/Alba.Testing/Security/OpenConnectUserPasswordTests.cs @@ -1,7 +1,7 @@ using System; using System.Threading.Tasks; using Alba.Security; -using IdentityServer; +using IdentityServer.New; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Extensions.DependencyInjection; using Shouldly; diff --git a/src/Alba.Testing/SpecificationExtensions.cs b/src/Alba.Testing/SpecificationExtensions.cs index 5b24372e..d574f43f 100644 --- a/src/Alba.Testing/SpecificationExtensions.cs +++ b/src/Alba.Testing/SpecificationExtensions.cs @@ -4,7 +4,7 @@ using System.Diagnostics; using System.Linq; using System.Threading.Tasks; -using Baseline; +using JasperFx.Core; using Shouldly; namespace Alba.Testing diff --git a/src/Alba.Testing/StringExtensionsTests.cs b/src/Alba.Testing/StringExtensionsTests.cs deleted file mode 100644 index 74c5281e..00000000 --- a/src/Alba.Testing/StringExtensionsTests.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using Shouldly; -using Xunit; - -namespace Alba.Testing -{ - public class StringExtensionsTests - { - [Fact] - public void get_comma_separated_values_from_header() - { - new[] { "v1", "v2, v3", "\"v4, b\"", "v5, v6", "v7", } - .GetCommaSeparatedHeaderValues() - .ShouldHaveTheSameElementsAs("v1", "v2", "v3", "v4, b", "v5", "v6", "v7"); - - new[] { "v1,v2, v3,\"v4, b\",v5, v6,v7" } - .GetCommaSeparatedHeaderValues() - .ShouldHaveTheSameElementsAs("v1", "v2", "v3", "v4, b", "v5", "v6", "v7"); - } - - [Fact] - public void quoted_string() - { - "foo".Quoted().ShouldBe("\"foo\""); - } - - - [Fact] - public void try_parse_http_date() - { - var date = DateTime.Today.AddHours(3); - - var datestring = date.ToString("r"); - - datestring.TryParseHttpDate().ShouldBe(date); - } - - [Fact] - public void try_parse_http_date_with_empty_string() - { - "".TryParseHttpDate().ShouldBeNull(); - } - - - } -} \ No newline at end of file diff --git a/src/Alba.Testing/reading_and_writing_xml_to_context.cs b/src/Alba.Testing/reading_and_writing_xml_to_context.cs index 1f4b05f6..257f145d 100644 --- a/src/Alba.Testing/reading_and_writing_xml_to_context.cs +++ b/src/Alba.Testing/reading_and_writing_xml_to_context.cs @@ -1,7 +1,7 @@ using System.IO; using System.Xml; using System.Xml.Serialization; -using Baseline; +using JasperFx.Core; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; diff --git a/src/Alba.sln b/src/Alba.sln index 4c9216e2..bc012264 100644 --- a/src/Alba.sln +++ b/src/Alba.sln @@ -12,20 +12,22 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApiStartupHostingModel", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebAppSecuredWithJwt", "WebAppSecuredWithJwt\WebAppSecuredWithJwt.csproj", "{91CA83AA-73DB-44B0-8A5D-5BB38532B897}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IdentityServer", "IdentityServer\IdentityServer.csproj", "{0E04B4E3-8183-4516-A1E2-768C425C98FE}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NUnitSamples", "NUnitSamples\NUnitSamples.csproj", "{EAE558C3-9759-4F7F-845B-D00056EE65DC}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApiNet6", "WebApiNet6\WebApiNet6.csproj", "{C2D01782-E9BC-4E61-A4B7-76FA48292386}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MinimalApiWithOakton", "MinimalApiWithOakton\MinimalApiWithOakton.csproj", "{2BD68525-60F2-4253-BFA7-12476F374EE9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IdentityServer.New", "IdentityServer.New\IdentityServer.New.csproj", "{B7AB5313-B55C-4AFF-940B-F17DC913AFB6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7509E7DF-DB3D-49A9-B3DA-862998D9F435}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7509E7DF-DB3D-49A9-B3DA-862998D9F435}.Release|Any CPU.ActiveCfg = Release|Any CPU {81D9EFA5-F846-40F3-B36C-8E82111EDDF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {81D9EFA5-F846-40F3-B36C-8E82111EDDF6}.Debug|Any CPU.Build.0 = Debug|Any CPU {81D9EFA5-F846-40F3-B36C-8E82111EDDF6}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -46,10 +48,6 @@ Global {91CA83AA-73DB-44B0-8A5D-5BB38532B897}.Debug|Any CPU.Build.0 = Debug|Any CPU {91CA83AA-73DB-44B0-8A5D-5BB38532B897}.Release|Any CPU.ActiveCfg = Release|Any CPU {91CA83AA-73DB-44B0-8A5D-5BB38532B897}.Release|Any CPU.Build.0 = Release|Any CPU - {0E04B4E3-8183-4516-A1E2-768C425C98FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0E04B4E3-8183-4516-A1E2-768C425C98FE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0E04B4E3-8183-4516-A1E2-768C425C98FE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0E04B4E3-8183-4516-A1E2-768C425C98FE}.Release|Any CPU.Build.0 = Release|Any CPU {EAE558C3-9759-4F7F-845B-D00056EE65DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EAE558C3-9759-4F7F-845B-D00056EE65DC}.Debug|Any CPU.Build.0 = Debug|Any CPU {EAE558C3-9759-4F7F-845B-D00056EE65DC}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -62,6 +60,10 @@ Global {2BD68525-60F2-4253-BFA7-12476F374EE9}.Debug|Any CPU.Build.0 = Debug|Any CPU {2BD68525-60F2-4253-BFA7-12476F374EE9}.Release|Any CPU.ActiveCfg = Release|Any CPU {2BD68525-60F2-4253-BFA7-12476F374EE9}.Release|Any CPU.Build.0 = Release|Any CPU + {B7AB5313-B55C-4AFF-940B-F17DC913AFB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B7AB5313-B55C-4AFF-940B-F17DC913AFB6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B7AB5313-B55C-4AFF-940B-F17DC913AFB6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B7AB5313-B55C-4AFF-940B-F17DC913AFB6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Alba/Alba.csproj b/src/Alba/Alba.csproj index 5d77901c..b4e826cc 100644 --- a/src/Alba/Alba.csproj +++ b/src/Alba/Alba.csproj @@ -3,8 +3,8 @@ Supercharged integration testing for ASP.NET Core HTTP endpoints Alba - 7.4.1 - net6.0;net7.0 + 8.0.0 + net6.0;net7.0;net8.0 Alba Alba http://jasperfx.github.io/alba/ @@ -21,7 +21,7 @@ - + @@ -35,4 +35,9 @@ + + + + + diff --git a/src/Alba/AlbaHost.cs b/src/Alba/AlbaHost.cs index 50f766da..15902a19 100644 --- a/src/Alba/AlbaHost.cs +++ b/src/Alba/AlbaHost.cs @@ -437,35 +437,6 @@ public async Task Invoke(Action setup) } } - /// - /// Create a SystemUnderTest using the designated "Startup" type - /// to configure the ASP.NET Core system - /// - /// - /// Optional configuration of the IHostBuilder to be applied *after* the call to - /// IWebHostBuilder.UseStartup() - /// - /// Specify the content root directory to be used by the host. - /// - /// The system under test - [Obsolete("We recommend using the WebApplicationFactory approach or bootstrapping from IHostBuilder. This will be removed in the future")] - public static AlbaHost ForStartup(Func? configure = null, - string? rootPath = null) where T : class - { - var builder = Host.CreateDefaultBuilder(); - builder.ConfigureWebHostDefaults(config => config.UseStartup()); - - if (configure != null) - { - builder = configure(builder); - } - - builder.UseContentRoot(rootPath ?? DirectoryFinder.FindParallelFolder(typeof(T).Assembly.GetName().Name) ?? - AppContext.BaseDirectory); - - return new AlbaHost(builder); - } - /// /// Creates a SystemUnderTest from a default HostBuilder using the provided IWebHostBuilder diff --git a/src/Alba/CommaTokenParser.cs b/src/Alba/CommaTokenParser.cs deleted file mode 100644 index 806ec60a..00000000 --- a/src/Alba/CommaTokenParser.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Alba -{ - [Obsolete] - internal class CommaTokenParser - { - private readonly List _tokens = new(); - private List _characters; - private IMode _mode; - - public CommaTokenParser() - { - _characters = new List(); - _mode = new Searching(this); - } - - public void Read(char c) - { - _mode.Read(c); - } - - private void addChar(char c) - { - _characters.Add(c); - } - - public IEnumerable Tokens => _tokens; - - private void startToken(IMode mode) - { - _mode = mode; - _characters = new List(); - } - - private void endToken() - { - var @string = new string(_characters.ToArray()); - _tokens.Add(@string); - - _mode = new Searching(this); - } - - - public interface IMode - { - void Read(char c); - } - - public class Searching : IMode - { - private readonly CommaTokenParser _parent; - - public Searching(CommaTokenParser parent) - { - _parent = parent; - } - - public void Read(char c) - { - if (c == ',') return; - - if (c == '"') - { - _parent.startToken(new InsideQuotedToken(_parent)); - } - else - { - var normalToken = new InsideNormalToken(_parent); - _parent.startToken(normalToken); - normalToken.Read(c); - } - } - } - - public class InsideQuotedToken : IMode - { - private readonly CommaTokenParser _parent; - - public InsideQuotedToken(CommaTokenParser parent) - { - _parent = parent; - } - - - public void Read(char c) - { - if (c == '"') - { - _parent.endToken(); - } - else - { - _parent.addChar(c); - } - } - } - - public class InsideNormalToken : IMode - { - private readonly CommaTokenParser _parent; - - public InsideNormalToken(CommaTokenParser parent) - { - _parent = parent; - } - - public void Read(char c) - { - if (c == ',') - { - _parent.endToken(); - } - else - { - _parent.addChar(c); - } - } - } - - - } -} \ No newline at end of file diff --git a/src/Alba/DirectoryFinder.cs b/src/Alba/DirectoryFinder.cs deleted file mode 100644 index 64e12fbe..00000000 --- a/src/Alba/DirectoryFinder.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace Alba -{ - // Delete this all in next semver - internal static class DirectoryFinder - { - /// - /// Tries to find the correct content path for a project that is parallel to the - /// testing project - /// - /// - /// - [Obsolete] - public static string? FindParallelFolder(string? folderName) - { - var starting = AppContext.BaseDirectory.ToFullPath(); - - // HACK ALERT!! but it does work - if (starting.Contains("dotnet-xunit")) - { - starting = Directory.GetCurrentDirectory(); - } - - -#pragma warning disable 8602 - while (starting.Contains(Path.DirectorySeparatorChar + "bin")) -#pragma warning restore 8602 - { - starting = starting.ParentDirectory(); - } - - -#pragma warning disable 8604 - var candidate = starting.ParentDirectory().AppendPath(folderName); -#pragma warning restore 8604 - - - return Directory.Exists(candidate) ? candidate : null; - } - - public static string ToFullPath(this string path) => Path.GetFullPath(path); - - public static string? ParentDirectory(this string path) => Path.GetDirectoryName(path.TrimEnd(Path.DirectorySeparatorChar)); - - public static string AppendPath(this string path, params string[] parts) - { - var stringList = new List { path }; - stringList.AddRange(parts); - return Combine(stringList.ToArray()); - } - public static string Combine(params string[] paths) => (paths).Aggregate(Path.Combine); } -} \ No newline at end of file diff --git a/src/Alba/FormDataExtensions.cs b/src/Alba/FormDataExtensions.cs index fc6d1fa1..e4823284 100644 --- a/src/Alba/FormDataExtensions.cs +++ b/src/Alba/FormDataExtensions.cs @@ -39,7 +39,7 @@ public static void WriteMultipartFormData(this HttpContext context, MultipartFor content.CopyTo(context.Request.Body, null, CancellationToken.None); foreach (var kv in content.Headers) { - context.Request.Headers.Add(kv.Key, kv.Value.ToArray()); + context.Request.Headers.Append(kv.Key, kv.Value.ToArray()); } } } diff --git a/src/Alba/HttpContextExtensions.cs b/src/Alba/HttpContextExtensions.cs index 26c2b381..25c4b1b3 100644 --- a/src/Alba/HttpContextExtensions.cs +++ b/src/Alba/HttpContextExtensions.cs @@ -15,7 +15,7 @@ public static void ContentType(this HttpRequest request, string mimeType) public static void ContentType(this HttpResponse response, string mimeType) { - response.Headers["content-type"] = mimeType; + response.Headers.ContentType = mimeType; } public static void RelativeUrl(this HttpContext context, [StringSyntax(StringSyntaxAttribute.Uri)]string? relativeUrl) @@ -43,12 +43,12 @@ public static void RelativeUrl(this HttpContext context, [StringSyntax(StringSyn /// public static void SetBearerToken(this HttpContext context, string jwt) { - context.Request.Headers["Authorization"] = $"Bearer {jwt}"; + context.Request.Headers.Authorization = $"Bearer {jwt}"; } public static void Accepts(this HttpContext context, string mimeType) { - context.Request.Headers["accept"] = mimeType; + context.Request.Headers.Accept = mimeType; } public static void HttpMethod(this HttpContext context, string method) diff --git a/src/Alba/IAlbaHost.cs b/src/Alba/IAlbaHost.cs index edc886a4..dfa3634b 100644 --- a/src/Alba/IAlbaHost.cs +++ b/src/Alba/IAlbaHost.cs @@ -15,10 +15,7 @@ public interface IAlbaHost : IHost, IAsyncDisposable /// /// /// - Task Scenario( - Action configure) - // ENDSAMPLE - ; + Task Scenario(Action configure); /// /// Execute some kind of action before each scenario. This is NOT additive diff --git a/src/Alba/Internal/AlbaTracing.cs b/src/Alba/Internal/AlbaTracing.cs index 48257015..db3bb287 100644 --- a/src/Alba/Internal/AlbaTracing.cs +++ b/src/Alba/Internal/AlbaTracing.cs @@ -9,7 +9,7 @@ namespace Alba.Internal; internal static class AlbaTracing { - public static readonly ActivitySource Source = new("Alba"); + private static readonly ActivitySource Source = new("Alba"); public const string HttpUrl = "http.url"; public const string HttpMethod = "http.method"; diff --git a/src/Alba/Internal/StreamExtensions.cs b/src/Alba/Internal/StreamExtensions.cs index 22b118ff..172f2ff7 100644 --- a/src/Alba/Internal/StreamExtensions.cs +++ b/src/Alba/Internal/StreamExtensions.cs @@ -19,10 +19,10 @@ public static byte[] ReadAllBytes(this Stream stream) return content.ToArray(); } - public static Task ReadAllTextAsync(this Stream stream) + public static async Task ReadAllTextAsync(this Stream stream) { using var sr = new StreamReader(stream, leaveOpen: true); - return sr.ReadToEndAsync(); + return await sr.ReadToEndAsync(); } public static async Task ReadAllBytesAsync(this Stream stream) diff --git a/src/Alba/Internal/StringSyntaxAttribute.cs b/src/Alba/Internal/StringSyntaxAttribute.cs index 6fc72587..ffb270d2 100644 --- a/src/Alba/Internal/StringSyntaxAttribute.cs +++ b/src/Alba/Internal/StringSyntaxAttribute.cs @@ -1,4 +1,4 @@ -#if !NET7_0 +#if !NET7_0_OR_GREATER // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. diff --git a/src/Alba/Scenario.cs b/src/Alba/Scenario.cs index 90f8bc99..201159b9 100644 --- a/src/Alba/Scenario.cs +++ b/src/Alba/Scenario.cs @@ -21,8 +21,8 @@ public class Scenario : IUrlExpression { private readonly ScenarioAssertionException _assertionRecords = new(); - private readonly IList _assertions = new List(); - private readonly IList> _setups = new List>(); + private readonly List _assertions = new(); + private readonly List> _setups = new(); private readonly AlbaHost _system; private int _expectedStatusCode = 200; private bool _ignoreStatusCode; @@ -130,7 +130,7 @@ SendExpression IUrlExpression.Url([StringSyntax(StringSyntaxAttribute.Uri)]strin return new SendExpression(this); } - SendExpression IUrlExpression.Json(T input, JsonStyle? jsonStyle = null) + SendExpression IUrlExpression.Json(T input, JsonStyle? jsonStyle) { WriteJson(input, jsonStyle); @@ -247,7 +247,7 @@ public void WriteJson(T input, JsonStyle? jsonStyle) ConfigureHttpContext(c => { - var stream = jsonStrategy.Write(input); + var stream = jsonStrategy!.Write(input); c.Request.ContentType = "application/json"; c.Request.Body = stream; @@ -346,17 +346,6 @@ internal void SetupHttpContext(HttpContext context) foreach (var setup in _setups) setup(context); } - /// - /// Set a value for a request header - /// - /// - /// - [Obsolete("Prefer the WithRequestHeader() method, and this will be removed in Alba v6")] - public void SetRequestHeader(string headerKey, string value) - { - WithRequestHeader(headerKey, value); - } - /// /// Set a value for a request header /// diff --git a/src/Alba/Security/AuthenticationExtensionBase.cs b/src/Alba/Security/AuthenticationExtensionBase.cs index a42d790d..7430dd16 100644 --- a/src/Alba/Security/AuthenticationExtensionBase.cs +++ b/src/Alba/Security/AuthenticationExtensionBase.cs @@ -11,7 +11,7 @@ namespace Alba.Security /// public abstract class AuthenticationExtensionBase : IHasClaims { - private readonly IList _baselineClaims = new List(); + private readonly List _baselineClaims = new(); void IHasClaims.AddClaim(Claim claim) diff --git a/src/Alba/Security/TestAuthHandler.cs b/src/Alba/Security/TestAuthHandler.cs index fd87add3..ca037cf2 100644 --- a/src/Alba/Security/TestAuthHandler.cs +++ b/src/Alba/Security/TestAuthHandler.cs @@ -13,6 +13,8 @@ internal class TestAuthHandler : AuthenticationHandler options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) @@ -21,6 +23,14 @@ public TestAuthHandler(IHttpContextAccessor accessor, AuthenticationStub parent, _parent = parent; } +#else + public TestAuthHandler(IHttpContextAccessor accessor, AuthenticationStub parent, IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder) : base(options, logger, encoder) + { + _context = accessor.HttpContext ?? throw new InvalidOperationException("HttpContext is missing"); + + _parent = parent; + } +#endif protected override Task HandleAuthenticateAsync() { diff --git a/src/Alba/SendExpression.cs b/src/Alba/SendExpression.cs index 1d35d9e2..8e3e2bc5 100644 --- a/src/Alba/SendExpression.cs +++ b/src/Alba/SendExpression.cs @@ -27,7 +27,7 @@ private Action modify /// public SendExpression ContentType(string contentType) { - modify = request => request.Headers["content-type"] = contentType; + modify = request => request.Headers.ContentType = contentType; return this; } @@ -38,7 +38,7 @@ public SendExpression ContentType(string contentType) /// public SendExpression Accepts(string accepts) { - modify = request => request.Headers["accept"] = accepts; + modify = request => request.Headers.Accept = accepts; return this; } @@ -49,7 +49,7 @@ public SendExpression Accepts(string accepts) /// public SendExpression Etag(string etag) { - modify = request => request.Headers["If-None-Match"] = etag; + modify = request => request.Headers.IfNoneMatch = etag; return this; } diff --git a/src/Alba/StringExtensions.cs b/src/Alba/StringExtensions.cs deleted file mode 100644 index 330f544b..00000000 --- a/src/Alba/StringExtensions.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Net; - -namespace Alba -{ - public static class StringExtensions - { - /// - /// Helper to expand any comma separated values out into an enumerable of - /// all the string values - /// - /// - /// - [Obsolete("Use HeaderDictionary.GetCommaSeparatedHeaderValues instead")] - public static IEnumerable GetCommaSeparatedHeaderValues(this IEnumerable enumerable) - { - foreach (var content in enumerable) - { - var searchString = content.Trim(); - if (searchString.Length == 0) break; - - var parser = new CommaTokenParser(); - var array = content.ToCharArray(); - - foreach (var c in array) - { - parser.Read(c); - } - - // Gotta force the parser to know it's done - parser.Read(','); - - foreach (var token in parser.Tokens) - { - yield return token.Trim(); - } - } - - - } - - [Obsolete("Use WebUtility.UrlEncode directly")] - public static string UrlEncoded(this string value) - { - return WebUtility.UrlEncode(value); - } - - [Obsolete("Copy this extension into your own codebase if you wish to continue using it.")] - public static string Quoted(this string value) - { - return $"\"{value}\""; - } - - [Obsolete("Copy this extension into your own codebase if you wish to continue using it.")] - public static DateTime? TryParseHttpDate(this string dateString) - { - return DateTime.TryParseExact(dateString, "r", CultureInfo.InvariantCulture, DateTimeStyles.None, out var date) - ? date - : null; - } - - } -} \ No newline at end of file diff --git a/src/IdentityServer.New/Config.cs b/src/IdentityServer.New/Config.cs new file mode 100644 index 00000000..95bb2e42 --- /dev/null +++ b/src/IdentityServer.New/Config.cs @@ -0,0 +1,41 @@ +using Duende.IdentityServer.Models; + +namespace IdentityServer.New +{ + public static class Config + { + + public static IEnumerable IdentityResources => + new IdentityResource[] + { + new IdentityResources.OpenId(), + new IdentityResources.Profile(), + }; + public const string ClientId = "spa"; + public const string ApiScope = "api"; + public const string ClientSecret = "secret"; + + public static IEnumerable ApiScopes => + new ApiScope[] + { + new ApiScope(ApiScope, new[] { "name" }), + }; + + + public static IEnumerable Clients => + new Client[] + { + // m2m client credentials flow client + new Client + { + ClientId = ClientId, + ClientName = "Client Credentials Client", + + AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials, + ClientSecrets = { new Secret(ClientSecret.Sha256()) }, + + AllowedScopes = { "openid", "profile", ApiScope } + }, + }; + } +} diff --git a/src/IdentityServer.New/HostingExtensions.cs b/src/IdentityServer.New/HostingExtensions.cs new file mode 100644 index 00000000..0932d2a5 --- /dev/null +++ b/src/IdentityServer.New/HostingExtensions.cs @@ -0,0 +1,55 @@ +using Duende.IdentityServer; +using IdentityServer.New; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Serilog; + +namespace IdentityServer.New +{ + internal static class HostingExtensions + { + public static WebApplication ConfigureServices(this WebApplicationBuilder builder) + { + builder.Services.AddRazorPages(); + + var isBuilder = builder.Services.AddIdentityServer(options => + { + options.Events.RaiseErrorEvents = true; + options.Events.RaiseInformationEvents = true; + options.Events.RaiseFailureEvents = true; + options.Events.RaiseSuccessEvents = true; + + // see https://docs.duendesoftware.com/identityserver/v6/fundamentals/resources/ + options.EmitStaticAudienceClaim = true; + }) + .AddTestUsers(TestUsers.Users); + + isBuilder.AddInMemoryIdentityResources(Config.IdentityResources); + isBuilder.AddInMemoryApiScopes(Config.ApiScopes); + isBuilder.AddInMemoryClients(Config.Clients); + + builder.Services.AddAuthentication(); + + return builder.Build(); + } + + public static WebApplication ConfigurePipeline(this WebApplication app) + { + app.UseSerilogRequestLogging(); + + if (app.Environment.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseStaticFiles(); + app.UseRouting(); + app.UseIdentityServer(); + app.UseAuthorization(); + + app.MapRazorPages() + .RequireAuthorization(); + + return app; + } + } +} \ No newline at end of file diff --git a/src/IdentityServer.New/IdentityServer.New.csproj b/src/IdentityServer.New/IdentityServer.New.csproj new file mode 100644 index 00000000..865af961 --- /dev/null +++ b/src/IdentityServer.New/IdentityServer.New.csproj @@ -0,0 +1,57 @@ + + + + net6.0 + enable + + + + + + + + + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap4-glyphicons\css\bootstrap-glyphicons.css" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap4-glyphicons\css\bootstrap-glyphicons.min.css" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap4-glyphicons\fonts\glyphicons\glyphicons-halflings-regular.eot" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap4-glyphicons\fonts\glyphicons\glyphicons-halflings-regular.svg" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap4-glyphicons\fonts\glyphicons\glyphicons-halflings-regular.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap4-glyphicons\fonts\glyphicons\glyphicons-halflings-regular.woff" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap4-glyphicons\fonts\glyphicons\glyphicons-halflings-regular.woff2" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap4-glyphicons\LICENSE" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap4-glyphicons\maps\glyphicons-fontawesome.css" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap4-glyphicons\maps\glyphicons-fontawesome.less" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap4-glyphicons\maps\glyphicons-fontawesome.min.css" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-grid.css" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-grid.css.map" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-grid.min.css" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-grid.min.css.map" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-reboot.css" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-reboot.css.map" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-reboot.min.css" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-reboot.min.css.map" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap.css" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap.css.map" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap.min.css" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap.min.css.map" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.bundle.js" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.bundle.js.map" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.bundle.min.js" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.bundle.min.js.map" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.js" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.js.map" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.min.js" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.min.js.map" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\LICENSE" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\README.md" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\jquery\dist\jquery.js" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\jquery\dist\jquery.min.js" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\jquery\dist\jquery.min.map" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\jquery\dist\jquery.slim.js" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\jquery\dist\jquery.slim.min.js" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\jquery\dist\jquery.slim.min.map" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\jquery\LICENSE.txt" /> + <_ContentIncludedByDefault Remove="wwwroot\lib\jquery\README.md" /> + + + \ No newline at end of file diff --git a/src/IdentityServer.New/Pages/Account/AccessDenied.cshtml b/src/IdentityServer.New/Pages/Account/AccessDenied.cshtml new file mode 100644 index 00000000..5ede1a3a --- /dev/null +++ b/src/IdentityServer.New/Pages/Account/AccessDenied.cshtml @@ -0,0 +1,10 @@ +@page +@model IdentityServer.New.Pages.Account.AccessDeniedModel +@{ +} +
+
+

Access Denied

+

You do not have permission to access that resource.

+
+
\ No newline at end of file diff --git a/src/IdentityServer.New/Pages/Account/AccessDenied.cshtml.cs b/src/IdentityServer.New/Pages/Account/AccessDenied.cshtml.cs new file mode 100644 index 00000000..acca4e14 --- /dev/null +++ b/src/IdentityServer.New/Pages/Account/AccessDenied.cshtml.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace IdentityServer.New.Pages.Account +{ + public class AccessDeniedModel : PageModel + { + public void OnGet() + { + } + } +} \ No newline at end of file diff --git a/src/IdentityServer.New/Pages/Account/Create/Index.cshtml b/src/IdentityServer.New/Pages/Account/Create/Index.cshtml new file mode 100644 index 00000000..51b0ffab --- /dev/null +++ b/src/IdentityServer.New/Pages/Account/Create/Index.cshtml @@ -0,0 +1,40 @@ +@page +@model IdentityServer.New.Pages.Create.Index + + \ No newline at end of file diff --git a/src/IdentityServer.New/Pages/Account/Create/Index.cshtml.cs b/src/IdentityServer.New/Pages/Account/Create/Index.cshtml.cs new file mode 100644 index 00000000..037e4fba --- /dev/null +++ b/src/IdentityServer.New/Pages/Account/Create/Index.cshtml.cs @@ -0,0 +1,121 @@ +using Duende.IdentityServer; +using Duende.IdentityServer.Events; +using Duende.IdentityServer.Models; +using Duende.IdentityServer.Services; +using Duende.IdentityServer.Stores; +using Duende.IdentityServer.Test; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace IdentityServer.New.Pages.Create +{ + [SecurityHeaders] + [AllowAnonymous] + public class Index : PageModel + { + private readonly TestUserStore _users; + private readonly IIdentityServerInteractionService _interaction; + + [BindProperty] + public InputModel Input { get; set; } + + public Index( + IIdentityServerInteractionService interaction, + TestUserStore users = null) + { + // this is where you would plug in your own custom identity management library (e.g. ASP.NET Identity) + _users = users ?? throw new Exception("Please call 'AddTestUsers(TestUsers.Users)' on the IIdentityServerBuilder in Startup or remove the TestUserStore from the AccountController."); + + _interaction = interaction; + } + + public IActionResult OnGet(string returnUrl) + { + Input = new InputModel { ReturnUrl = returnUrl }; + return Page(); + } + + public async Task OnPost() + { + // check if we are in the context of an authorization request + var context = await _interaction.GetAuthorizationContextAsync(Input.ReturnUrl); + + // the user clicked the "cancel" button + if (Input.Button != "create") + { + if (context != null) + { + // if the user cancels, send a result back into IdentityServer as if they + // denied the consent (even if this client does not require consent). + // this will send back an access denied OIDC error response to the client. + await _interaction.DenyAuthorizationAsync(context, AuthorizationError.AccessDenied); + + // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null + if (context.IsNativeClient()) + { + // The client is native, so this change in how to + // return the response is for better UX for the end user. + return this.LoadingPage(Input.ReturnUrl); + } + + return Redirect(Input.ReturnUrl); + } + else + { + // since we don't have a valid context, then we just go back to the home page + return Redirect("~/"); + } + } + + if (_users.FindByUsername(Input.Username) != null) + { + ModelState.AddModelError("Input.Username", "Invalid username"); + } + + if (ModelState.IsValid) + { + var user = _users.CreateUser(Input.Username, Input.Password, Input.Name, Input.Email); + + // issue authentication cookie with subject ID and username + var isuser = new IdentityServerUser(user.SubjectId) + { + DisplayName = user.Username + }; + + await HttpContext.SignInAsync(isuser); + + if (context != null) + { + if (context.IsNativeClient()) + { + // The client is native, so this change in how to + // return the response is for better UX for the end user. + return this.LoadingPage(Input.ReturnUrl); + } + + // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null + return Redirect(Input.ReturnUrl); + } + + // request for a local page + if (Url.IsLocalUrl(Input.ReturnUrl)) + { + return Redirect(Input.ReturnUrl); + } + else if (string.IsNullOrEmpty(Input.ReturnUrl)) + { + return Redirect("~/"); + } + else + { + // user might have clicked on a malicious link - should be logged + throw new Exception("invalid return URL"); + } + } + + return Page(); + } + } +} \ No newline at end of file diff --git a/src/IdentityServer.New/Pages/Account/Create/InputModel.cs b/src/IdentityServer.New/Pages/Account/Create/InputModel.cs new file mode 100644 index 00000000..a51a7c75 --- /dev/null +++ b/src/IdentityServer.New/Pages/Account/Create/InputModel.cs @@ -0,0 +1,24 @@ +// Copyright (c) Duende Software. All rights reserved. +// See LICENSE in the project root for license information. + + +using System.ComponentModel.DataAnnotations; + +namespace IdentityServer.New.Pages.Create +{ + public class InputModel + { + [Required] + public string Username { get; set; } + + [Required] + public string Password { get; set; } + + public string Name { get; set; } + public string Email { get; set; } + + public string ReturnUrl { get; set; } + + public string Button { get; set; } + } +} \ No newline at end of file diff --git a/src/IdentityServer/Views/Account/Login.cshtml b/src/IdentityServer.New/Pages/Account/Login/Index.cshtml similarity index 66% rename from src/IdentityServer/Views/Account/Login.cshtml rename to src/IdentityServer.New/Pages/Account/Login/Index.cshtml index 28dbeec8..f7a18055 100644 --- a/src/IdentityServer/Views/Account/Login.cshtml +++ b/src/IdentityServer.New/Pages/Account/Login/Index.cshtml @@ -1,4 +1,5 @@ -@model LoginViewModel +@page +@model IdentityServer.New.Pages.Login.Index