diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0492e10bb..d730a1e34 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,10 +26,19 @@ jobs: acm: image: ghcr.io/informatievlaanderen/identity-server-fake:033d00a ports: - - "5051:80" + - 5051:80 volumes: - ${{ github.workspace }}/identityserver:/home/identityserver + db: + image: postgres + env: + POSTGRES_USER: root + POSTGRES_PASSWORD: root + POSTGRES_DB: verenigingsregister + ports: + - 5432:5432 + outputs: version: ${{ steps.set-version.outputs.version }} diff --git a/.github/workflows/pre-merge-tests.yml b/.github/workflows/pre-merge-tests.yml index c48f72b02..120a76e3f 100644 --- a/.github/workflows/pre-merge-tests.yml +++ b/.github/workflows/pre-merge-tests.yml @@ -26,10 +26,19 @@ jobs: acm: image: ghcr.io/informatievlaanderen/identity-server-fake:033d00a ports: - - "5051:80" + - 5051:80 volumes: - ${{ github.workspace }}/identityserver:/home/identityserver + db: + image: postgres + env: + POSTGRES_USER: root + POSTGRES_PASSWORD: root + POSTGRES_DB: verenigingsregister + ports: + - 5432:5432 + steps: - name: Checkout Code uses: actions/checkout@v3 diff --git a/AssociationRegistry.sln b/AssociationRegistry.sln index 56241dca7..24d43bccc 100644 --- a/AssociationRegistry.sln +++ b/AssociationRegistry.sln @@ -43,10 +43,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssociationRegistry.Public.Api", "src\AssociationRegistry.Public.Api\AssociationRegistry.Public.Api.csproj", "{EC780706-D1C5-46DA-A9EA-1691E3C5052A}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{FA18A1E7-4ED1-4FA0-8A25-877A7207BEFD}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IdentityServer", "src\IdentityServer\IdentityServer.csproj", "{B83E79CF-359E-47DA-91C7-42C091A1B5E5}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "identityserver", "identityserver", "{D4AE63B8-9A67-43E5-865B-8E40BD1335CD}" ProjectSection(SolutionItems) = preProject identityserver\acm.json = identityserver\acm.json @@ -58,6 +54,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "minio", "minio", "{A5715AC1 minio\data.json = minio\data.json EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssociationRegistry.Admin.Api", "src\AssociationRegistry.Admin.Api\AssociationRegistry.Admin.Api.csproj", "{DAF6443B-10E9-4847-9E1F-DBE275E8C750}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -79,10 +77,10 @@ Global {EC780706-D1C5-46DA-A9EA-1691E3C5052A}.Debug|Any CPU.Build.0 = Debug|Any CPU {EC780706-D1C5-46DA-A9EA-1691E3C5052A}.Release|Any CPU.ActiveCfg = Release|Any CPU {EC780706-D1C5-46DA-A9EA-1691E3C5052A}.Release|Any CPU.Build.0 = Release|Any CPU - {B83E79CF-359E-47DA-91C7-42C091A1B5E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B83E79CF-359E-47DA-91C7-42C091A1B5E5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B83E79CF-359E-47DA-91C7-42C091A1B5E5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B83E79CF-359E-47DA-91C7-42C091A1B5E5}.Release|Any CPU.Build.0 = Release|Any CPU + {DAF6443B-10E9-4847-9E1F-DBE275E8C750}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DAF6443B-10E9-4847-9E1F-DBE275E8C750}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DAF6443B-10E9-4847-9E1F-DBE275E8C750}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DAF6443B-10E9-4847-9E1F-DBE275E8C750}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {3BD77833-6175-4440-903D-88F9B07C6FE6} = {1157AB42-EACF-454D-92D5-DBA9FF3D0E24} @@ -90,8 +88,8 @@ Global {75DFC6A3-90CB-4041-B122-8546DCD4D7F6} = {C9AFE263-FC5F-4243-BC1F-737B0CCB6DE6} {11DA6C2C-FBC0-4537-A4F8-2C7F546BB8CA} = {75DFC6A3-90CB-4041-B122-8546DCD4D7F6} {EC780706-D1C5-46DA-A9EA-1691E3C5052A} = {1157AB42-EACF-454D-92D5-DBA9FF3D0E24} - {B83E79CF-359E-47DA-91C7-42C091A1B5E5} = {FA18A1E7-4ED1-4FA0-8A25-877A7207BEFD} {D4AE63B8-9A67-43E5-865B-8E40BD1335CD} = {C9AFE263-FC5F-4243-BC1F-737B0CCB6DE6} {A5715AC1-4220-431A-B298-BCA9D857870B} = {C9AFE263-FC5F-4243-BC1F-737B0CCB6DE6} + {DAF6443B-10E9-4847-9E1F-DBE275E8C750} = {1157AB42-EACF-454D-92D5-DBA9FF3D0E24} EndGlobalSection EndGlobal diff --git a/AssociationRegistry.sln.DotSettings b/AssociationRegistry.sln.DotSettings index acd9d1fc5..f136a4f96 100644 --- a/AssociationRegistry.sln.DotSettings +++ b/AssociationRegistry.sln.DotSettings @@ -1,5 +1,6 @@  ACM + SQL <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_aaBb" /> True True diff --git a/docker-compose.yml b/docker-compose.yml index 4ff166501..90efc67aa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,5 +18,21 @@ services: - ./identityserver:/home/identityserver ports: - "5051:80" + + db: + container_name: pg_container + image: postgres + restart: always + environment: + POSTGRES_USER: root + POSTGRES_PASSWORD: root + POSTGRES_DB: verenigingsregister + ports: + - "5432:5432" + volumes: + - pg-data:/var/lib/postgresql/data + volumes: minio-data: + pg-data: + pg-admin-data: diff --git a/identityserver/acm.json b/identityserver/acm.json index 716ebed85..4b00f8dcb 100644 --- a/identityserver/acm.json +++ b/identityserver/acm.json @@ -9,7 +9,6 @@ } ], "apiScopes": [ - "vo_info", "dv_verenigingsregister_hoofdvertegenwoordigers" ], "apiResources": [ @@ -19,22 +18,26 @@ "a_very=Secr3t*Key" ], "scopes": [ - "vo_info", "dv_verenigingsregister_hoofdvertegenwoordigers" ] - }, + } + ], + "clients": [ { - "name": "acmClient", - "apiSecrets": [ - "secret" + "clientId": "association-registry-local-dev", + "clientSecrets": [ + "a_very=Secr3t*Key" ], - "scopes": [ + "allowedGrantTypes": "code", + "allowedScopes":[ + "standardscopes.openid", + "standardscopes.profile", "vo_info", "dv_verenigingsregister_hoofdvertegenwoordigers" - ] - } - ], - "clients": [ + ], + "alwaysSendClientClaims": true, + "alwaysIncludeUserClaimsInIdToken": true + }, { "clientId": "acmClient", "clientSecrets": [ @@ -42,7 +45,6 @@ ], "allowedGrantTypes": "clientCredentials", "allowedScopes":[ - "vo_info", "dv_verenigingsregister_hoofdvertegenwoordigers" ], "accessTokenLifetime": -1, diff --git a/identityserver/acm.json.doesntwork.bak b/identityserver/acm.json.doesntwork.bak deleted file mode 100644 index 219b3feb6..000000000 --- a/identityserver/acm.json.doesntwork.bak +++ /dev/null @@ -1,57 +0,0 @@ -{ - "identityResources": [ - { - "name": "dv", - "displayName": "Digitaal Vlaanderen", - "userClaims": [ - "dv_verenigingsregister_hoofdvertegenwoordigers" - ] - } - ], - "apiScopes": [ - "vo_info", - "dv_verenigingsregister_hoofdvertegenwoordigers" - ], - "apiResources": [ - { - "name": "association-registry-local-dev", - "apiSecrets": [ - "a_very=Secr3t*Key" - ], - "scopes": [ - "vo_info", - "dv_verenigingsregister_hoofdvertegenwoordigers" - ] - } - ], - "clients": [ - { - "clientId": "acmClient", - "clientSecrets": [ - "secret" - ], - "allowedGrantTypes": "clientCredentials", - "allowedScopes":[ - "vo_info", - "dv_verenigingsregister_hoofdvertegenwoordigers" - ], - "accessTokenLifetime": -1, - "identityTokenLifetime": -1, - "clientClaimsPrefix": "" - }, - { - "clientId": "association-registry-local-dev", - "clientSecrets": [ - "a_very=Secr3t*Key" - ], - "allowedGrantTypes": "code", - "allowedScopes":[ - "vo_info", - "dv_verenigingsregister_hoofdvertegenwoordigers" - ], - "accessTokenLifetime": -1, - "identityTokenLifetime": -1, - "clientClaimsPrefix": "" - } - ] -} diff --git a/paket.dependencies b/paket.dependencies index cdf7c649d..e5199f782 100644 --- a/paket.dependencies +++ b/paket.dependencies @@ -70,6 +70,13 @@ nuget Structurizr.Core 0.9.7 nuget Structurizr.Client 0.9.7 nuget Structurizr.AdrTools 0.9.3 +// MARTEN +nuget Marten 5.10.1 + +// MediatR +nuget MediatR 11.0.0 +nuget MediatR.Extensions.Microsoft.DependencyInjection 11.0.0 + // TEST STUFF nuget Microsoft.AspNetCore.Mvc.Testing 6.0.4 nuget Microsoft.AspNetCore.TestHost 6.0.4 diff --git a/paket.lock b/paket.lock index a18f00612..e122b2dc4 100644 --- a/paket.lock +++ b/paket.lock @@ -37,6 +37,8 @@ NUGET System.Security.Cryptography.ProtectedData (>= 4.7) System.Text.Json (>= 4.7.2) System.Threading.Tasks.Extensions (>= 4.5.4) + Baseline (3.2.2) + BaselineTypeDiscovery (1.1.3) Be.Vlaanderen.Basisregisters.AggregateSource (6.2.2) Be.Vlaanderen.Basisregisters.Utilities.HashCodeCalculator (>= 3.0) - restriction: || (&& (== net472) (>= net6.0)) (== net6.0) Be.Vlaanderen.Basisregisters.Utilities.HexByteConvertor (>= 3.0) - restriction: || (&& (== net472) (>= net6.0)) (== net6.0) @@ -416,6 +418,10 @@ NUGET Fake.Core.Trace (>= 5.23) FSharp.Core (>= 6.0) Fare (2.2.1) + FastExpressionCompiler.Internal.src (3.3.3) + System.Dynamic.Runtime (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Reflection.Emit.Lightweight (>= 4.7) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.ValueTuple (>= 4.5) - restriction: || (== net472) (&& (== net6.0) (>= net45)) FluentAssertions (6.5.1) System.Configuration.ConfigurationManager (>= 4.4) - restriction: || (&& (== net472) (>= netcoreapp2.1)) (&& (== net472) (>= netcoreapp3.0)) (== net6.0) System.Net.Http (>= 4.3.4) - restriction: || (== net472) (&& (== net6.0) (>= net47)) @@ -441,10 +447,48 @@ NUGET FSharp.Core (>= 4.7.2) System.Reactive (>= 5.0 < 6.0) FSharp.Core (6.0.5) - Humanizer.Core (2.14.1) - restriction: || (&& (== net472) (>= net6.0)) (== net6.0) + Humanizer.Core (2.14.1) IdentityModel (6.0) - restriction: || (&& (== net472) (>= netcoreapp3.1)) (== net6.0) IdentityModel.AspNetCore.OAuth2Introspection (6.0) IdentityModel (>= 5.2) - restriction: || (&& (== net472) (>= netcoreapp3.1)) (== net6.0) + LamarCodeGeneration (6.1.6) + Microsoft.Bcl.AsyncInterfaces (>= 5.0) - restriction: || (== net472) (&& (== net6.0) (< net5.0)) + System.Reflection.Emit.Lightweight (>= 4.7) + System.Threading.Tasks.Extensions (>= 4.5.4) + System.ValueTuple (>= 4.5) + LamarCompiler (6.1.6) + BaselineTypeDiscovery (>= 1.1.3) + LamarCodeGeneration (>= 6.1.6) + Microsoft.CodeAnalysis (>= 4.1) + Microsoft.CodeAnalysis.CSharp (>= 4.1) + Microsoft.CodeAnalysis.Scripting (>= 4.1) + Microsoft.Extensions.Logging.Abstractions (>= 3.0 < 8.0) + System.Reflection.Emit.Lightweight (>= 4.7) + System.Runtime.Loader (>= 4.3) + Marten (5.10.1) + BaselineTypeDiscovery (>= 1.1.3) + FastExpressionCompiler.Internal.src (>= 3.2.2) + LamarCodeGeneration (>= 6.1.4) + LamarCompiler (>= 6.1.4) + Microsoft.Extensions.DependencyInjection.Abstractions (>= 3.1.22) - restriction: || (== net472) (&& (== net6.0) (< net5.0)) + Microsoft.Extensions.DependencyInjection.Abstractions (>= 6.0) - restriction: || (&& (== net472) (>= net6.0)) (== net6.0) + Microsoft.Extensions.Hosting.Abstractions (>= 3.1.22) - restriction: || (== net472) (&& (== net6.0) (< net5.0)) + Microsoft.Extensions.Hosting.Abstractions (>= 6.0) - restriction: || (&& (== net472) (>= net6.0)) (== net6.0) + Microsoft.Extensions.Logging.Abstractions (>= 3.1.22) - restriction: || (== net472) (&& (== net6.0) (< net5.0)) + Microsoft.Extensions.Logging.Abstractions (>= 6.0) - restriction: || (&& (== net472) (>= net6.0)) (== net6.0) + Newtonsoft.Json (>= 13.0.1) + Npgsql.Json.NET (>= 6.0.1 < 7.0) + Remotion.Linq (>= 2.2) + System.Collections.Immutable (>= 6.0) - restriction: || (== net472) (&& (== net6.0) (< net5.0)) + System.Text.Json (>= 6.0) + System.Threading.Tasks.Dataflow (>= 6.0) + Weasel.Postgresql (>= 5.7.1) + MediatR (11.0) + MediatR.Contracts (>= 1.0.1) - restriction: || (&& (== net472) (>= netstandard2.1)) (== net6.0) + MediatR.Contracts (1.0.1) - restriction: || (&& (== net472) (>= netstandard2.1)) (== net6.0) + MediatR.Extensions.Microsoft.DependencyInjection (11.0) + MediatR (>= 11.0) - restriction: || (&& (== net472) (>= netstandard2.1)) (== net6.0) + Microsoft.Extensions.DependencyInjection.Abstractions (>= 6.0) - restriction: || (&& (== net472) (>= netstandard2.1)) (== net6.0) Microsoft.AspNetCore.Authentication.Abstractions (2.2) - restriction: || (== net472) (&& (== net6.0) (< netcoreapp3.0)) Microsoft.AspNetCore.Http.Abstractions (>= 2.2) Microsoft.Extensions.Logging.Abstractions (>= 2.2) @@ -559,6 +603,7 @@ NUGET System.Text.Encodings.Web (>= 4.5) Microsoft.Bcl.AsyncInterfaces (6.0) System.Threading.Tasks.Extensions (>= 4.5.4) - restriction: || (== net472) (&& (== net6.0) (>= net461)) (&& (== net6.0) (< netstandard2.1)) + Microsoft.Bcl.HashCode (1.1.1) - restriction: || (== net472) (&& (== net6.0) (< netstandard2.1)) Microsoft.Build (17.2) Microsoft.Build.Framework (>= 17.2) Microsoft.IO.Redist (>= 6.0) - restriction: || (== net472) (&& (== net6.0) (>= net472)) @@ -597,8 +642,48 @@ NUGET Microsoft.Win32.Registry (>= 4.3) - restriction: || (&& (== net472) (>= net6.0)) (== net6.0) System.Collections.Immutable (>= 5.0) System.Configuration.ConfigurationManager (>= 4.7) + Microsoft.CodeAnalysis (4.3.1) + Microsoft.CodeAnalysis.CSharp.Workspaces (4.3.1) + Microsoft.CodeAnalysis.VisualBasic.Workspaces (4.3.1) + Microsoft.CodeAnalysis.Analyzers (3.3.3) + Microsoft.CodeAnalysis.Common (4.3.1) + Microsoft.CodeAnalysis.Analyzers (>= 3.3.3) + System.Collections.Immutable (>= 6.0) + System.Memory (>= 4.5.4) + System.Reflection.Metadata (>= 5.0) + System.Runtime.CompilerServices.Unsafe (>= 6.0) + System.Text.Encoding.CodePages (>= 6.0) + System.Threading.Tasks.Extensions (>= 4.5.4) + Microsoft.CodeAnalysis.CSharp (4.3.1) + Microsoft.CodeAnalysis.Common (4.3.1) + Microsoft.CodeAnalysis.CSharp.Scripting (4.3.1) + Microsoft.CodeAnalysis.Common (4.3.1) + Microsoft.CodeAnalysis.CSharp (4.3.1) + Microsoft.CodeAnalysis.Scripting.Common (4.3.1) + Microsoft.CSharp (>= 4.7) + Microsoft.CodeAnalysis.CSharp.Workspaces (4.3.1) + Humanizer.Core (>= 2.14.1) + Microsoft.CodeAnalysis.Common (4.3.1) + Microsoft.CodeAnalysis.CSharp (4.3.1) + Microsoft.CodeAnalysis.Workspaces.Common (4.3.1) + Microsoft.CodeAnalysis.Scripting (4.3.1) + Microsoft.CodeAnalysis.CSharp.Scripting (4.3.1) + Microsoft.CodeAnalysis.Scripting.Common (4.3.1) + Microsoft.CodeAnalysis.Common (4.3.1) + Microsoft.CodeAnalysis.VisualBasic (4.3.1) + Microsoft.CodeAnalysis.Common (4.3.1) + Microsoft.CodeAnalysis.VisualBasic.Workspaces (4.3.1) + Microsoft.CodeAnalysis.Common (4.3.1) + Microsoft.CodeAnalysis.VisualBasic (4.3.1) + Microsoft.CodeAnalysis.Workspaces.Common (4.3.1) + Microsoft.CodeAnalysis.Workspaces.Common (4.3.1) + Humanizer.Core (>= 2.14.1) + Microsoft.Bcl.AsyncInterfaces (>= 6.0) + Microsoft.CodeAnalysis.Common (4.3.1) + System.Composition (>= 6.0) + System.IO.Pipelines (>= 6.0.3) Microsoft.CodeCoverage (17.3) - Microsoft.CSharp (4.7) - restriction: || (&& (== net472) (>= netstandard2.1)) (== net6.0) + Microsoft.CSharp (4.7) Microsoft.Data.SqlClient (5.0) Azure.Identity (>= 1.6) Microsoft.Data.SqlClient.SNI.runtime (>= 5.0) - restriction: || (&& (== net472) (< net462)) (&& (== net472) (>= netstandard2.1)) (== net6.0) @@ -686,8 +771,6 @@ NUGET System.Runtime.CompilerServices.Unsafe (>= 6.0) System.Threading.Tasks.Extensions (>= 4.5.4) - restriction: || (== net472) (&& (== net6.0) (>= net461)) (&& (== net6.0) (< netstandard2.1)) Microsoft.Extensions.DependencyInjection.Abstractions (6.0) - Microsoft.Bcl.AsyncInterfaces (>= 6.0) - restriction: || (== net472) (&& (== net6.0) (>= net461)) (&& (== net6.0) (< netstandard2.1)) - System.Threading.Tasks.Extensions (>= 4.5.4) - restriction: || (== net472) (&& (== net6.0) (>= net461)) (&& (== net6.0) (< netstandard2.1)) Microsoft.Extensions.DependencyModel (6.0) System.Buffers (>= 4.5.1) System.Memory (>= 4.5.4) @@ -756,6 +839,8 @@ NUGET Microsoft.Extensions.Options (>= 6.0) System.Diagnostics.DiagnosticSource (>= 6.0) Microsoft.Extensions.Logging.Abstractions (6.0.1) + System.Buffers (>= 4.5.1) - restriction: || (== net472) (&& (== net6.0) (>= net461)) + System.Memory (>= 4.5.4) - restriction: || (== net472) (&& (== net6.0) (>= net461)) Microsoft.Extensions.Logging.Configuration (6.0) - restriction: || (&& (== net472) (>= net6.0)) (== net6.0) Microsoft.Extensions.Configuration (>= 6.0) Microsoft.Extensions.Configuration.Abstractions (>= 6.0) @@ -898,6 +983,16 @@ NUGET NodaTime.Serialization.JsonNet (3.0) - restriction: || (&& (== net472) (>= net6.0)) (== net6.0) Newtonsoft.Json (>= 12.0.1) NodaTime (>= 3.0 < 4.0) + Npgsql (6.0.7) + Microsoft.Bcl.HashCode (>= 1.1.1) - restriction: || (== net472) (&& (== net6.0) (< netstandard2.1)) + System.Collections.Immutable (>= 6.0) - restriction: || (== net472) (&& (== net6.0) (< netcoreapp3.1)) (&& (== net6.0) (< netstandard2.1)) + System.Diagnostics.DiagnosticSource (>= 6.0) - restriction: || (== net472) (&& (== net6.0) (< net5.0)) (&& (== net6.0) (< netcoreapp3.1)) (&& (== net6.0) (< netstandard2.1)) + System.Runtime.CompilerServices.Unsafe (>= 6.0) + System.Text.Json (>= 6.0) - restriction: || (== net472) (&& (== net6.0) (< netcoreapp3.1)) (&& (== net6.0) (< netstandard2.1)) + System.Threading.Channels (>= 6.0) - restriction: || (== net472) (&& (== net6.0) (< netcoreapp3.1)) (&& (== net6.0) (< netstandard2.1)) + Npgsql.Json.NET (6.0.7) + Newtonsoft.Json (>= 13.0.1) + Npgsql (>= 6.0.7) NSwag.CodeGeneration (13.16.1) - restriction: || (&& (== net472) (>= net6.0)) (== net6.0) Newtonsoft.Json (>= 9.0.1) NJsonSchema (>= 10.7.2) @@ -929,6 +1024,18 @@ NUGET NuGet.Versioning (6.3) Parlot (0.0.23) - restriction: || (&& (== net472) (>= net6.0)) (== net6.0) Polly (7.2.3) - restriction: || (&& (== net472) (>= net6.0)) (== net6.0) + Remotion.Linq (2.2) + System.Collections (>= 4.0.11) - restriction: || (&& (== net472) (< net35)) (== net6.0) + System.Diagnostics.Debug (>= 4.0.11) - restriction: || (&& (== net472) (< net35)) (== net6.0) + System.Linq (>= 4.1) - restriction: || (&& (== net472) (< net35)) (== net6.0) + System.Linq.Expressions (>= 4.1) - restriction: || (&& (== net472) (< net35)) (== net6.0) + System.Linq.Queryable (>= 4.0.1) - restriction: || (&& (== net472) (< net35)) (== net6.0) + System.ObjectModel (>= 4.0.12) - restriction: || (&& (== net472) (< net35)) (== net6.0) + System.Reflection (>= 4.1) - restriction: || (&& (== net472) (< net35)) (== net6.0) + System.Reflection.Extensions (>= 4.0.1) - restriction: || (&& (== net472) (< net35)) (== net6.0) + System.Runtime (>= 4.1) - restriction: || (&& (== net472) (< net35)) (== net6.0) + System.Runtime.Extensions (>= 4.1) - restriction: || (&& (== net472) (< net35)) (== net6.0) + System.Threading (>= 4.0.11) - restriction: || (&& (== net472) (< net35)) (== net6.0) runtime.native.System.Data.SqlClient.sni (4.7) - restriction: || (&& (== net472) (>= netcoreapp3.1)) (&& (== net472) (>= netstandard2.1)) (== net6.0) runtime.win-arm64.runtime.native.System.Data.SqlClient.sni (>= 4.4) runtime.win-x64.runtime.native.System.Data.SqlClient.sni (>= 4.4) @@ -1005,8 +1112,25 @@ NUGET Microsoft.NETCore.Targets (>= 1.1) - restriction: || (&& (== net472) (< net45)) (== net6.0) System.Runtime (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) System.Collections.Immutable (6.0) + System.Memory (>= 4.5.4) - restriction: || (== net472) (&& (== net6.0) (>= net461)) System.Runtime.CompilerServices.Unsafe (>= 6.0) System.ComponentModel.Annotations (5.0) + System.Composition (6.0) + System.Composition.AttributedModel (>= 6.0) + System.Composition.Convention (>= 6.0) + System.Composition.Hosting (>= 6.0) + System.Composition.Runtime (>= 6.0) + System.Composition.TypedParts (>= 6.0) + System.Composition.AttributedModel (6.0) + System.Composition.Convention (6.0) + System.Composition.AttributedModel (>= 6.0) + System.Composition.Hosting (6.0) + System.Composition.Runtime (>= 6.0) + System.Composition.Runtime (6.0) + System.Composition.TypedParts (6.0) + System.Composition.AttributedModel (>= 6.0) + System.Composition.Hosting (>= 6.0) + System.Composition.Runtime (>= 6.0) System.Configuration.ConfigurationManager (6.0) System.Security.Cryptography.ProtectedData (>= 6.0) - restriction: || (&& (== net472) (< net461)) (&& (== net472) (>= net6.0)) (== net6.0) System.Security.Permissions (>= 6.0) @@ -1023,7 +1147,7 @@ NUGET Microsoft.Win32.Registry (>= 4.7) - restriction: || (&& (== net472) (< net451)) (&& (== net472) (>= netcoreapp2.0)) (&& (== net472) (>= netcoreapp2.1)) (&& (== net472) (>= netcoreapp3.1)) (== net6.0) runtime.native.System.Data.SqlClient.sni (>= 4.7) - restriction: || (&& (== net472) (< net451)) (&& (== net472) (>= netcoreapp2.0)) (&& (== net472) (>= netcoreapp2.1)) (&& (== net472) (>= netcoreapp3.1)) (== net6.0) System.Security.Principal.Windows (>= 4.7) - restriction: || (&& (== net472) (< net451)) (&& (== net472) (>= netcoreapp2.0)) (&& (== net472) (>= netcoreapp2.1)) (&& (== net472) (>= netcoreapp3.1)) (== net6.0) - System.Diagnostics.Debug (4.3) - restriction: || (&& (== net472) (>= net6.0)) (== net6.0) + System.Diagnostics.Debug (4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) Microsoft.NETCore.Platforms (>= 1.1) - restriction: || (&& (== net472) (< net45)) (== net6.0) Microsoft.NETCore.Targets (>= 1.1) - restriction: || (&& (== net472) (< net45)) (== net6.0) System.Runtime (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) @@ -1033,6 +1157,21 @@ NUGET System.Diagnostics.EventLog (6.0) - restriction: || (&& (== net472) (>= net6.0)) (== net6.0) System.Drawing.Common (6.0) - restriction: || (&& (== net472) (>= netcoreapp3.1)) (== net6.0) Microsoft.Win32.SystemEvents (>= 6.0) - restriction: || (&& (== net472) (>= netcoreapp3.1)) (== net6.0) + System.Dynamic.Runtime (4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Collections (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Diagnostics.Debug (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Linq (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Linq.Expressions (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.ObjectModel (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Reflection (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Reflection.Emit (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Reflection.Emit.ILGeneration (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Reflection.Primitives (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Reflection.TypeExtensions (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Resources.ResourceManager (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Runtime (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Runtime.Extensions (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Threading (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) System.Formats.Asn1 (6.0) - restriction: || (&& (== net472) (>= net5.0)) (&& (== net472) (>= net6.0)) (== net6.0) System.Globalization (4.3) - restriction: || (&& (== net472) (>= net6.0)) (== net6.0) Microsoft.NETCore.Platforms (>= 1.1) - restriction: || (&& (== net472) (< net45)) (== net6.0) @@ -1068,6 +1207,33 @@ NUGET System.Resources.ResourceManager (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) System.Runtime (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) System.Runtime.Extensions (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Linq.Expressions (4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Collections (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Diagnostics.Debug (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Globalization (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.IO (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Linq (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.ObjectModel (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Reflection (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Reflection.Emit (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Reflection.Emit.ILGeneration (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Reflection.Emit.Lightweight (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Reflection.Extensions (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Reflection.Primitives (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Reflection.TypeExtensions (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Resources.ResourceManager (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Runtime (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Runtime.Extensions (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Threading (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Linq.Queryable (4.3) - restriction: || (&& (== net472) (< net35)) (== net6.0) + System.Collections (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Diagnostics.Debug (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Linq (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Linq.Expressions (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Reflection (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Reflection.Extensions (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Resources.ResourceManager (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Runtime (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) System.Memory (4.5.5) System.Buffers (>= 4.5.1) - restriction: || (== net472) (&& (== net6.0) (>= monotouch)) (&& (== net6.0) (>= net461)) (&& (== net6.0) (< netcoreapp2.0)) (&& (== net6.0) (< netstandard1.1)) (&& (== net6.0) (< netstandard2.0)) (&& (== net6.0) (>= xamarinios)) (&& (== net6.0) (>= xamarinmac)) (&& (== net6.0) (>= xamarintvos)) (&& (== net6.0) (>= xamarinwatchos)) System.Numerics.Vectors (>= 4.5) - restriction: || (== net472) (&& (== net6.0) (>= net461)) @@ -1077,6 +1243,12 @@ NUGET System.Net.Http (4.3.4) System.Security.Cryptography.X509Certificates (>= 4.3) System.Numerics.Vectors (4.5) + System.ObjectModel (4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Collections (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Diagnostics.Debug (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Resources.ResourceManager (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Runtime (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Threading (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) System.Reactive (5.0) System.Reflection (4.3) - restriction: || (&& (== net472) (>= net6.0)) (== net6.0) Microsoft.NETCore.Platforms (>= 1.1) - restriction: || (&& (== net472) (< net45)) (== net6.0) @@ -1084,12 +1256,21 @@ NUGET System.IO (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) System.Reflection.Primitives (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) System.Runtime (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Reflection.Emit (4.7) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Reflection.Emit.ILGeneration (4.7) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Reflection.Emit.Lightweight (4.7) + System.Reflection.Extensions (4.3) - restriction: || (&& (== net472) (< net35)) (== net6.0) + Microsoft.NETCore.Platforms (>= 1.1) - restriction: || (&& (== net472) (< net45)) (== net6.0) + Microsoft.NETCore.Targets (>= 1.1) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Reflection (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Runtime (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) System.Reflection.Metadata (6.0.1) System.Collections.Immutable (>= 6.0) System.Reflection.Primitives (4.3) - restriction: || (&& (== net472) (>= net6.0)) (== net6.0) Microsoft.NETCore.Platforms (>= 1.1) - restriction: || (&& (== net472) (< net45)) (== net6.0) Microsoft.NETCore.Targets (>= 1.1) - restriction: || (&& (== net472) (< net45)) (== net6.0) System.Runtime (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Reflection.TypeExtensions (4.7) - restriction: || (&& (== net472) (< net45)) (== net6.0) System.Resources.Extensions (6.0) System.Memory (>= 4.5.4) - restriction: || (== net472) (&& (== net6.0) (>= net461)) System.Resources.ResourceManager (4.3) - restriction: || (&& (== net472) (>= net6.0)) (== net6.0) @@ -1119,6 +1300,10 @@ NUGET System.Reflection.Primitives (>= 4.3) - restriction: || (&& (== net472) (< net45)) (&& (== net472) (>= netcoreapp1.1)) (== net6.0) System.Runtime (>= 4.3) System.Runtime.Handles (>= 4.3) - restriction: || (&& (== net472) (< net45)) (&& (== net472) (>= netcoreapp1.1)) (== net6.0) + System.Runtime.Loader (4.3) + System.IO (>= 4.3) - restriction: || (&& (== net472) (< net462)) (== net6.0) + System.Reflection (>= 4.3) - restriction: || (&& (== net472) (< net462)) (== net6.0) + System.Runtime (>= 4.3) - restriction: || (&& (== net472) (< net462)) (== net6.0) System.Security.AccessControl (6.0) - restriction: || (&& (== net472) (>= netstandard2.1)) (== net6.0) System.Security.Cryptography.Algorithms (4.3.1) - restriction: || (== net472) (&& (== net6.0) (>= net461)) System.IO (>= 4.3) @@ -1146,7 +1331,7 @@ NUGET Microsoft.NETCore.Platforms (>= 1.1) - restriction: || (&& (== net472) (< net45)) (== net6.0) Microsoft.NETCore.Targets (>= 1.1) - restriction: || (&& (== net472) (< net45)) (== net6.0) System.Runtime (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) - System.Text.Encoding.CodePages (6.0) - restriction: || (&& (== net472) (>= net6.0)) (== net6.0) + System.Text.Encoding.CodePages (6.0) System.Runtime.CompilerServices.Unsafe (>= 6.0) System.Text.Encoding.Extensions (4.3) - restriction: || (&& (== net472) (>= net6.0)) (== net6.0) Microsoft.NETCore.Platforms (>= 1.1) - restriction: || (&& (== net472) (< net45)) (== net6.0) @@ -1166,6 +1351,11 @@ NUGET System.ValueTuple (>= 4.5) - restriction: || (== net472) (&& (== net6.0) (>= net461)) System.Text.RegularExpressions (4.3.1) - restriction: || (&& (== net472) (>= net6.0)) (== net6.0) System.Runtime (>= 4.3.1) - restriction: || (&& (== net472) (< net45)) (&& (== net472) (>= netcoreapp1.1)) (== net6.0) + System.Threading (4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Runtime (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Threading.Tasks (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) + System.Threading.Channels (6.0) - restriction: || (== net472) (&& (== net6.0) (< netcoreapp3.1)) (&& (== net6.0) (< netstandard2.1)) + System.Threading.Tasks.Extensions (>= 4.5.4) - restriction: || (== net472) (&& (== net6.0) (>= net461)) (&& (== net6.0) (< netstandard2.1)) System.Threading.Tasks (4.3) - restriction: || (&& (== net472) (>= net6.0)) (== net6.0) Microsoft.NETCore.Platforms (>= 1.1) - restriction: || (&& (== net472) (< net45)) (== net6.0) Microsoft.NETCore.Targets (>= 1.1) - restriction: || (&& (== net472) (< net45)) (== net6.0) @@ -1173,7 +1363,7 @@ NUGET System.Threading.Tasks.Dataflow (6.0) System.Threading.Tasks.Extensions (4.5.4) System.Runtime.CompilerServices.Unsafe (>= 4.5.3) - restriction: || (== net472) (&& (== net6.0) (>= net461)) (&& (== net6.0) (< netcoreapp2.1)) (&& (== net6.0) (< netstandard1.0)) (&& (== net6.0) (< netstandard2.0)) (&& (== net6.0) (>= wp8)) - System.ValueTuple (4.5) - restriction: || (== net472) (&& (== net6.0) (>= net461)) + System.ValueTuple (4.5) System.Windows.Extensions (6.0) - restriction: || (&& (== net472) (>= netcoreapp3.1)) (== net6.0) System.Drawing.Common (>= 6.0) - restriction: || (&& (== net472) (>= netcoreapp3.1)) (== net6.0) System.Xml.ReaderWriter (4.3.1) - restriction: || (&& (== net472) (>= net6.0)) (== net6.0) @@ -1193,6 +1383,12 @@ NUGET System.Threading.Tasks (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) System.Threading.Tasks.Extensions (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0) TimeZoneConverter (5.0) - restriction: || (&& (== net472) (>= net6.0)) (== net6.0) + Weasel.Core (5.7.1) + Baseline (>= 3.2.2) + System.Threading.Tasks.Extensions (>= 4.5.4) - restriction: || (== net472) (&& (== net6.0) (< net5.0)) + Weasel.Postgresql (5.7.1) + Npgsql (>= 6.0.1 < 7.0) + Weasel.Core (>= 5.7.1) xunit (2.4.1) xunit.analyzers (>= 0.10) xunit.assert (2.4.1) diff --git a/readme.md b/readme.md index e9d3a01cb..fbc8c2bb1 100644 --- a/readme.md +++ b/readme.md @@ -11,14 +11,16 @@ Please see our [contributing guidelines](CONTRIBUTING.md) before contributing. - docker compose ## Setup -### Minio -- start docker container(s): +### Start docker containers ```~~~~ docker compose up ``` +#### Minio - use a webbrowser to navigate to [http://localhost:9011](http://localhost:9011) - login into minio with the correct credentials - create a bucket with the name 'verenigingen' +- insert the file 'data.json' from the solutionfolder 'minio' + ## Conventions ### Json --- responses - strings are empty or have a value (never NULL) diff --git a/src/AssociationRegistry.Acm.Api/Infrastructure/Modules/ApiModule.cs b/src/AssociationRegistry.Acm.Api/Infrastructure/Modules/ApiModule.cs index 421f19f4f..71159f572 100755 --- a/src/AssociationRegistry.Acm.Api/Infrastructure/Modules/ApiModule.cs +++ b/src/AssociationRegistry.Acm.Api/Infrastructure/Modules/ApiModule.cs @@ -9,18 +9,19 @@ namespace AssociationRegistry.Acm.Api.Infrastructure.Modules; public class ApiModule : Module { - private readonly IConfiguration _configuration; + //private readonly IConfiguration _configuration; private readonly IServiceCollection _services; - private readonly ILoggerFactory _loggerFactory; + //private readonly ILoggerFactory _loggerFactory; public ApiModule( - IConfiguration configuration, - IServiceCollection services, - ILoggerFactory loggerFactory) + //IConfiguration configuration, + IServiceCollection services + //, ILoggerFactory loggerFactory + ) { - _configuration = configuration; + //_configuration = configuration; _services = services; - _loggerFactory = loggerFactory; + //_loggerFactory = loggerFactory; } protected override void Load(ContainerBuilder builder) diff --git a/src/AssociationRegistry.Acm.Api/Startup.cs b/src/AssociationRegistry.Acm.Api/Startup.cs index 9ca81c0d9..40e8b7646 100755 --- a/src/AssociationRegistry.Acm.Api/Startup.cs +++ b/src/AssociationRegistry.Acm.Api/Startup.cs @@ -136,7 +136,11 @@ public IServiceProvider ConfigureServices(IServiceCollection services) .Configure(_configuration); var containerBuilder = new ContainerBuilder(); - containerBuilder.RegisterModule(new ApiModule(_configuration, services, _loggerFactory)); + containerBuilder.RegisterModule(new ApiModule( + //_configuration, + services + //, _loggerFactory + )); _applicationContainer = containerBuilder.Build(); return new AutofacServiceProvider(_applicationContainer); diff --git a/src/AssociationRegistry.Acm.Api/paket.references b/src/AssociationRegistry.Acm.Api/paket.references index 7e1b8ab37..3cc3c5de1 100755 --- a/src/AssociationRegistry.Acm.Api/paket.references +++ b/src/AssociationRegistry.Acm.Api/paket.references @@ -3,15 +3,12 @@ Be.Vlaanderen.Basisregisters.Api Be.Vlaanderen.Basisregisters.EventHandling.Autofac Be.Vlaanderen.Basisregisters.DataDog.Tracing.Autofac Be.Vlaanderen.Basisregisters.BlobStore - AWSSDK.S3 AWSSDK.Core - AspNetCore.HealthChecks.SqlServer Microsoft.AspNetCore.Authentication.JwtBearer IdentityModel.AspNetCore.OAuth2Introspection - SourceLink.Embed.AllSourceFiles SourceLink.Copy.PdbFiles - - +MediatR +MediatR.Extensions.Microsoft.DependencyInjection \ No newline at end of file diff --git a/src/AssociationRegistry.Admin.Api/AssociationRegistry.Admin.Api.csproj b/src/AssociationRegistry.Admin.Api/AssociationRegistry.Admin.Api.csproj new file mode 100755 index 000000000..7e39d8616 --- /dev/null +++ b/src/AssociationRegistry.Admin.Api/AssociationRegistry.Admin.Api.csproj @@ -0,0 +1,36 @@ + + + + + true + false + + + + bin\Debug\net6.0\AssociationRegistry.Public.Api.xml + 1701;1702;1705;1591 + TRACE;DEBUG;NETCOREAPP;NET6_0 + + + bin\Release\net6.0\AssociationRegistry.Public.Api.xml + 1701;1702;1705;1591 + + + + + + + + + + + + + + + + + + + + diff --git a/src/AssociationRegistry.Admin.Api/Constants/WellknownFormats.cs b/src/AssociationRegistry.Admin.Api/Constants/WellknownFormats.cs new file mode 100644 index 000000000..ce08688f7 --- /dev/null +++ b/src/AssociationRegistry.Admin.Api/Constants/WellknownFormats.cs @@ -0,0 +1,6 @@ +namespace AssociationRegistry.Admin.Api.Constants; + +public static class WellknownFormats +{ + public const string DateOnly = "yyyy-MM-dd"; +} diff --git a/src/AssociationRegistry.Admin.Api/Dockerfile b/src/AssociationRegistry.Admin.Api/Dockerfile new file mode 100644 index 000000000..9da7e066c --- /dev/null +++ b/src/AssociationRegistry.Admin.Api/Dockerfile @@ -0,0 +1,16 @@ +FROM mcr.microsoft.com/dotnet/runtime-deps:6.0.3 + +LABEL maintainer="Digitaal Vlaanderen " +LABEL registry="association-registry" + +COPY / /app +WORKDIR /app + +RUN apt-get update && \ + apt-get install curl jq -y && \ + chmod +x ./init.sh + +EXPOSE 11004/tcp +ENV ASPNETCORE_URLS http://*:11004 + +ENTRYPOINT ["./init.sh"] diff --git a/src/AssociationRegistry.Admin.Api/Events/EventStore.cs b/src/AssociationRegistry.Admin.Api/Events/EventStore.cs new file mode 100644 index 000000000..05affea20 --- /dev/null +++ b/src/AssociationRegistry.Admin.Api/Events/EventStore.cs @@ -0,0 +1,24 @@ +namespace AssociationRegistry.Admin.Api.Events; + +using System.Threading.Tasks; +using Baseline; +using Marten; + +public class EventStore : IEventStore +{ + private readonly IDocumentStore _documentStore; + + public EventStore(IDocumentStore documentStore) + { + _documentStore = documentStore; + } + + public async Task Save(string aggregateId, params IEvent[] events) + { + await using var session = _documentStore.OpenSession(); + + session.Events.Append(aggregateId, events.As()); + + await session.SaveChangesAsync(); + } +} diff --git a/src/AssociationRegistry.Admin.Api/Events/IEvent.cs b/src/AssociationRegistry.Admin.Api/Events/IEvent.cs new file mode 100644 index 000000000..f1d6cbc98 --- /dev/null +++ b/src/AssociationRegistry.Admin.Api/Events/IEvent.cs @@ -0,0 +1,5 @@ +namespace AssociationRegistry.Admin.Api.Events; + +public interface IEvent +{ +} diff --git a/src/AssociationRegistry.Admin.Api/Events/IEventStore.cs b/src/AssociationRegistry.Admin.Api/Events/IEventStore.cs new file mode 100644 index 000000000..fb6348f4f --- /dev/null +++ b/src/AssociationRegistry.Admin.Api/Events/IEventStore.cs @@ -0,0 +1,8 @@ +namespace AssociationRegistry.Admin.Api.Events; + +using System.Threading.Tasks; + +public interface IEventStore +{ + Task Save(string aggregateId, params IEvent[] events); +} diff --git a/src/AssociationRegistry.Admin.Api/Infrastructure/Configuration/AddNoCacheHeadersMiddleware.cs b/src/AssociationRegistry.Admin.Api/Infrastructure/Configuration/AddNoCacheHeadersMiddleware.cs new file mode 100755 index 000000000..467b61bcd --- /dev/null +++ b/src/AssociationRegistry.Admin.Api/Infrastructure/Configuration/AddNoCacheHeadersMiddleware.cs @@ -0,0 +1,24 @@ +namespace AssociationRegistry.Admin.Api.Infrastructure.Configuration; + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Net.Http.Headers; + +/// +/// Add headers to the response to prevent any caching. +/// +public class AddNoCacheHeadersMiddleware +{ + private readonly RequestDelegate _next; + + public AddNoCacheHeadersMiddleware(RequestDelegate next) => _next = next; + + public Task Invoke(HttpContext context) + { + context.Response.Headers.Add(HeaderNames.CacheControl, "no-store, no-cache, must-revalidate"); + context.Response.Headers.Add(HeaderNames.Pragma, "no-cache"); + context.Response.Headers.Add(HeaderNames.Expires, "0"); + + return _next(context); + } +} diff --git a/src/AssociationRegistry.Admin.Api/Infrastructure/EmptyController.cs b/src/AssociationRegistry.Admin.Api/Infrastructure/EmptyController.cs new file mode 100755 index 000000000..21f6f7239 --- /dev/null +++ b/src/AssociationRegistry.Admin.Api/Infrastructure/EmptyController.cs @@ -0,0 +1,18 @@ +namespace AssociationRegistry.Admin.Api.Infrastructure; + +using System.Reflection; +using Be.Vlaanderen.Basisregisters.Api; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Net.Http.Headers; + +[ApiVersionNeutral] +[Route("")] +public class EmptyController : ApiController +{ + [HttpGet] + [ApiExplorerSettings(IgnoreApi = true)] + public IActionResult Get() + => Request.Headers[HeaderNames.Accept].ToString().Contains("text/html") + ? new RedirectResult("/docs") + : new OkObjectResult($"Welcome to the Basisregisters Vlaanderen Public Api {Assembly.GetEntryAssembly()!.GetVersionText()}."); +} diff --git a/src/AssociationRegistry.Admin.Api/Infrastructure/Json/DateOnlyJsonConvertor.cs b/src/AssociationRegistry.Admin.Api/Infrastructure/Json/DateOnlyJsonConvertor.cs new file mode 100644 index 000000000..4e46c24d8 --- /dev/null +++ b/src/AssociationRegistry.Admin.Api/Infrastructure/Json/DateOnlyJsonConvertor.cs @@ -0,0 +1,24 @@ +namespace AssociationRegistry.Admin.Api.Infrastructure.Json; + +using System; +using System.Globalization; +using Newtonsoft.Json; +using JsonSerializer = Newtonsoft.Json.JsonSerializer; + +public class DateOnlyJsonConvertor : JsonConverter +{ + private readonly string _format; + + public DateOnlyJsonConvertor(string format) + { + _format = format; + } + + public override void WriteJson(JsonWriter writer, DateOnly value, JsonSerializer serializer) + { + writer.WriteValue(value.ToString(_format, CultureInfo.InvariantCulture)); + } + + public override DateOnly ReadJson(JsonReader reader, Type objectType, DateOnly existingValue, bool hasExistingValue, JsonSerializer serializer) + => DateOnly.ParseExact((string)reader.Value!, _format, CultureInfo.InvariantCulture); +} diff --git a/src/AssociationRegistry.Admin.Api/Infrastructure/Json/NullableDateOnlyJsonConvertor.cs b/src/AssociationRegistry.Admin.Api/Infrastructure/Json/NullableDateOnlyJsonConvertor.cs new file mode 100644 index 000000000..0a56f87b6 --- /dev/null +++ b/src/AssociationRegistry.Admin.Api/Infrastructure/Json/NullableDateOnlyJsonConvertor.cs @@ -0,0 +1,26 @@ +namespace AssociationRegistry.Admin.Api.Infrastructure.Json; + +using System; +using System.Globalization; +using Newtonsoft.Json; +using JsonSerializer = Newtonsoft.Json.JsonSerializer; + +public class NullableDateOnlyJsonConvertor : JsonConverter +{ + private readonly string _format; + + public NullableDateOnlyJsonConvertor(string format) + { + _format = format; + } + + public override void WriteJson(JsonWriter writer, DateOnly? value, JsonSerializer serializer) + => writer.WriteValue(value.HasValue ? value.Value.ToString(_format, CultureInfo.InvariantCulture) : string.Empty); + + public override DateOnly? ReadJson(JsonReader reader, Type objectType, DateOnly? existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var readValue = (string)reader.Value!; + if (string.IsNullOrEmpty(readValue)) return null; + return DateOnly.ParseExact(readValue, _format, CultureInfo.InvariantCulture); + } +} diff --git a/src/AssociationRegistry.Admin.Api/Infrastructure/Modules/ApiModule.cs b/src/AssociationRegistry.Admin.Api/Infrastructure/Modules/ApiModule.cs new file mode 100755 index 000000000..66995159e --- /dev/null +++ b/src/AssociationRegistry.Admin.Api/Infrastructure/Modules/ApiModule.cs @@ -0,0 +1,28 @@ +namespace AssociationRegistry.Admin.Api.Infrastructure.Modules; + +using Autofac; +using Autofac.Extensions.DependencyInjection; +using Be.Vlaanderen.Basisregisters.Api.Exceptions; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +public class ApiModule : Module +{ + private readonly IServiceCollection _services; + + public ApiModule(IServiceCollection services) + { + _services = services; + } + + protected override void Load(ContainerBuilder builder) + { + builder + .RegisterType() + .AsSelf(); + + builder + .Populate(_services); + } +} diff --git a/src/AssociationRegistry.Admin.Api/Program.cs b/src/AssociationRegistry.Admin.Api/Program.cs new file mode 100755 index 000000000..fee37962e --- /dev/null +++ b/src/AssociationRegistry.Admin.Api/Program.cs @@ -0,0 +1,32 @@ +namespace AssociationRegistry.Admin.Api; + +using Be.Vlaanderen.Basisregisters.Api; +using Microsoft.AspNetCore.Hosting; + +public class Program +{ + protected Program() + { } + + public static void Main(string[] args) => CreateWebHostBuilder(args).Build().Run(); + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) + => new WebHostBuilder() + .UseDefaultForApi( + new ProgramOptions + { + Hosting = + { + HttpPort = 11004, + }, + Logging = + { + WriteTextToConsole = false, + WriteJsonToConsole = false, + }, + Runtime = + { + CommandLineArgs = args, + }, + }); +} diff --git a/src/AssociationRegistry.Admin.Api/Properties/AssemblyInfo.cs b/src/AssociationRegistry.Admin.Api/Properties/AssemblyInfo.cs new file mode 100755 index 000000000..9eb82fc85 --- /dev/null +++ b/src/AssociationRegistry.Admin.Api/Properties/AssemblyInfo.cs @@ -0,0 +1,7 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyDescription("AssociationRegistry Legacy API")] + +[assembly: ComVisible(false)] +[assembly: Guid("2cea5ed4-f453-4417-af2c-1b0c11dbfbe1")] diff --git a/src/AssociationRegistry.Admin.Api/Properties/launchSettings.json b/src/AssociationRegistry.Admin.Api/Properties/launchSettings.json new file mode 100644 index 000000000..fcedb7cdf --- /dev/null +++ b/src/AssociationRegistry.Admin.Api/Properties/launchSettings.json @@ -0,0 +1,26 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:13215/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "AssociationRegistry.Api.Legacy": { + "commandName": "Project", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:5000/" + } + } +} diff --git a/src/AssociationRegistry.Admin.Api/Startup.cs b/src/AssociationRegistry.Admin.Api/Startup.cs new file mode 100755 index 000000000..f6c86dbf7 --- /dev/null +++ b/src/AssociationRegistry.Admin.Api/Startup.cs @@ -0,0 +1,241 @@ +namespace AssociationRegistry.Admin.Api; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Autofac; +using Autofac.Extensions.DependencyInjection; +using Be.Vlaanderen.Basisregisters.Api; +using Constants; +using Events; +using Infrastructure.Configuration; +using Infrastructure.Json; +using Infrastructure.Modules; +using Marten; +using Marten.Events; +using MediatR; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.OpenApi.Models; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using Swashbuckle.AspNetCore.SwaggerGen; +using Verenigingen; + +/// Represents the startup process for the application. +public class Startup +{ + private const string DatabaseTag = "db"; + + private IContainer _applicationContainer = null!; + + private readonly IConfiguration _configuration; + private readonly ILoggerFactory _loggerFactory; + + public Startup( + IConfiguration configuration, + ILoggerFactory loggerFactory) + { + _configuration = configuration; + _loggerFactory = loggerFactory; + } + + /// Configures services for the application. + /// The collection of services to configure the application with. + public IServiceProvider ConfigureServices(IServiceCollection services) + { + var baseUrl = _configuration.GetValue("BaseUrl"); + var baseUrlForExceptions = baseUrl.EndsWith("/") + ? baseUrl.Substring(0, baseUrl.Length - 1) + : baseUrl; + + services.AddSwaggerGen( + options => + { + options.MapType( + () => new OpenApiSchema + { + Type = "string", + Format = "date", + Pattern = "yyyy-MM-dd", + }); + }); + + services.AddMarten( + opts => + { + opts.Connection(_configuration.GetValue("eventstore_connectionstring")); + opts.Events.StreamIdentity = StreamIdentity.AsString; + }); + + services.AddMediatR(typeof(Startup)); + services.AddTransient(); + services.AddTransient(); + services.AddSingleton(); + services + .ConfigureDefaultForApi( + new StartupConfigureOptions + { + Cors = + { + Origins = _configuration + .GetSection("Cors") + .GetChildren() + .Select(c => c.Value) + .ToArray(), + }, + Server = + { + BaseUrl = baseUrlForExceptions, + }, + Swagger = + { + MiddlewareHooks = + { + AfterSwaggerGen = options => { options.CustomSchemaIds(type => type.ToString()); }, + }, + ApiInfo = (_, description) => new OpenApiInfo + { + Version = description.ApiVersion.ToString(), + Title = "Basisregisters Vlaanderen Verenigingenregister Beheer API", + Description = GetApiLeadingText(description), + Contact = new OpenApiContact + { + Name = "Digitaal Vlaanderen", + Email = "digitaal.vlaanderen@vlaanderen.be", + Url = new Uri("https://beheer.api.verenigingen.vlaanderen.be"), + }, + }, + XmlCommentPaths = new[] { typeof(Startup).GetTypeInfo().Assembly.GetName().Name! }, + }, + MiddlewareHooks = + { + ConfigureJsonOptions = options => + { + options.SerializerSettings.Converters.Add(new NullableDateOnlyJsonConvertor(WellknownFormats.DateOnly)); + options.SerializerSettings.Converters.Add(new DateOnlyJsonConvertor(WellknownFormats.DateOnly)); + options.SerializerSettings.NullValueHandling = NullValueHandling.Include; + options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); + }, + AfterHealthChecks = health => + { + var connectionStrings = _configuration + .GetSection("ConnectionStrings") + .GetChildren(); + + foreach (var connectionString in connectionStrings) + health.AddSqlServer( + connectionString.Value, + name: $"sqlserver-{connectionString.Key.ToLowerInvariant()}", + tags: new[] { DatabaseTag, "sql", "sqlserver" }); + + // health.AddDbContextCheck( + // $"dbcontext-{nameof(LegacyContext).ToLowerInvariant()}", + // tags: new[] { DatabaseTag, "sql", "sqlserver" }); + }, + }, + }); + //.Configure(_configuration); + + var containerBuilder = new ContainerBuilder(); + containerBuilder.RegisterModule(new ApiModule(services)); + _applicationContainer = containerBuilder.Build(); + + return new AutofacServiceProvider(_applicationContainer); + } + + + public void Configure( + IServiceProvider serviceProvider, + IApplicationBuilder app, + IWebHostEnvironment env, + IHostApplicationLifetime appLifetime, + ILoggerFactory loggerFactory, + IApiVersionDescriptionProvider apiVersionProvider + // ApiDataDogToggle datadogToggle, + // ApiDebugDataDogToggle debugDataDogToggle, + // HealthCheckService healthCheckService + ) + { + // StartupHelpers.CheckDatabases(healthCheckService, DatabaseTag, loggerFactory).GetAwaiter().GetResult(); + + app + // .UseDataDog(new DataDogOptions + // { + // Common = + // { + // ServiceProvider = serviceProvider, + // LoggerFactory = loggerFactory + // }, + // Toggles = + // { + // Enable = datadogToggle, + // Debug = debugDataDogToggle + // }, + // Tracing = + // { + // ServiceName = _configuration["DataDog:ServiceName"], + // } + // }) + .UseDefaultForApi( + new StartupUseOptions + { + Common = + { + ApplicationContainer = _applicationContainer, + ServiceProvider = serviceProvider, + HostingEnvironment = env, + ApplicationLifetime = appLifetime, + LoggerFactory = loggerFactory, + }, + Api = + { + VersionProvider = apiVersionProvider, + Info = groupName => $"Basisregisters Vlaanderen - Verenigingenregister Public API {groupName}", + CSharpClientOptions = + { + ClassName = "Verenigingenregister", + Namespace = "Be.Vlaanderen.Basisregisters", + }, + TypeScriptClientOptions = + { + ClassName = "Verenigingenregister", + }, + }, + MiddlewareHooks = + { + AfterMiddleware = x => x.UseMiddleware(), + }, + }); + } + + private static string GetApiLeadingText(ApiVersionDescription description) + => $"Momenteel leest u de documentatie voor versie {description.ApiVersion} van de Basisregisters Vlaanderen Verenigingenregister Public API{string.Format(description.IsDeprecated ? ", **deze API versie is niet meer ondersteund * *." : ".")}"; +} + +public class ProblemJsonResponseFilter : IOperationFilter +{ + public void Apply(OpenApiOperation operation, OperationFilterContext context) + { + foreach (var (_, value) in operation.Responses.Where( + entry => + (entry.Key.StartsWith("4") && entry.Key != "400") || + entry.Key.StartsWith("5"))) + { + if (!value.Content.Any()) + return; + + var openApiMediaType = value.Content.First().Value; + + value.Content.Clear(); + value.Content.Add( + new KeyValuePair("application/problem+json", openApiMediaType)); + } + } +} diff --git a/src/AssociationRegistry.Admin.Api/Verenigingen/CommandEnvelope.cs b/src/AssociationRegistry.Admin.Api/Verenigingen/CommandEnvelope.cs new file mode 100644 index 000000000..1e10ffa94 --- /dev/null +++ b/src/AssociationRegistry.Admin.Api/Verenigingen/CommandEnvelope.cs @@ -0,0 +1,5 @@ +namespace AssociationRegistry.Admin.Api.Verenigingen; + +using MediatR; + +public record CommandEnvelope(TCommand Command) : IRequest where TCommand : IRequest; diff --git a/src/AssociationRegistry.Admin.Api/Verenigingen/CreateVerenigingCommand.cs b/src/AssociationRegistry.Admin.Api/Verenigingen/CreateVerenigingCommand.cs new file mode 100644 index 000000000..c2fd42439 --- /dev/null +++ b/src/AssociationRegistry.Admin.Api/Verenigingen/CreateVerenigingCommand.cs @@ -0,0 +1,7 @@ +namespace AssociationRegistry.Admin.Api.Verenigingen; + +using System.Runtime.Serialization; +using MediatR; + +[DataContract] +public record CreateVerenigingCommand([property: DataMember] string Naam) : IRequest; diff --git a/src/AssociationRegistry.Admin.Api/Verenigingen/CreateVerenigingCommandHandler.cs b/src/AssociationRegistry.Admin.Api/Verenigingen/CreateVerenigingCommandHandler.cs new file mode 100644 index 000000000..c7d5c34c1 --- /dev/null +++ b/src/AssociationRegistry.Admin.Api/Verenigingen/CreateVerenigingCommandHandler.cs @@ -0,0 +1,25 @@ +namespace AssociationRegistry.Admin.Api.Verenigingen; + +using System.Threading; +using System.Threading.Tasks; +using MediatR; + +public class CreateVerenigingCommandHandler : IRequestHandler> +{ + private readonly IVerenigingsRepository _verenigingsRepository; + private readonly IVCodeService _vCodeService; + + public CreateVerenigingCommandHandler(IVerenigingsRepository verenigingsRepository, IVCodeService vCodeService) + { + _verenigingsRepository = verenigingsRepository; + _vCodeService = vCodeService; + } + + public async Task Handle(CommandEnvelope request, CancellationToken cancellationToken) + { + var vCode = await _vCodeService.GetNext(); + var vereniging = new Vereniging(vCode, request.Command.Naam); + await _verenigingsRepository.Save(vereniging); + return Unit.Value; + } +} diff --git a/src/AssociationRegistry.Admin.Api/Verenigingen/CreateVerenigingValidator.cs b/src/AssociationRegistry.Admin.Api/Verenigingen/CreateVerenigingValidator.cs new file mode 100644 index 000000000..d9782d0d5 --- /dev/null +++ b/src/AssociationRegistry.Admin.Api/Verenigingen/CreateVerenigingValidator.cs @@ -0,0 +1,11 @@ +namespace AssociationRegistry.Admin.Api.Verenigingen; + +using FluentValidation; + +public class CreateVerenigingValidator : AbstractValidator> +{ + public CreateVerenigingValidator() + { + RuleFor(c => c.Command.Naam).NotNull().NotEmpty().WithMessage("Naam is verplicht"); + } +} diff --git a/src/AssociationRegistry.Admin.Api/Verenigingen/IVCodeService.cs b/src/AssociationRegistry.Admin.Api/Verenigingen/IVCodeService.cs new file mode 100644 index 000000000..1600cd53a --- /dev/null +++ b/src/AssociationRegistry.Admin.Api/Verenigingen/IVCodeService.cs @@ -0,0 +1,8 @@ +namespace AssociationRegistry.Admin.Api.Verenigingen; + +using System.Threading.Tasks; + +public interface IVCodeService +{ + Task GetNext(); +} diff --git a/src/AssociationRegistry.Admin.Api/Verenigingen/IVerenigingsRepository.cs b/src/AssociationRegistry.Admin.Api/Verenigingen/IVerenigingsRepository.cs new file mode 100644 index 000000000..218498cc0 --- /dev/null +++ b/src/AssociationRegistry.Admin.Api/Verenigingen/IVerenigingsRepository.cs @@ -0,0 +1,8 @@ +namespace AssociationRegistry.Admin.Api.Verenigingen; + +using System.Threading.Tasks; + +public interface IVerenigingsRepository +{ + Task Save(Vereniging vereniging); +} diff --git a/src/AssociationRegistry.Admin.Api/Verenigingen/SequentialVCodeService.cs b/src/AssociationRegistry.Admin.Api/Verenigingen/SequentialVCodeService.cs new file mode 100644 index 000000000..64f95d969 --- /dev/null +++ b/src/AssociationRegistry.Admin.Api/Verenigingen/SequentialVCodeService.cs @@ -0,0 +1,17 @@ +namespace AssociationRegistry.Admin.Api.Verenigingen; + +using System.Threading.Tasks; + +public class SequentialVCodeService : IVCodeService +{ + private static int vCode = 0; + + public Task GetNext() + => Task.FromResult($"V{++vCode:000000}"); + + /// + /// only for testing + /// + public static string GetLast() + => $"V{vCode:000000}"; +} diff --git a/src/AssociationRegistry.Admin.Api/Verenigingen/Vereniging.cs b/src/AssociationRegistry.Admin.Api/Verenigingen/Vereniging.cs new file mode 100644 index 000000000..5bbb3b92a --- /dev/null +++ b/src/AssociationRegistry.Admin.Api/Verenigingen/Vereniging.cs @@ -0,0 +1,38 @@ +namespace AssociationRegistry.Admin.Api.Verenigingen; + +using System.Collections.Generic; +using System.Linq; +using Events; + +public class Vereniging +{ + private class State + { + public string VCode { get; } + public string Naam { get; } + + private State(string vCode, string naam) + { + VCode = vCode; + Naam = naam; + } + + public static State Apply(VerenigingWerdGeregistreerd @event) + => new(@event.VCode, @event.Naam); + } + + private readonly State _state; + + public string VCode + => _state.VCode; + + + public Vereniging(string vCode, string naam) + { + _state = State.Apply(new VerenigingWerdGeregistreerd(vCode, naam)); + + Events = Events.Append(new VerenigingWerdGeregistreerd(vCode, naam)); + } + + public IEnumerable Events { get; } = new List(); +} diff --git a/src/AssociationRegistry.Admin.Api/Verenigingen/VerenigingWerdGeregistreerd.cs b/src/AssociationRegistry.Admin.Api/Verenigingen/VerenigingWerdGeregistreerd.cs new file mode 100644 index 000000000..e14685acd --- /dev/null +++ b/src/AssociationRegistry.Admin.Api/Verenigingen/VerenigingWerdGeregistreerd.cs @@ -0,0 +1,10 @@ +namespace AssociationRegistry.Admin.Api.Verenigingen; + +using System.Runtime.Serialization; +using Events; + +[DataContract] +public record VerenigingWerdGeregistreerd( + [property: DataMember] string VCode, + [property: DataMember] string Naam +) : IEvent; diff --git a/src/AssociationRegistry.Admin.Api/Verenigingen/VerenigingenController.cs b/src/AssociationRegistry.Admin.Api/Verenigingen/VerenigingenController.cs new file mode 100644 index 000000000..0838685dc --- /dev/null +++ b/src/AssociationRegistry.Admin.Api/Verenigingen/VerenigingenController.cs @@ -0,0 +1,28 @@ +namespace AssociationRegistry.Admin.Api.Verenigingen; + +using System.Threading.Tasks; +using Be.Vlaanderen.Basisregisters.Api; +using MediatR; +using Microsoft.AspNetCore.Mvc; + +[ApiVersion("1.0")] +[AdvertiseApiVersions("1.0")] +[ApiRoute("verenigingen")] +[ApiExplorerSettings(GroupName = "Verenigingen")] +public class VerenigingenController : ApiController +{ + private readonly ISender _sender; + + public VerenigingenController(ISender sender) + { + _sender = sender; + } + + [HttpPost] + public async Task Post([FromBody] CreateVerenigingCommand command) + { + var envelope = new CommandEnvelope(command); + await _sender.Send(envelope); + return Accepted(); + } +} diff --git a/src/AssociationRegistry.Admin.Api/Verenigingen/VerenigingsRepository.cs b/src/AssociationRegistry.Admin.Api/Verenigingen/VerenigingsRepository.cs new file mode 100644 index 000000000..af6971efc --- /dev/null +++ b/src/AssociationRegistry.Admin.Api/Verenigingen/VerenigingsRepository.cs @@ -0,0 +1,20 @@ +namespace AssociationRegistry.Admin.Api.Verenigingen; + +using System.Linq; +using System.Threading.Tasks; +using Events; + +public class VerenigingsRepository : IVerenigingsRepository +{ + private readonly IEventStore _eventStore; + + public VerenigingsRepository(IEventStore eventStore) + { + _eventStore = eventStore; + } + + public async Task Save(Vereniging vereniging) + { + await _eventStore.Save(vereniging.VCode, vereniging.Events.ToArray()); + } +} diff --git a/src/AssociationRegistry.Admin.Api/appsettings.json b/src/AssociationRegistry.Admin.Api/appsettings.json new file mode 100755 index 000000000..ca6c75555 --- /dev/null +++ b/src/AssociationRegistry.Admin.Api/appsettings.json @@ -0,0 +1,25 @@ +{ + "Cors": [ + ], + + "BaseUrl": "https://public.api.verenigingen.vlaanderen.be/", + + "Serilog": { + "MinimumLevel": { + "Default": "Information" + }, + "WriteTo": [ + { + "Name": "Console", + "Args": { + "formatter": "Serilog.Formatting.Compact.RenderedCompactJsonFormatter, Serilog.Formatting.Compact" + } + } + ], + "Properties": { + "Application": "AssociationRegistry - Legacy API", + "ContainerId": "REPLACE_CONTAINERID" + } + }, + "eventstore_connectionstring": "host=localhost;database=verenigingsregister;password=root;username=root" +} diff --git a/src/AssociationRegistry.Admin.Api/init.sh b/src/AssociationRegistry.Admin.Api/init.sh new file mode 100755 index 000000000..2fd638856 --- /dev/null +++ b/src/AssociationRegistry.Admin.Api/init.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +CONTAINERID=$(curl -s http://169.254.170.2/v2/metadata | jq -r ".Containers[] | select(.Labels[\"com.amazonaws.ecs.container-name\"] | startswith(\"basisregisters-\") and endswith(\"-legacy\")) | .DockerId") + +sed -i "s/REPLACE_CONTAINERID/$CONTAINERID/g" appsettings.json + +./AssociationRegistry.Admin.Api diff --git a/src/AssociationRegistry.Admin.Api/paket.references b/src/AssociationRegistry.Admin.Api/paket.references new file mode 100755 index 000000000..2a64a46a1 --- /dev/null +++ b/src/AssociationRegistry.Admin.Api/paket.references @@ -0,0 +1,15 @@ +Be.Vlaanderen.Basisregisters.AggregateSource +Be.Vlaanderen.Basisregisters.Api +Be.Vlaanderen.Basisregisters.EventHandling.Autofac +Be.Vlaanderen.Basisregisters.DataDog.Tracing.Autofac +Be.Vlaanderen.Basisregisters.BlobStore + +AspNetCore.HealthChecks.SqlServer + +SourceLink.Embed.AllSourceFiles +SourceLink.Copy.PdbFiles + +Marten + +MediatR +MediatR.Extensions.Microsoft.DependencyInjection diff --git a/src/AssociationRegistry.Admin.Api/paket.template b/src/AssociationRegistry.Admin.Api/paket.template new file mode 100755 index 000000000..0e6310535 --- /dev/null +++ b/src/AssociationRegistry.Admin.Api/paket.template @@ -0,0 +1,21 @@ +type file +version 1.0.0 + +id Be.Vlaanderen.Basisregisters.AssociationRegistry.Admin.Api +title Be.Vlaanderen.Basisregisters.AssociationRegistry.Admin.Api + +authors Basisregisters Vlaanderen +owners Digitaal Vlaanderen +copyright Copyright (c) Digitaal Vlaanderen +requireLicenseAcceptance false +projectUrl https://github.com/Informatievlaanderen/association-registry +iconUrl https://raw.githubusercontent.com/Informatievlaanderen/build-pipeline/master/logo.png +licenseUrl https://opensource.org/licenses/EUPL-1.2 + +description Association Registry. + +files + AssociationRegistry.Admin.Api.dll => lib\net6.0 + AssociationRegistry.Admin.Api.pdb => lib\net6.0 + AssociationRegistry.Admin.Api.xml => lib\net6.0 + AssociationRegistry.Admin.Api.xml => content diff --git a/src/AssociationRegistry.Public.Api/ListVerenigingen/ListVerenigingenController.cs b/src/AssociationRegistry.Public.Api/ListVerenigingen/ListVerenigingenController.cs index 474f90d72..77bd8aa47 100644 --- a/src/AssociationRegistry.Public.Api/ListVerenigingen/ListVerenigingenController.cs +++ b/src/AssociationRegistry.Public.Api/ListVerenigingen/ListVerenigingenController.cs @@ -29,7 +29,7 @@ public ListVerenigingenController(IVerenigingenRepository verenigingenRepository /// /// Vraag de lijst van verenigingen op. /// - /// + /// /// /// Een lijst met de bevraagde verenigingen /// Als er een interne fout is opgetreden. diff --git a/src/IdentityServer/Config/Extensions.cs b/src/IdentityServer/Config/Extensions.cs deleted file mode 100644 index dc1521107..000000000 --- a/src/IdentityServer/Config/Extensions.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace IdentityServer.Config; - -using Duende.IdentityServer.Models; - -public static class Extensions -{ - public static void SetAccessTokenLifetimeOrDefault(this Client client, int? accessTokenLifetime) - { - if (accessTokenLifetime.HasValue) - client.AccessTokenLifetime = accessTokenLifetime == -1 - ? int.MaxValue - : accessTokenLifetime.Value; - } - - public static void SetIdentityTokenLifetimeOrDefault(this Client client, int? identityTokenLifetime) - { - if (identityTokenLifetime.HasValue) - client.IdentityTokenLifetime = identityTokenLifetime == -1 - ? int.MaxValue - : identityTokenLifetime.Value; - } - - public static void SetClientClaimsPrefixOrDefault(this Client client, string? maybeClientClaimsPrefix) - { - if (maybeClientClaimsPrefix is { } clientClaimsPrefix) - client.ClientClaimsPrefix = clientClaimsPrefix; - } - - public static void SetAlwaysSendClientClaimsOrDefault(this Client client, bool? alwaysSendClientClaims) - { - if (alwaysSendClientClaims.HasValue) - client.AlwaysSendClientClaims = alwaysSendClientClaims.Value; - } - - public static void SetAlwaysIncludeUserClaimsInIdTokenOrDefault(this Client client, bool? alwaysIncludeUserClaimsInIdToken) - { - if (alwaysIncludeUserClaimsInIdToken.HasValue) - client.AlwaysIncludeUserClaimsInIdToken = alwaysIncludeUserClaimsInIdToken.Value; - } - - public static List MergeLists(this List list1, List list2, Func areSame) - { - var result = list1; - - foreach (var item in list2.Where(item => !result.Any(x => areSame(x, item)))) - { - result.Add(item); - } - - return result; - } -} diff --git a/src/IdentityServer/Config/JsonApiResource.cs b/src/IdentityServer/Config/JsonApiResource.cs deleted file mode 100644 index 8e18f6e64..000000000 --- a/src/IdentityServer/Config/JsonApiResource.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace IdentityServer.Config; - -using Duende.IdentityServer.Models; - -public class JsonApiResource -{ - public string Name { get; set; } = null!; - public List ApiSecrets { get; set; } = new(); - public List Scopes { get; set; } = new(); - - public static ApiResource Export(JsonApiResource resource) - => new(resource.Name) - { - ApiSecrets = resource.ApiSecrets - .Select( - apiSecret => - new Secret(apiSecret.Sha256())) - .ToList(), - Scopes = resource.Scopes, - }; -} diff --git a/src/IdentityServer/Config/JsonClient.cs b/src/IdentityServer/Config/JsonClient.cs deleted file mode 100644 index 1f37b70ea..000000000 --- a/src/IdentityServer/Config/JsonClient.cs +++ /dev/null @@ -1,62 +0,0 @@ -namespace IdentityServer.Config; - -using Duende.IdentityServer; -using Duende.IdentityServer.Models; - -public class JsonClient -{ - public string ClientId { get; set; } = null!; - public List ClientSecrets { get; set; } = new(); - public string AllowedGrantTypes { get; set; } = null!; - public List AllowedScopes { get; set; } = new(); - public bool? AlwaysSendClientClaims { get; set; } - public bool? AlwaysIncludeUserClaimsInIdToken { get; set; } - public int? AccessTokenLifetime { get; set; } - public int? IdentityTokenLifetime { get; set; } - public string? ClientClaimsPrefix { get; set; } - - public static Client Export(JsonClient jsonClient) - { - var client = new Client - { - ClientId = jsonClient.ClientId, - ClientSecrets = jsonClient.ClientSecrets - .Select( - secret => - new Secret(secret.Sha256())) - .ToList(), - AllowedGrantTypes = GetAllowedGrantTypes(jsonClient.AllowedGrantTypes), - AllowedScopes = GetAllowedScopes(jsonClient.AllowedScopes), - }; - - client.SetAccessTokenLifetimeOrDefault(jsonClient.AccessTokenLifetime); - client.SetIdentityTokenLifetimeOrDefault(jsonClient.IdentityTokenLifetime); - client.SetClientClaimsPrefixOrDefault(jsonClient.ClientClaimsPrefix); - client.SetAlwaysSendClientClaimsOrDefault(jsonClient.AlwaysSendClientClaims); - client.SetAlwaysIncludeUserClaimsInIdTokenOrDefault(jsonClient.AlwaysIncludeUserClaimsInIdToken); - - return client; - } - - private static ICollection GetAllowedScopes(List allowedScopes) - { - Dictionary predefinedScopes = - new() - { - { "standardscopes.openid", IdentityServerConstants.StandardScopes.OpenId }, - { "standardscopes.profile", IdentityServerConstants.StandardScopes.Profile }, - }; - - return allowedScopes - .Select(scope => predefinedScopes.ContainsKey(scope.ToLower()) ? predefinedScopes[scope.ToLower()] : scope) - .ToList(); - } - - private static ICollection GetAllowedGrantTypes(string allowedGrantTypes) - => allowedGrantTypes.ToLowerInvariant() switch - { - "clientcredentials" => GrantTypes.ClientCredentials, - "code" => GrantTypes.Code, - _ => throw new NotSupportedException(), - }; -} diff --git a/src/IdentityServer/Config/JsonConfig.cs b/src/IdentityServer/Config/JsonConfig.cs deleted file mode 100644 index 20c244270..000000000 --- a/src/IdentityServer/Config/JsonConfig.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace IdentityServer.Config; - -using Duende.IdentityServer.Models; - -public class JsonConfig -{ - public List IdentityResources { get; set; } = new(); - public List ApiScopes { get; set; } = new(); - public List ApiResources { get; set; } = new(); - public List Clients { get; set; } = new(); - - public IEnumerable GetIdentityResources() - => IdentityResources.Select(JsonIdentityResource.Export); - - public IEnumerable GetApiScopes() - => ApiScopes.Select(s => new ApiScope(s)); - - public IEnumerable GetApiResources() - => ApiResources.Select(JsonApiResource.Export); - - public IEnumerable GetClients() - => Clients.Select(JsonClient.Export); - - public static JsonConfig Merge(JsonConfig jc1, JsonConfig jc2) - => new() - { - IdentityResources = jc1.IdentityResources.MergeLists(jc2.IdentityResources, areSame: (r1, r2) => r1.Name == r2.Name), - ApiScopes = jc1.ApiScopes.MergeLists(jc2.ApiScopes, areSame: (s1, s2) => s1 == s2), - ApiResources = MergeApiResources(jc1.ApiResources, jc2.ApiResources).ToList(), - Clients = jc1.Clients.MergeLists(jc2.Clients, areSame: (c1, c2) => c1.ClientId == c2.ClientId).ToList(), - }; - - private static List MergeApiResources(List list1, List list2) - { - var result = list1; - - foreach (var item in list2) - { - if (result.Any(x => x.Name == item.Name)) - { - var element = result.Single(x => x.Name == item.Name); - element.Scopes = element.Scopes.MergeLists(item.Scopes, (s1, s2) => s1 == s2); - element.ApiSecrets = element.ApiSecrets.MergeLists(item.ApiSecrets, (s1, s2) => s1 == s2); - } - else - { - result.Add(item); - } - } - - return result; - } -} diff --git a/src/IdentityServer/Config/JsonIdentityResource.cs b/src/IdentityServer/Config/JsonIdentityResource.cs deleted file mode 100644 index 0af81273c..000000000 --- a/src/IdentityServer/Config/JsonIdentityResource.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace IdentityServer.Config; - -using Duende.IdentityServer.Models; - -public class JsonIdentityResource -{ - private static readonly Dictionary Predefined = - new() - { - { "identityresources.openid", new IdentityResources.OpenId() }, - { "identityresources.profile", new IdentityResources.Profile() }, - }; - - public string Name { get; set; } = null!; - public string? DisplayName { get; set; } - public List? UserClaims { get; set; } - - public static IdentityResource Export(JsonIdentityResource resource) - => Predefined.ContainsKey(resource.Name.ToLower()) - ? Predefined[resource.Name.ToLower()] - : new IdentityResource(resource.Name, resource.DisplayName, resource.UserClaims); -} diff --git a/src/IdentityServer/Dockerfile b/src/IdentityServer/Dockerfile deleted file mode 100644 index 37dc9a7cc..000000000 --- a/src/IdentityServer/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -# syntax=docker/dockerfile:1 -FROM mcr.microsoft.com/dotnet/sdk:6.0.201 AS build-env -WORKDIR /app - -# Copy csproj and restore as distinct layers -COPY *.csproj ./ -RUN dotnet restore - -# Copy everything else and build -COPY ./ ./ -RUN dotnet publish -c Release -o out - -# Build runtime image -FROM mcr.microsoft.com/dotnet/aspnet:6.0 -WORKDIR /app -COPY --from=build-env /app/out . -ENTRYPOINT ["dotnet", "IdentityServer.dll"] diff --git a/src/IdentityServer/HostingExtensions.cs b/src/IdentityServer/HostingExtensions.cs deleted file mode 100644 index 4f9b5de40..000000000 --- a/src/IdentityServer/HostingExtensions.cs +++ /dev/null @@ -1,94 +0,0 @@ -namespace IdentityServer; - -using Config; -using Newtonsoft.Json; -using Serilog; - -internal static class HostingExtensions -{ - public static WebApplicationBuilder ConfigureIdentityServer(this WebApplicationBuilder builder) - { - var finalJsonConfig = new JsonConfig(); - - var di = new DirectoryInfo("/home/identityserver"); - //var di = new DirectoryInfo("/home/wodan/gitrepos/digitaalvlaanderen/association-registry/identityserver"); - foreach (var fi in di.GetFiles("*.json")) - { - using var fs = fi.OpenRead(); - using var sr = new StreamReader(fs); - - Console.WriteLine($"Processing {fi.FullName}"); - try - { - var json = sr.ReadToEnd(); - var jsonConfig = JsonConvert.DeserializeObject(json); - - - foreach (var client in jsonConfig.Clients) - { - Console.WriteLine($"client {client.ClientId} parsed successfully"); - } - - finalJsonConfig = JsonConfig.Merge(finalJsonConfig, jsonConfig); - } - catch (Exception e) - { - Console.WriteLine(e); - } - } - - builder.Services.AddIdentityServer( - options => - { - // https://docs.duendesoftware.com/identityserver/v6/fundamentals/resources/api_scopes#authorization-based-on-scopes - options.EmitStaticAudienceClaim = true; - }) - .AddInMemoryIdentityResources(finalJsonConfig.GetIdentityResources()) - .AddInMemoryApiScopes(finalJsonConfig.GetApiScopes()) - .AddInMemoryApiResources(finalJsonConfig.GetApiResources()) - .AddInMemoryClients(finalJsonConfig.GetClients()); - - return builder; - } - - public static WebApplication ConfigureServices(this WebApplicationBuilder builder) - { - // uncomment if you want to add a UI - builder.Services.AddRazorPages(); - - builder.Services.AddCors(options => options.AddDefaultPolicy(policyBuilder => policyBuilder.AllowAnyOrigin())); - builder.Services.ConfigureNonBreakingSameSiteCookies(); - - builder.ConfigureIdentityServer(); - - return builder.Build(); - } - - public static WebApplication ConfigurePipeline(this WebApplication app) - { - app.UseSerilogRequestLogging(); - - if (app.Environment.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - // uncomment if you want to add a UI - app.UseStaticFiles(); - app.UseRouting(); - - app.UseIdentityServer(); - - app.UseCookiePolicy(); - - // This will write cookies, so make sure it's after the cookie policy - app.UseAuthentication(); - - - // uncomment if you want to add a UI - app.UseAuthorization(); - app.MapRazorPages().RequireAuthorization(); - - return app; - } -} diff --git a/src/IdentityServer/IdentityServer.csproj b/src/IdentityServer/IdentityServer.csproj deleted file mode 100644 index d9bf71455..000000000 --- a/src/IdentityServer/IdentityServer.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - net6.0 - enable - enable - - - - - - - - diff --git a/src/IdentityServer/IdentityServer.sln b/src/IdentityServer/IdentityServer.sln deleted file mode 100644 index b11957f36..000000000 --- a/src/IdentityServer/IdentityServer.sln +++ /dev/null @@ -1,39 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30114.105 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IdentityServer", "IdentityServer.csproj", "{67E6A399-F128-44D8-A564-334B17E4EAEF}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{A18AC2FB-08F5-4B6B-82F3-862FD68FEEEF}" - ProjectSection(SolutionItems) = preProject - global.json = global.json - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {67E6A399-F128-44D8-A564-334B17E4EAEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {67E6A399-F128-44D8-A564-334B17E4EAEF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {67E6A399-F128-44D8-A564-334B17E4EAEF}.Debug|x64.ActiveCfg = Debug|Any CPU - {67E6A399-F128-44D8-A564-334B17E4EAEF}.Debug|x64.Build.0 = Debug|Any CPU - {67E6A399-F128-44D8-A564-334B17E4EAEF}.Debug|x86.ActiveCfg = Debug|Any CPU - {67E6A399-F128-44D8-A564-334B17E4EAEF}.Debug|x86.Build.0 = Debug|Any CPU - {67E6A399-F128-44D8-A564-334B17E4EAEF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {67E6A399-F128-44D8-A564-334B17E4EAEF}.Release|Any CPU.Build.0 = Release|Any CPU - {67E6A399-F128-44D8-A564-334B17E4EAEF}.Release|x64.ActiveCfg = Release|Any CPU - {67E6A399-F128-44D8-A564-334B17E4EAEF}.Release|x64.Build.0 = Release|Any CPU - {67E6A399-F128-44D8-A564-334B17E4EAEF}.Release|x86.ActiveCfg = Release|Any CPU - {67E6A399-F128-44D8-A564-334B17E4EAEF}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal diff --git a/src/IdentityServer/Pages/Account/AccessDenied.cshtml b/src/IdentityServer/Pages/Account/AccessDenied.cshtml deleted file mode 100644 index f56cfdb67..000000000 --- a/src/IdentityServer/Pages/Account/AccessDenied.cshtml +++ /dev/null @@ -1,10 +0,0 @@ -@page -@model IdentityServerHost.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/Pages/Account/AccessDenied.cshtml.cs b/src/IdentityServer/Pages/Account/AccessDenied.cshtml.cs deleted file mode 100644 index 6ef5f1754..000000000 --- a/src/IdentityServer/Pages/Account/AccessDenied.cshtml.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace IdentityServerHost.Pages.Account; - -public class AccessDeniedModel : PageModel -{ - public void OnGet() - { - } -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Account/Login/Index.cshtml b/src/IdentityServer/Pages/Account/Login/Index.cshtml deleted file mode 100644 index 1ec5ae3f1..000000000 --- a/src/IdentityServer/Pages/Account/Login/Index.cshtml +++ /dev/null @@ -1,89 +0,0 @@ -@page -@model IdentityServerHost.Pages.Login.Index - - \ No newline at end of file diff --git a/src/IdentityServer/Pages/Account/Login/Index.cshtml.cs b/src/IdentityServer/Pages/Account/Login/Index.cshtml.cs deleted file mode 100644 index d084b9c85..000000000 --- a/src/IdentityServer/Pages/Account/Login/Index.cshtml.cs +++ /dev/null @@ -1,227 +0,0 @@ -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 IdentityServerHost.Pages.Login; - -[SecurityHeaders] -[AllowAnonymous] -public class Index : PageModel -{ - private readonly TestUserStore _users; - private readonly IIdentityServerInteractionService _interaction; - private readonly IClientStore _clientStore; - private readonly IEventService _events; - private readonly IAuthenticationSchemeProvider _schemeProvider; - private readonly IIdentityProviderStore _identityProviderStore; - - public ViewModel View { get; set; } - - [BindProperty] - public InputModel Input { get; set; } - - public Index( - IIdentityServerInteractionService interaction, - IClientStore clientStore, - IAuthenticationSchemeProvider schemeProvider, - IIdentityProviderStore identityProviderStore, - IEventService events, - 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; - _clientStore = clientStore; - _schemeProvider = schemeProvider; - _identityProviderStore = identityProviderStore; - _events = events; - } - - public async Task OnGet(string returnUrl) - { - await BuildModelAsync(returnUrl); - - if (View.IsExternalLoginOnly) - { - // we only have one option for logging in and it's an external provider - return RedirectToPage("/ExternalLogin/Challenge/Index", new { scheme = View.ExternalLoginScheme, 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 != "login") - { - 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 (ModelState.IsValid) - { - // validate username/password against in-memory store - if (_users.ValidateCredentials(Input.Username, Input.Password)) - { - var user = _users.FindByUsername(Input.Username); - await _events.RaiseAsync(new UserLoginSuccessEvent(user.Username, user.SubjectId, user.Username, clientId: context?.Client.ClientId)); - - // only set explicit expiration here if user chooses "remember me". - // otherwise we rely upon expiration configured in cookie middleware. - AuthenticationProperties props = null; - if (LoginOptions.AllowRememberLogin && Input.RememberLogin) - { - props = new AuthenticationProperties - { - IsPersistent = true, - ExpiresUtc = DateTimeOffset.UtcNow.Add(LoginOptions.RememberMeLoginDuration) - }; - }; - - // issue authentication cookie with subject ID and username - var isuser = new IdentityServerUser(user.SubjectId) - { - DisplayName = user.Username - }; - - await HttpContext.SignInAsync(isuser, props); - - 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"); - } - } - - await _events.RaiseAsync(new UserLoginFailureEvent(Input.Username, "invalid credentials", clientId:context?.Client.ClientId)); - ModelState.AddModelError(string.Empty, LoginOptions.InvalidCredentialsErrorMessage); - } - - // something went wrong, show form with error - await BuildModelAsync(Input.ReturnUrl); - return Page(); - } - - private async Task BuildModelAsync(string returnUrl) - { - Input = new InputModel - { - ReturnUrl = returnUrl - }; - - var context = await _interaction.GetAuthorizationContextAsync(returnUrl); - if (context?.IdP != null && await _schemeProvider.GetSchemeAsync(context.IdP) != null) - { - var local = context.IdP == Duende.IdentityServer.IdentityServerConstants.LocalIdentityProvider; - - // this is meant to short circuit the UI and only trigger the one external IdP - View = new ViewModel - { - EnableLocalLogin = local, - }; - - Input.Username = context?.LoginHint; - - if (!local) - { - View.ExternalProviders = new[] { new ViewModel.ExternalProvider { AuthenticationScheme = context.IdP } }; - } - } - - var schemes = await _schemeProvider.GetAllSchemesAsync(); - - var providers = schemes - .Where(x => x.DisplayName != null) - .Select(x => new ViewModel.ExternalProvider - { - DisplayName = x.DisplayName ?? x.Name, - AuthenticationScheme = x.Name - }).ToList(); - - var dyanmicSchemes = (await _identityProviderStore.GetAllSchemeNamesAsync()) - .Where(x => x.Enabled) - .Select(x => new ViewModel.ExternalProvider - { - AuthenticationScheme = x.Scheme, - DisplayName = x.DisplayName - }); - providers.AddRange(dyanmicSchemes); - - - var allowLocal = true; - if (context?.Client.ClientId != null) - { - var client = await _clientStore.FindEnabledClientByIdAsync(context.Client.ClientId); - if (client != null) - { - allowLocal = client.EnableLocalLogin; - - if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Any()) - { - providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList(); - } - } - } - - View = new ViewModel - { - AllowRememberLogin = LoginOptions.AllowRememberLogin, - EnableLocalLogin = allowLocal && LoginOptions.AllowLocalLogin, - ExternalProviders = providers.ToArray() - }; - } -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Account/Login/InputModel.cs b/src/IdentityServer/Pages/Account/Login/InputModel.cs deleted file mode 100644 index de20f0d02..000000000 --- a/src/IdentityServer/Pages/Account/Login/InputModel.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - - -using System.ComponentModel.DataAnnotations; - -namespace IdentityServerHost.Pages.Login; - -public class InputModel -{ - [Required] - public string Username { get; set; } - - [Required] - public string Password { get; set; } - - public bool RememberLogin { get; set; } - - public string ReturnUrl { get; set; } - - public string Button { get; set; } -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Account/Login/LoginOptions.cs b/src/IdentityServer/Pages/Account/Login/LoginOptions.cs deleted file mode 100644 index eb8c1e177..000000000 --- a/src/IdentityServer/Pages/Account/Login/LoginOptions.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace IdentityServerHost.Pages.Login; - -public class LoginOptions -{ - public static bool AllowLocalLogin = true; - public static bool AllowRememberLogin = true; - public static TimeSpan RememberMeLoginDuration = TimeSpan.FromDays(30); - public static string InvalidCredentialsErrorMessage = "Invalid username or password"; -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Account/Login/ViewModel.cs b/src/IdentityServer/Pages/Account/Login/ViewModel.cs deleted file mode 100644 index c9125fa92..000000000 --- a/src/IdentityServer/Pages/Account/Login/ViewModel.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - - -namespace IdentityServerHost.Pages.Login; - -public class ViewModel -{ - public bool AllowRememberLogin { get; set; } = true; - public bool EnableLocalLogin { get; set; } = true; - - public IEnumerable ExternalProviders { get; set; } = Enumerable.Empty(); - public IEnumerable VisibleExternalProviders => ExternalProviders.Where(x => !String.IsNullOrWhiteSpace(x.DisplayName)); - - public bool IsExternalLoginOnly => EnableLocalLogin == false && ExternalProviders?.Count() == 1; - public string ExternalLoginScheme => IsExternalLoginOnly ? ExternalProviders?.SingleOrDefault()?.AuthenticationScheme : null; - - public class ExternalProvider - { - public string DisplayName { get; set; } - public string AuthenticationScheme { get; set; } - } -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Account/Logout/Index.cshtml b/src/IdentityServer/Pages/Account/Logout/Index.cshtml deleted file mode 100644 index 2d8c4beb2..000000000 --- a/src/IdentityServer/Pages/Account/Logout/Index.cshtml +++ /dev/null @@ -1,17 +0,0 @@ -@page -@model IdentityServerHost.Pages.Logout.Index - -
-
-

Logout

-

Would you like to logout of IdentityServer?

-
- -
- - -
- -
-
-
\ No newline at end of file diff --git a/src/IdentityServer/Pages/Account/Logout/Index.cshtml.cs b/src/IdentityServer/Pages/Account/Logout/Index.cshtml.cs deleted file mode 100644 index 7d2562aed..000000000 --- a/src/IdentityServer/Pages/Account/Logout/Index.cshtml.cs +++ /dev/null @@ -1,96 +0,0 @@ -using Duende.IdentityServer.Events; -using Duende.IdentityServer.Extensions; -using Duende.IdentityServer.Services; -using IdentityModel; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace IdentityServerHost.Pages.Logout; - -[SecurityHeaders] -[AllowAnonymous] -public class Index : PageModel -{ - private readonly IIdentityServerInteractionService _interaction; - private readonly IEventService _events; - - [BindProperty] - public string LogoutId { get; set; } - - public Index(IIdentityServerInteractionService interaction, IEventService events) - { - _interaction = interaction; - _events = events; - } - - public async Task OnGet(string logoutId) - { - LogoutId = logoutId; - - var showLogoutPrompt = LogoutOptions.ShowLogoutPrompt; - - if (User?.Identity.IsAuthenticated != true) - { - // if the user is not authenticated, then just show logged out page - showLogoutPrompt = false; - } - else - { - var context = await _interaction.GetLogoutContextAsync(LogoutId); - if (context?.ShowSignoutPrompt == false) - { - // it's safe to automatically sign-out - showLogoutPrompt = false; - } - } - - if (showLogoutPrompt == false) - { - // if the request for logout was properly authenticated from IdentityServer, then - // we don't need to show the prompt and can just log the user out directly. - return await OnPost(); - } - - return Page(); - } - - public async Task OnPost() - { - if (User?.Identity.IsAuthenticated == true) - { - // if there's no current logout context, we need to create one - // this captures necessary info from the current logged in user - // this can still return null if there is no context needed - LogoutId ??= await _interaction.CreateLogoutContextAsync(); - - // delete local authentication cookie - await HttpContext.SignOutAsync(); - - // raise the logout event - await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName())); - - // see if we need to trigger federated logout - var idp = User.FindFirst(JwtClaimTypes.IdentityProvider)?.Value; - - // if it's a local login we can ignore this workflow - if (idp != null && idp != Duende.IdentityServer.IdentityServerConstants.LocalIdentityProvider) - { - // we need to see if the provider supports external logout - if (await HttpContext.GetSchemeSupportsSignOutAsync(idp)) - { - // build a return URL so the upstream provider will redirect back - // to us after the user has logged out. this allows us to then - // complete our single sign-out processing. - string url = Url.Page("/Account/Logout/Loggedout", new { logoutId = LogoutId }); - - // this triggers a redirect to the external provider for sign-out - return SignOut(new AuthenticationProperties { RedirectUri = url }, idp); - } - } - } - - return RedirectToPage("/Account/Logout/LoggedOut", new { logoutId = LogoutId }); - } -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Account/Logout/LoggedOut.cshtml b/src/IdentityServer/Pages/Account/Logout/LoggedOut.cshtml deleted file mode 100644 index 1169453be..000000000 --- a/src/IdentityServer/Pages/Account/Logout/LoggedOut.cshtml +++ /dev/null @@ -1,30 +0,0 @@ -@page -@model IdentityServerHost.Pages.Logout.LoggedOut - -
-

- Logout - You are now logged out -

- - @if (Model.View.PostLogoutRedirectUri != null) - { -
- Click here to return to the - @Model.View.ClientName application. -
- } - - @if (Model.View.SignOutIframeUrl != null) - { - - } -
- -@section scripts -{ - @if (Model.View.AutomaticRedirectAfterSignOut) - { - - } -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Account/Logout/LoggedOut.cshtml.cs b/src/IdentityServer/Pages/Account/Logout/LoggedOut.cshtml.cs deleted file mode 100644 index 6d91b9bbc..000000000 --- a/src/IdentityServer/Pages/Account/Logout/LoggedOut.cshtml.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Duende.IdentityServer.Services; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace IdentityServerHost.Pages.Logout; - -[SecurityHeaders] -[AllowAnonymous] -public class LoggedOut : PageModel -{ - private readonly IIdentityServerInteractionService _interactionService; - - public LoggedOutViewModel View { get; set; } - - public LoggedOut(IIdentityServerInteractionService interactionService) - { - _interactionService = interactionService; - } - - public async Task OnGet(string logoutId) - { - // get context information (client name, post logout redirect URI and iframe for federated signout) - var logout = await _interactionService.GetLogoutContextAsync(logoutId); - - View = new LoggedOutViewModel - { - AutomaticRedirectAfterSignOut = LogoutOptions.AutomaticRedirectAfterSignOut, - PostLogoutRedirectUri = logout?.PostLogoutRedirectUri, - ClientName = String.IsNullOrEmpty(logout?.ClientName) ? logout?.ClientId : logout?.ClientName, - SignOutIframeUrl = logout?.SignOutIFrameUrl - }; - } -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Account/Logout/LoggedOutViewModel.cs b/src/IdentityServer/Pages/Account/Logout/LoggedOutViewModel.cs deleted file mode 100644 index 77b1ef09f..000000000 --- a/src/IdentityServer/Pages/Account/Logout/LoggedOutViewModel.cs +++ /dev/null @@ -1,14 +0,0 @@ - -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - - -namespace IdentityServerHost.Pages.Logout; - -public class LoggedOutViewModel -{ - public string PostLogoutRedirectUri { get; set; } - public string ClientName { get; set; } - public string SignOutIframeUrl { get; set; } - public bool AutomaticRedirectAfterSignOut { get; set; } -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Account/Logout/LogoutOptions.cs b/src/IdentityServer/Pages/Account/Logout/LogoutOptions.cs deleted file mode 100644 index d93a8521a..000000000 --- a/src/IdentityServer/Pages/Account/Logout/LogoutOptions.cs +++ /dev/null @@ -1,8 +0,0 @@ - -namespace IdentityServerHost.Pages.Logout; - -public class LogoutOptions -{ - public static bool ShowLogoutPrompt = true; - public static bool AutomaticRedirectAfterSignOut = false; -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Ciba/All.cshtml b/src/IdentityServer/Pages/Ciba/All.cshtml deleted file mode 100644 index 30ecd6f94..000000000 --- a/src/IdentityServer/Pages/Ciba/All.cshtml +++ /dev/null @@ -1,48 +0,0 @@ -@page -@model IdentityServerHost.Pages.Ciba.AllModel -@{ -} - -
-
-
-
-
-

Pending Backchannel Login Requests

-
-
- @if (Model.Logins?.Any() == true) - { - - - - - - - - - - - @foreach (var login in Model.Logins) - { - - - - - - - } - -
IdClient IdBinding Message
@login.InternalId@login.Client.ClientId@login.BindingMessage - Process -
- } - else - { -
No Pending Login Requests
- } -
-
-
-
-
\ No newline at end of file diff --git a/src/IdentityServer/Pages/Ciba/All.cshtml.cs b/src/IdentityServer/Pages/Ciba/All.cshtml.cs deleted file mode 100644 index d408fb287..000000000 --- a/src/IdentityServer/Pages/Ciba/All.cshtml.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -using System.ComponentModel.DataAnnotations; -using Duende.IdentityServer.Models; -using Duende.IdentityServer.Services; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace IdentityServerHost.Pages.Ciba; - -[SecurityHeaders] -[Authorize] -public class AllModel : PageModel -{ - public IEnumerable Logins { get; set; } - - [BindProperty, Required] - public string Id { get; set; } - [BindProperty, Required] - public string Button { get; set; } - - private readonly IBackchannelAuthenticationInteractionService _backchannelAuthenticationInteraction; - - public AllModel(IBackchannelAuthenticationInteractionService backchannelAuthenticationInteractionService) - { - _backchannelAuthenticationInteraction = backchannelAuthenticationInteractionService; - } - - public async Task OnGet() - { - Logins = await _backchannelAuthenticationInteraction.GetPendingLoginRequestsForCurrentUserAsync(); - } -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Ciba/Consent.cshtml b/src/IdentityServer/Pages/Ciba/Consent.cshtml deleted file mode 100644 index 5960db3a7..000000000 --- a/src/IdentityServer/Pages/Ciba/Consent.cshtml +++ /dev/null @@ -1,98 +0,0 @@ -@page -@model IdentityServerHost.Pages.Ciba.Consent -@{ -} - - diff --git a/src/IdentityServer/Pages/Ciba/Consent.cshtml.cs b/src/IdentityServer/Pages/Ciba/Consent.cshtml.cs deleted file mode 100644 index 4da18ab54..000000000 --- a/src/IdentityServer/Pages/Ciba/Consent.cshtml.cs +++ /dev/null @@ -1,218 +0,0 @@ -using Duende.IdentityServer.Events; -using Duende.IdentityServer.Extensions; -using Duende.IdentityServer.Models; -using Duende.IdentityServer.Services; -using Duende.IdentityServer.Validation; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace IdentityServerHost.Pages.Ciba; - -[Authorize] -[SecurityHeadersAttribute] -public class Consent : PageModel -{ - private readonly IBackchannelAuthenticationInteractionService _interaction; - private readonly IEventService _events; - private readonly ILogger _logger; - - public Consent( - IBackchannelAuthenticationInteractionService interaction, - IEventService events, - ILogger logger) - { - _interaction = interaction; - _events = events; - _logger = logger; - } - - public ViewModel View { get; set; } - - [BindProperty] - public InputModel Input { get; set; } - - public async Task OnGet(string id) - { - View = await BuildViewModelAsync(id); - if (View == null) - { - return RedirectToPage("/Home/Error/Index"); - } - - Input = new InputModel - { - Id = id - }; - - return Page(); - } - - public async Task OnPost() - { - // validate return url is still valid - var request = await _interaction.GetLoginRequestByInternalIdAsync(Input.Id); - if (request == null || request.Subject.GetSubjectId() != User.GetSubjectId()) - { - _logger.LogError("Invalid id {id}", Input.Id); - return RedirectToPage("/Home/Error/Index"); - } - - CompleteBackchannelLoginRequest result = null; - - // user clicked 'no' - send back the standard 'access_denied' response - if (Input?.Button == "no") - { - result = new CompleteBackchannelLoginRequest(Input.Id); - - // emit event - await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); - } - // user clicked 'yes' - validate the data - else if (Input?.Button == "yes") - { - // if the user consented to some scope, build the response model - if (Input.ScopesConsented != null && Input.ScopesConsented.Any()) - { - var scopes = Input.ScopesConsented; - if (ConsentOptions.EnableOfflineAccess == false) - { - scopes = scopes.Where(x => x != Duende.IdentityServer.IdentityServerConstants.StandardScopes.OfflineAccess); - } - - result = new CompleteBackchannelLoginRequest(Input.Id) - { - ScopesValuesConsented = scopes.ToArray(), - Description = Input.Description - }; - - // emit event - await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, result.ScopesValuesConsented, false)); - } - else - { - ModelState.AddModelError("", ConsentOptions.MustChooseOneErrorMessage); - } - } - else - { - ModelState.AddModelError("", ConsentOptions.InvalidSelectionErrorMessage); - } - - if (result != null) - { - // communicate outcome of consent back to identityserver - await _interaction.CompleteLoginRequestAsync(result); - - return RedirectToPage("/Ciba/All"); - } - - // we need to redisplay the consent UI - View = await BuildViewModelAsync(Input.Id, Input); - return Page(); - } - - private async Task BuildViewModelAsync(string id, InputModel model = null) - { - var request = await _interaction.GetLoginRequestByInternalIdAsync(id); - if (request != null && request.Subject.GetSubjectId() == User.GetSubjectId()) - { - return CreateConsentViewModel(model, id, request); - } - else - { - _logger.LogError("No backchannel login request matching id: {id}", id); - } - return null; - } - - private ViewModel CreateConsentViewModel( - InputModel model, string id, - BackchannelUserLoginRequest request) - { - var vm = new ViewModel - { - ClientName = request.Client.ClientName ?? request.Client.ClientId, - ClientUrl = request.Client.ClientUri, - ClientLogoUrl = request.Client.LogoUri, - BindingMessage = request.BindingMessage - }; - - vm.IdentityScopes = request.ValidatedResources.Resources.IdentityResources - .Select(x => CreateScopeViewModel(x, model?.ScopesConsented == null || model.ScopesConsented?.Contains(x.Name) == true)) - .ToArray(); - - var resourceIndicators = request.RequestedResourceIndicators ?? Enumerable.Empty(); - var apiResources = request.ValidatedResources.Resources.ApiResources.Where(x => resourceIndicators.Contains(x.Name)); - - var apiScopes = new List(); - foreach (var parsedScope in request.ValidatedResources.ParsedScopes) - { - var apiScope = request.ValidatedResources.Resources.FindApiScope(parsedScope.ParsedName); - if (apiScope != null) - { - var scopeVm = CreateScopeViewModel(parsedScope, apiScope, model == null || model.ScopesConsented?.Contains(parsedScope.RawValue) == true); - scopeVm.Resources = apiResources.Where(x => x.Scopes.Contains(parsedScope.ParsedName)) - .Select(x => new ResourceViewModel - { - Name = x.Name, - DisplayName = x.DisplayName ?? x.Name, - }).ToArray(); - apiScopes.Add(scopeVm); - } - } - if (ConsentOptions.EnableOfflineAccess && request.ValidatedResources.Resources.OfflineAccess) - { - apiScopes.Add(GetOfflineAccessScope(model == null || model.ScopesConsented?.Contains(Duende.IdentityServer.IdentityServerConstants.StandardScopes.OfflineAccess) == true)); - } - vm.ApiScopes = apiScopes; - - return vm; - } - - private ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check) - { - return new ScopeViewModel - { - Name = identity.Name, - Value = identity.Name, - DisplayName = identity.DisplayName ?? identity.Name, - Description = identity.Description, - Emphasize = identity.Emphasize, - Required = identity.Required, - Checked = check || identity.Required - }; - } - - public ScopeViewModel CreateScopeViewModel(ParsedScopeValue parsedScopeValue, ApiScope apiScope, bool check) - { - var displayName = apiScope.DisplayName ?? apiScope.Name; - if (!String.IsNullOrWhiteSpace(parsedScopeValue.ParsedParameter)) - { - displayName += ":" + parsedScopeValue.ParsedParameter; - } - - return new ScopeViewModel - { - Name = parsedScopeValue.ParsedName, - Value = parsedScopeValue.RawValue, - DisplayName = displayName, - Description = apiScope.Description, - Emphasize = apiScope.Emphasize, - Required = apiScope.Required, - Checked = check || apiScope.Required - }; - } - - private ScopeViewModel GetOfflineAccessScope(bool check) - { - return new ScopeViewModel - { - Value = Duende.IdentityServer.IdentityServerConstants.StandardScopes.OfflineAccess, - DisplayName = ConsentOptions.OfflineAccessDisplayName, - Description = ConsentOptions.OfflineAccessDescription, - Emphasize = true, - Checked = check - }; - } -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Ciba/ConsentOptions.cs b/src/IdentityServer/Pages/Ciba/ConsentOptions.cs deleted file mode 100644 index adca0ebf7..000000000 --- a/src/IdentityServer/Pages/Ciba/ConsentOptions.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - - -namespace IdentityServerHost.Pages.Ciba; - -public class ConsentOptions -{ - public static bool EnableOfflineAccess = true; - public static string OfflineAccessDisplayName = "Offline Access"; - public static string OfflineAccessDescription = "Access to your applications and resources, even when you are offline"; - - public static readonly string MustChooseOneErrorMessage = "You must pick at least one permission"; - public static readonly string InvalidSelectionErrorMessage = "Invalid selection"; -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Ciba/Index.cshtml b/src/IdentityServer/Pages/Ciba/Index.cshtml deleted file mode 100644 index 0f79796fd..000000000 --- a/src/IdentityServer/Pages/Ciba/Index.cshtml +++ /dev/null @@ -1,30 +0,0 @@ -@page -@model IdentityServerHost.Pages.Ciba.IndexModel -@{ -} - -
-
- @if (Model.LoginRequest.Client.LogoUri != null) - { - - } -

- @Model.LoginRequest.Client.ClientName - is requesting your permission -

- -

- Verify that this identifier matches what the client is displaying: - @Model.LoginRequest.BindingMessage -

- -

- Do you wish to continue? -

- - -
-
diff --git a/src/IdentityServer/Pages/Ciba/Index.cshtml.cs b/src/IdentityServer/Pages/Ciba/Index.cshtml.cs deleted file mode 100644 index 36e78717c..000000000 --- a/src/IdentityServer/Pages/Ciba/Index.cshtml.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -using Duende.IdentityServer.Models; -using Duende.IdentityServer.Services; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace IdentityServerHost.Pages.Ciba; - -[AllowAnonymous] -[SecurityHeaders] -public class IndexModel : PageModel -{ - public BackchannelUserLoginRequest LoginRequest { get; set; } - - private readonly IBackchannelAuthenticationInteractionService _backchannelAuthenticationInteraction; - private readonly ILogger _logger; - - public IndexModel(IBackchannelAuthenticationInteractionService backchannelAuthenticationInteractionService, ILogger logger) - { - _backchannelAuthenticationInteraction = backchannelAuthenticationInteractionService; - _logger = logger; - } - - public async Task OnGet(string id) - { - LoginRequest = await _backchannelAuthenticationInteraction.GetLoginRequestByInternalIdAsync(id); - if (LoginRequest == null) - { - _logger.LogWarning("Invalid backchannel login id {id}", id); - return RedirectToPage("/home/error/index"); - } - - return Page(); - } -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Ciba/InputModel.cs b/src/IdentityServer/Pages/Ciba/InputModel.cs deleted file mode 100644 index 5cfede373..000000000 --- a/src/IdentityServer/Pages/Ciba/InputModel.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - - -namespace IdentityServerHost.Pages.Ciba; - -public class InputModel -{ - public string Button { get; set; } - public IEnumerable ScopesConsented { get; set; } - public string Id { get; set; } - public string Description { get; set; } -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Ciba/ViewModel.cs b/src/IdentityServer/Pages/Ciba/ViewModel.cs deleted file mode 100644 index 8149e8131..000000000 --- a/src/IdentityServer/Pages/Ciba/ViewModel.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -namespace IdentityServerHost.Pages.Ciba; - -public class ViewModel -{ - public string ClientName { get; set; } - public string ClientUrl { get; set; } - public string ClientLogoUrl { get; set; } - - public string BindingMessage { get; set; } - - public IEnumerable IdentityScopes { get; set; } - public IEnumerable ApiScopes { get; set; } -} - -public class ScopeViewModel -{ - public string Name { get; set; } - public string Value { get; set; } - public string DisplayName { get; set; } - public string Description { get; set; } - public bool Emphasize { get; set; } - public bool Required { get; set; } - public bool Checked { get; set; } - public IEnumerable Resources { get; set; } -} - -public class ResourceViewModel -{ - public string Name { get; set; } - public string DisplayName { get; set; } -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Ciba/_ScopeListItem.cshtml b/src/IdentityServer/Pages/Ciba/_ScopeListItem.cshtml deleted file mode 100644 index 3edbf10a1..000000000 --- a/src/IdentityServer/Pages/Ciba/_ScopeListItem.cshtml +++ /dev/null @@ -1,47 +0,0 @@ -@using IdentityServerHost.Pages.Ciba -@model ScopeViewModel - -
  • - - @if (Model.Required) - { - (required) - } - @if (Model.Description != null) - { - - } - @if (Model.Resources?.Any() == true) - { - - } -
  • \ No newline at end of file diff --git a/src/IdentityServer/Pages/Consent/ConsentOptions.cs b/src/IdentityServer/Pages/Consent/ConsentOptions.cs deleted file mode 100644 index ad78a1f83..000000000 --- a/src/IdentityServer/Pages/Consent/ConsentOptions.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - - -namespace IdentityServerHost.Pages.Consent; - -public class ConsentOptions -{ - public static bool EnableOfflineAccess = true; - public static string OfflineAccessDisplayName = "Offline Access"; - public static string OfflineAccessDescription = "Access to your applications and resources, even when you are offline"; - - public static readonly string MustChooseOneErrorMessage = "You must pick at least one permission"; - public static readonly string InvalidSelectionErrorMessage = "Invalid selection"; -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Consent/Index.cshtml b/src/IdentityServer/Pages/Consent/Index.cshtml deleted file mode 100644 index 1e3dbb3c6..000000000 --- a/src/IdentityServer/Pages/Consent/Index.cshtml +++ /dev/null @@ -1,107 +0,0 @@ -@page -@model IdentityServerHost.Pages.Consent.Index -@{ -} - - diff --git a/src/IdentityServer/Pages/Consent/Index.cshtml.cs b/src/IdentityServer/Pages/Consent/Index.cshtml.cs deleted file mode 100644 index bd9d07936..000000000 --- a/src/IdentityServer/Pages/Consent/Index.cshtml.cs +++ /dev/null @@ -1,224 +0,0 @@ -using Duende.IdentityServer.Events; -using Duende.IdentityServer.Extensions; -using Duende.IdentityServer.Models; -using Duende.IdentityServer.Services; -using Duende.IdentityServer.Validation; -using IdentityModel; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace IdentityServerHost.Pages.Consent; - -[Authorize] -[SecurityHeadersAttribute] -public class Index : PageModel -{ - private readonly IIdentityServerInteractionService _interaction; - private readonly IEventService _events; - private readonly ILogger _logger; - - public Index( - IIdentityServerInteractionService interaction, - IEventService events, - ILogger logger) - { - _interaction = interaction; - _events = events; - _logger = logger; - } - - public ViewModel View { get; set; } - - [BindProperty] - public InputModel Input { get; set; } - - public async Task OnGet(string returnUrl) - { - View = await BuildViewModelAsync(returnUrl); - if (View == null) - { - return RedirectToPage("/Error/Index"); - } - - Input = new InputModel - { - ReturnUrl = returnUrl, - }; - - return Page(); - } - - public async Task OnPost() - { - // validate return url is still valid - var request = await _interaction.GetAuthorizationContextAsync(Input.ReturnUrl); - if (request == null) return RedirectToPage("/Error/Index"); - - ConsentResponse grantedConsent = null; - - // user clicked 'no' - send back the standard 'access_denied' response - if (Input?.Button == "no") - { - grantedConsent = new ConsentResponse { Error = AuthorizationError.AccessDenied }; - - // emit event - await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); - } - // user clicked 'yes' - validate the data - else if (Input?.Button == "yes") - { - // if the user consented to some scope, build the response model - if (Input.ScopesConsented != null && Input.ScopesConsented.Any()) - { - var scopes = Input.ScopesConsented; - if (ConsentOptions.EnableOfflineAccess == false) - { - scopes = scopes.Where(x => x != Duende.IdentityServer.IdentityServerConstants.StandardScopes.OfflineAccess); - } - - grantedConsent = new ConsentResponse - { - RememberConsent = Input.RememberConsent, - ScopesValuesConsented = scopes.ToArray(), - Description = Input.Description - }; - - // emit event - await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); - } - else - { - ModelState.AddModelError("", ConsentOptions.MustChooseOneErrorMessage); - } - } - else - { - ModelState.AddModelError("", ConsentOptions.InvalidSelectionErrorMessage); - } - - if (grantedConsent != null) - { - // communicate outcome of consent back to identityserver - await _interaction.GrantConsentAsync(request, grantedConsent); - - // redirect back to authorization endpoint - if (request.IsNativeClient() == true) - { - // 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); - } - - // we need to redisplay the consent UI - View = await BuildViewModelAsync(Input.ReturnUrl, Input); - return Page(); - } - - private async Task BuildViewModelAsync(string returnUrl, InputModel model = null) - { - var request = await _interaction.GetAuthorizationContextAsync(returnUrl); - if (request != null) - { - return CreateConsentViewModel(model, returnUrl, request); - } - else - { - _logger.LogError("No consent request matching request: {0}", returnUrl); - } - return null; - } - - private ViewModel CreateConsentViewModel( - InputModel model, string returnUrl, - AuthorizationRequest request) - { - var vm = new ViewModel - { - ClientName = request.Client.ClientName ?? request.Client.ClientId, - ClientUrl = request.Client.ClientUri, - ClientLogoUrl = request.Client.LogoUri, - AllowRememberConsent = request.Client.AllowRememberConsent - }; - - vm.IdentityScopes = request.ValidatedResources.Resources.IdentityResources - .Select(x => CreateScopeViewModel(x, model?.ScopesConsented == null || model.ScopesConsented?.Contains(x.Name) == true)) - .ToArray(); - - var resourceIndicators = request.Parameters.GetValues(OidcConstants.AuthorizeRequest.Resource) ?? Enumerable.Empty(); - var apiResources = request.ValidatedResources.Resources.ApiResources.Where(x => resourceIndicators.Contains(x.Name)); - - var apiScopes = new List(); - foreach (var parsedScope in request.ValidatedResources.ParsedScopes) - { - var apiScope = request.ValidatedResources.Resources.FindApiScope(parsedScope.ParsedName); - if (apiScope != null) - { - var scopeVm = CreateScopeViewModel(parsedScope, apiScope, model == null || model.ScopesConsented?.Contains(parsedScope.RawValue) == true); - scopeVm.Resources = apiResources.Where(x => x.Scopes.Contains(parsedScope.ParsedName)) - .Select(x => new ResourceViewModel - { - Name = x.Name, - DisplayName = x.DisplayName ?? x.Name, - }).ToArray(); - apiScopes.Add(scopeVm); - } - } - if (ConsentOptions.EnableOfflineAccess && request.ValidatedResources.Resources.OfflineAccess) - { - apiScopes.Add(GetOfflineAccessScope(model == null || model.ScopesConsented?.Contains(Duende.IdentityServer.IdentityServerConstants.StandardScopes.OfflineAccess) == true)); - } - vm.ApiScopes = apiScopes; - - return vm; - } - - private ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check) - { - return new ScopeViewModel - { - Name = identity.Name, - Value = identity.Name, - DisplayName = identity.DisplayName ?? identity.Name, - Description = identity.Description, - Emphasize = identity.Emphasize, - Required = identity.Required, - Checked = check || identity.Required - }; - } - - public ScopeViewModel CreateScopeViewModel(ParsedScopeValue parsedScopeValue, ApiScope apiScope, bool check) - { - var displayName = apiScope.DisplayName ?? apiScope.Name; - if (!String.IsNullOrWhiteSpace(parsedScopeValue.ParsedParameter)) - { - displayName += ":" + parsedScopeValue.ParsedParameter; - } - - return new ScopeViewModel - { - Name = parsedScopeValue.ParsedName, - Value = parsedScopeValue.RawValue, - DisplayName = displayName, - Description = apiScope.Description, - Emphasize = apiScope.Emphasize, - Required = apiScope.Required, - Checked = check || apiScope.Required - }; - } - - private ScopeViewModel GetOfflineAccessScope(bool check) - { - return new ScopeViewModel - { - Value = Duende.IdentityServer.IdentityServerConstants.StandardScopes.OfflineAccess, - DisplayName = ConsentOptions.OfflineAccessDisplayName, - Description = ConsentOptions.OfflineAccessDescription, - Emphasize = true, - Checked = check - }; - } -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Consent/InputModel.cs b/src/IdentityServer/Pages/Consent/InputModel.cs deleted file mode 100644 index 31dedcb01..000000000 --- a/src/IdentityServer/Pages/Consent/InputModel.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - - -namespace IdentityServerHost.Pages.Consent; - -public class InputModel -{ - public string Button { get; set; } - public IEnumerable ScopesConsented { get; set; } - public bool RememberConsent { get; set; } = true; - public string ReturnUrl { get; set; } - public string Description { get; set; } -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Consent/ViewModel.cs b/src/IdentityServer/Pages/Consent/ViewModel.cs deleted file mode 100644 index 85e6ee73e..000000000 --- a/src/IdentityServer/Pages/Consent/ViewModel.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -namespace IdentityServerHost.Pages.Consent; - -public class ViewModel -{ - public string ClientName { get; set; } - public string ClientUrl { get; set; } - public string ClientLogoUrl { get; set; } - public bool AllowRememberConsent { get; set; } - - public IEnumerable IdentityScopes { get; set; } - public IEnumerable ApiScopes { get; set; } -} - -public class ScopeViewModel -{ - public string Name { get; set; } - public string Value { get; set; } - public string DisplayName { get; set; } - public string Description { get; set; } - public bool Emphasize { get; set; } - public bool Required { get; set; } - public bool Checked { get; set; } - public IEnumerable Resources { get; set; } -} - -public class ResourceViewModel -{ - public string Name { get; set; } - public string DisplayName { get; set; } -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Consent/_ScopeListItem.cshtml b/src/IdentityServer/Pages/Consent/_ScopeListItem.cshtml deleted file mode 100644 index 465fadee6..000000000 --- a/src/IdentityServer/Pages/Consent/_ScopeListItem.cshtml +++ /dev/null @@ -1,47 +0,0 @@ -@using IdentityServerHost.Pages.Consent -@model ScopeViewModel - -
  • - - @if (Model.Required) - { - (required) - } - @if (Model.Description != null) - { - - } - @if (Model.Resources?.Any() == true) - { - - } -
  • \ No newline at end of file diff --git a/src/IdentityServer/Pages/Device/DeviceOptions.cs b/src/IdentityServer/Pages/Device/DeviceOptions.cs deleted file mode 100644 index 421ea7aa5..000000000 --- a/src/IdentityServer/Pages/Device/DeviceOptions.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - - -namespace IdentityServerHost.Pages.Device; - -public class DeviceOptions -{ - public static bool EnableOfflineAccess = true; - public static string OfflineAccessDisplayName = "Offline Access"; - public static string OfflineAccessDescription = "Access to your applications and resources, even when you are offline"; - - public static readonly string InvalidUserCode = "Invalid user code"; - public static readonly string MustChooseOneErrorMessage = "You must pick at least one permission"; - public static readonly string InvalidSelectionErrorMessage = "Invalid selection"; -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Device/Index.cshtml b/src/IdentityServer/Pages/Device/Index.cshtml deleted file mode 100644 index 1dabae4d2..000000000 --- a/src/IdentityServer/Pages/Device/Index.cshtml +++ /dev/null @@ -1,141 +0,0 @@ -@page -@model IdentityServerHost.Pages.Device.Index -@{ -} - -@if (Model.Input.UserCode == null) -{ - @*We need to collect the user code*@ -
    -
    -

    User Code

    -

    Please enter the code displayed on your device.

    -
    - -
    -
    - -
    -
    - -
    -
    -
    -
    - - -
    - - -
    -
    -
    -
    -} -else -{ - @*collect consent for the user code provided*@ -
    -
    - @if (Model.View.ClientLogoUrl != null) - { - - } -

    - @Model.View.ClientName - is requesting your permission -

    -

    Please confirm that the authorization request matches the code: @Model.Input.UserCode.

    -

    Uncheck the permissions you do not wish to grant.

    -
    - -
    -
    - -
    -
    - -
    - -
    -
    - @if (Model.View.IdentityScopes.Any()) - { -
    -
    -
    - - Personal Information -
    -
      - @foreach (var scope in Model.View.IdentityScopes) - { - - } -
    -
    -
    - } - - @if (Model.View.ApiScopes.Any()) - { -
    -
    -
    - - Application Access -
    -
      - @foreach (var scope in Model.View.ApiScopes) - { - - } -
    -
    -
    - } - -
    -
    -
    - - Description -
    -
    - -
    -
    -
    - - @if (Model.View.AllowRememberConsent) - { -
    -
    - - -
    -
    - } -
    -
    - -
    -
    - - -
    -
    - @if (Model.View.ClientUrl != null) - { - - - @Model.View.ClientName - - } -
    -
    -
    -
    -} diff --git a/src/IdentityServer/Pages/Device/Index.cshtml.cs b/src/IdentityServer/Pages/Device/Index.cshtml.cs deleted file mode 100644 index a41a0177b..000000000 --- a/src/IdentityServer/Pages/Device/Index.cshtml.cs +++ /dev/null @@ -1,211 +0,0 @@ -using Duende.IdentityServer.Configuration; -using Duende.IdentityServer.Events; -using Duende.IdentityServer.Extensions; -using Duende.IdentityServer.Models; -using Duende.IdentityServer.Services; -using Duende.IdentityServer.Validation; -using IdentityServerHost.Pages.Consent; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.Extensions.Options; - -namespace IdentityServerHost.Pages.Device; - -[SecurityHeaders] -[Authorize] -public class Index : PageModel -{ - private readonly IDeviceFlowInteractionService _interaction; - private readonly IEventService _events; - private readonly IOptions _options; - private readonly ILogger _logger; - - public Index( - IDeviceFlowInteractionService interaction, - IEventService eventService, - IOptions options, - ILogger logger) - { - _interaction = interaction; - _events = eventService; - _options = options; - _logger = logger; - } - - public ViewModel View { get; set; } - - [BindProperty] - public InputModel Input { get; set; } - - public async Task OnGet(string userCode) - { - if (String.IsNullOrWhiteSpace(userCode)) - { - View = new ViewModel(); - Input = new InputModel(); - return Page(); - } - - View = await BuildViewModelAsync(userCode); - if (View == null) - { - ModelState.AddModelError("", DeviceOptions.InvalidUserCode); - View = new ViewModel(); - Input = new InputModel(); - return Page(); - } - - Input = new InputModel { - UserCode = userCode, - }; - - return Page(); - } - - public async Task OnPost() - { - var request = await _interaction.GetAuthorizationContextAsync(Input.UserCode); - if (request == null) return RedirectToPage("/Error/Index"); - - ConsentResponse grantedConsent = null; - - // user clicked 'no' - send back the standard 'access_denied' response - if (Input.Button == "no") - { - grantedConsent = new ConsentResponse - { - Error = AuthorizationError.AccessDenied - }; - - // emit event - await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); - } - // user clicked 'yes' - validate the data - else if (Input.Button == "yes") - { - // if the user consented to some scope, build the response model - if (Input.ScopesConsented != null && Input.ScopesConsented.Any()) - { - var scopes = Input.ScopesConsented; - if (ConsentOptions.EnableOfflineAccess == false) - { - scopes = scopes.Where(x => x != Duende.IdentityServer.IdentityServerConstants.StandardScopes.OfflineAccess); - } - - grantedConsent = new ConsentResponse - { - RememberConsent = Input.RememberConsent, - ScopesValuesConsented = scopes.ToArray(), - Description = Input.Description - }; - - // emit event - await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); - } - else - { - ModelState.AddModelError("", ConsentOptions.MustChooseOneErrorMessage); - } - } - else - { - ModelState.AddModelError("", ConsentOptions.InvalidSelectionErrorMessage); - } - - if (grantedConsent != null) - { - // communicate outcome of consent back to identityserver - await _interaction.HandleRequestAsync(Input.UserCode, grantedConsent); - - // indicate that's it ok to redirect back to authorization endpoint - return RedirectToPage("/Device/Success"); - } - - // we need to redisplay the consent UI - View = await BuildViewModelAsync(Input.UserCode, Input); - return Page(); - } - - - private async Task BuildViewModelAsync(string userCode, InputModel model = null) - { - var request = await _interaction.GetAuthorizationContextAsync(userCode); - if (request != null) - { - return CreateConsentViewModel(model, request); - } - - return null; - } - - private ViewModel CreateConsentViewModel(InputModel model, DeviceFlowAuthorizationRequest request) - { - var vm = new ViewModel - { - ClientName = request.Client.ClientName ?? request.Client.ClientId, - ClientUrl = request.Client.ClientUri, - ClientLogoUrl = request.Client.LogoUri, - AllowRememberConsent = request.Client.AllowRememberConsent - }; - - vm.IdentityScopes = request.ValidatedResources.Resources.IdentityResources.Select(x => CreateScopeViewModel(x, model == null || model.ScopesConsented?.Contains(x.Name) == true)).ToArray(); - - var apiScopes = new List(); - foreach (var parsedScope in request.ValidatedResources.ParsedScopes) - { - var apiScope = request.ValidatedResources.Resources.FindApiScope(parsedScope.ParsedName); - if (apiScope != null) - { - var scopeVm = CreateScopeViewModel(parsedScope, apiScope, model == null || model.ScopesConsented?.Contains(parsedScope.RawValue) == true); - apiScopes.Add(scopeVm); - } - } - if (DeviceOptions.EnableOfflineAccess && request.ValidatedResources.Resources.OfflineAccess) - { - apiScopes.Add(GetOfflineAccessScope(model == null || model.ScopesConsented?.Contains(Duende.IdentityServer.IdentityServerConstants.StandardScopes.OfflineAccess) == true)); - } - vm.ApiScopes = apiScopes; - - return vm; - } - - private ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check) - { - return new ScopeViewModel - { - Value = identity.Name, - DisplayName = identity.DisplayName ?? identity.Name, - Description = identity.Description, - Emphasize = identity.Emphasize, - Required = identity.Required, - Checked = check || identity.Required - }; - } - - public ScopeViewModel CreateScopeViewModel(ParsedScopeValue parsedScopeValue, ApiScope apiScope, bool check) - { - return new ScopeViewModel - { - Value = parsedScopeValue.RawValue, - // todo: use the parsed scope value in the display? - DisplayName = apiScope.DisplayName ?? apiScope.Name, - Description = apiScope.Description, - Emphasize = apiScope.Emphasize, - Required = apiScope.Required, - Checked = check || apiScope.Required - }; - } - - private ScopeViewModel GetOfflineAccessScope(bool check) - { - return new ScopeViewModel - { - Value = Duende.IdentityServer.IdentityServerConstants.StandardScopes.OfflineAccess, - DisplayName = DeviceOptions.OfflineAccessDisplayName, - Description = DeviceOptions.OfflineAccessDescription, - Emphasize = true, - Checked = check - }; - } -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Device/InputModel.cs b/src/IdentityServer/Pages/Device/InputModel.cs deleted file mode 100644 index 157286f15..000000000 --- a/src/IdentityServer/Pages/Device/InputModel.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace IdentityServerHost.Pages.Device; - -public class InputModel -{ - public string Button { get; set; } - public IEnumerable ScopesConsented { get; set; } - public bool RememberConsent { get; set; } = true; - public string ReturnUrl { get; set; } - public string Description { get; set; } - public string UserCode { get; set; } -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Device/Success.cshtml b/src/IdentityServer/Pages/Device/Success.cshtml deleted file mode 100644 index 74b374095..000000000 --- a/src/IdentityServer/Pages/Device/Success.cshtml +++ /dev/null @@ -1,12 +0,0 @@ -@page -@model IdentityServerHost.Pages.Device.SuccessModel -@{ -} - - -
    -
    -

    Success

    -

    You have successfully authorized the device

    -
    -
    diff --git a/src/IdentityServer/Pages/Device/Success.cshtml.cs b/src/IdentityServer/Pages/Device/Success.cshtml.cs deleted file mode 100644 index 184f650ae..000000000 --- a/src/IdentityServer/Pages/Device/Success.cshtml.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace IdentityServerHost.Pages.Device; - -[SecurityHeaders] -[Authorize] -public class SuccessModel : PageModel -{ - public void OnGet() - { - } -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Device/ViewModel.cs b/src/IdentityServer/Pages/Device/ViewModel.cs deleted file mode 100644 index 01085a84a..000000000 --- a/src/IdentityServer/Pages/Device/ViewModel.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace IdentityServerHost.Pages.Device; - -public class ViewModel -{ - public string ClientName { get; set; } - public string ClientUrl { get; set; } - public string ClientLogoUrl { get; set; } - public bool AllowRememberConsent { get; set; } - - public IEnumerable IdentityScopes { get; set; } - public IEnumerable ApiScopes { get; set; } -} - -public class ScopeViewModel -{ - public string Value { get; set; } - public string DisplayName { get; set; } - public string Description { get; set; } - public bool Emphasize { get; set; } - public bool Required { get; set; } - public bool Checked { get; set; } -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Device/_ScopeListItem.cshtml b/src/IdentityServer/Pages/Device/_ScopeListItem.cshtml deleted file mode 100644 index 367a4f6ac..000000000 --- a/src/IdentityServer/Pages/Device/_ScopeListItem.cshtml +++ /dev/null @@ -1,35 +0,0 @@ -@using IdentityServerHost.Pages.Device -@model ScopeViewModel - -
  • - - @if (Model.Required) - { - (required) - } - @if (Model.Description != null) - { - - } -
  • \ No newline at end of file diff --git a/src/IdentityServer/Pages/Diagnostics/Index.cshtml b/src/IdentityServer/Pages/Diagnostics/Index.cshtml deleted file mode 100644 index c93825d92..000000000 --- a/src/IdentityServer/Pages/Diagnostics/Index.cshtml +++ /dev/null @@ -1,61 +0,0 @@ -@page -@model IdentityServerHost.Pages.Diagnostics.Index - -
    -
    -

    Authentication Cookie

    -
    - -
    -
    -
    -
    -

    Claims

    -
    -
    -
    - @foreach (var claim in Model.View.AuthenticateResult.Principal.Claims) - { -
    @claim.Type
    -
    @claim.Value
    - } -
    -
    -
    -
    - -
    -
    -
    -

    Properties

    -
    -
    -
    - @foreach (var prop in Model.View.AuthenticateResult.Properties.Items) - { -
    @prop.Key
    -
    @prop.Value
    - } - @if (Model.View.Clients.Any()) - { -
    Clients
    -
    - @{ - var clients = Model.View.Clients.ToArray(); - for(var i = 0; i < clients.Length; i++) - { - @clients[i] - if (i < clients.Length - 1) - { - , - } - } - } -
    - } -
    -
    -
    -
    -
    -
    \ No newline at end of file diff --git a/src/IdentityServer/Pages/Diagnostics/Index.cshtml.cs b/src/IdentityServer/Pages/Diagnostics/Index.cshtml.cs deleted file mode 100644 index 7d8479e8e..000000000 --- a/src/IdentityServer/Pages/Diagnostics/Index.cshtml.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.AspNetCore.Authorization; - -namespace IdentityServerHost.Pages.Diagnostics; - -[SecurityHeaders] -[Authorize] -public class Index : PageModel -{ - public ViewModel View { get; set; } - - public async Task OnGet() - { - var localAddresses = new string[] { "127.0.0.1", "::1", HttpContext.Connection.LocalIpAddress.ToString() }; - if (!localAddresses.Contains(HttpContext.Connection.RemoteIpAddress.ToString())) - { - return NotFound(); - } - - View = new ViewModel(await HttpContext.AuthenticateAsync()); - - return Page(); - } -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Diagnostics/ViewModel.cs b/src/IdentityServer/Pages/Diagnostics/ViewModel.cs deleted file mode 100644 index e4d95ba68..000000000 --- a/src/IdentityServer/Pages/Diagnostics/ViewModel.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - - -using IdentityModel; -using Microsoft.AspNetCore.Authentication; -using System.Text; -using System.Text.Json; - -namespace IdentityServerHost.Pages.Diagnostics; - -public class ViewModel -{ - public ViewModel(AuthenticateResult result) - { - AuthenticateResult = result; - - if (result.Properties.Items.ContainsKey("client_list")) - { - var encoded = result.Properties.Items["client_list"]; - var bytes = Base64Url.Decode(encoded); - var value = Encoding.UTF8.GetString(bytes); - - Clients = JsonSerializer.Deserialize(value); - } - } - - public AuthenticateResult AuthenticateResult { get; } - public IEnumerable Clients { get; } = new List(); -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Extensions.cs b/src/IdentityServer/Pages/Extensions.cs deleted file mode 100644 index 04b68949a..000000000 --- a/src/IdentityServer/Pages/Extensions.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - - -using Duende.IdentityServer.Models; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace IdentityServerHost.Pages; - -public static class Extensions -{ - /// - /// Determines if the authentication scheme support signout. - /// - public static async Task GetSchemeSupportsSignOutAsync(this HttpContext context, string scheme) - { - var provider = context.RequestServices.GetRequiredService(); - var handler = await provider.GetHandlerAsync(context, scheme); - return (handler is IAuthenticationSignOutHandler); - } - - /// - /// Checks if the redirect URI is for a native client. - /// - public static bool IsNativeClient(this AuthorizationRequest context) - { - return !context.RedirectUri.StartsWith("https", StringComparison.Ordinal) - && !context.RedirectUri.StartsWith("http", StringComparison.Ordinal); - } - - /// - /// Renders a loading page that is used to redirect back to the redirectUri. - /// - public static IActionResult LoadingPage(this PageModel page, string redirectUri) - { - page.HttpContext.Response.StatusCode = 200; - page.HttpContext.Response.Headers["Location"] = ""; - - return page.RedirectToPage("/Redirect/Index", new { RedirectUri = redirectUri }); - } -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/ExternalLogin/Callback.cshtml b/src/IdentityServer/Pages/ExternalLogin/Callback.cshtml deleted file mode 100644 index 80fe0fb15..000000000 --- a/src/IdentityServer/Pages/ExternalLogin/Callback.cshtml +++ /dev/null @@ -1,19 +0,0 @@ -@page -@model IdentityServerHost.Pages.ExternalLogin.Callback - -@{ - Layout = null; -} - - - - - - - - -
    - -
    - - \ No newline at end of file diff --git a/src/IdentityServer/Pages/ExternalLogin/Callback.cshtml.cs b/src/IdentityServer/Pages/ExternalLogin/Callback.cshtml.cs deleted file mode 100644 index 2a737dc65..000000000 --- a/src/IdentityServer/Pages/ExternalLogin/Callback.cshtml.cs +++ /dev/null @@ -1,138 +0,0 @@ -using System.Security.Claims; -using Duende.IdentityServer; -using Duende.IdentityServer.Events; -using Duende.IdentityServer.Services; -using Duende.IdentityServer.Test; -using IdentityModel; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace IdentityServerHost.Pages.ExternalLogin; - -[AllowAnonymous] -[SecurityHeaders] -public class Callback : PageModel -{ - private readonly TestUserStore _users; - private readonly IIdentityServerInteractionService _interaction; - private readonly ILogger _logger; - private readonly IEventService _events; - - public Callback( - IIdentityServerInteractionService interaction, - IEventService events, - ILogger logger, - 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; - _logger = logger; - _events = events; - } - - public async Task OnGet() - { - // read external identity from the temporary cookie - var result = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme); - if (result?.Succeeded != true) - { - throw new Exception("External authentication error"); - } - - var externalUser = result.Principal; - - if (_logger.IsEnabled(LogLevel.Debug)) - { - var externalClaims = externalUser.Claims.Select(c => $"{c.Type}: {c.Value}"); - _logger.LogDebug("External claims: {@claims}", externalClaims); - } - - // lookup our user and external provider info - // try to determine the unique id of the external user (issued by the provider) - // the most common claim type for that are the sub claim and the NameIdentifier - // depending on the external provider, some other claim type might be used - var userIdClaim = externalUser.FindFirst(JwtClaimTypes.Subject) ?? - externalUser.FindFirst(ClaimTypes.NameIdentifier) ?? - throw new Exception("Unknown userid"); - - var provider = result.Properties.Items["scheme"]; - var providerUserId = userIdClaim.Value; - - // find external user - var user = _users.FindByExternalProvider(provider, providerUserId); - if (user == null) - { - // this might be where you might initiate a custom workflow for user registration - // in this sample we don't show how that would be done, as our sample implementation - // simply auto-provisions new external user - // - // remove the user id claim so we don't include it as an extra claim if/when we provision the user - var claims = externalUser.Claims.ToList(); - claims.Remove(userIdClaim); - user = _users.AutoProvisionUser(provider, providerUserId, claims.ToList()); - } - - // this allows us to collect any additional claims or properties - // for the specific protocols used and store them in the local auth cookie. - // this is typically used to store data needed for signout from those protocols. - var additionalLocalClaims = new List(); - var localSignInProps = new AuthenticationProperties(); - CaptureExternalLoginContext(result, additionalLocalClaims, localSignInProps); - - // issue authentication cookie for user - var isuser = new IdentityServerUser(user.SubjectId) - { - DisplayName = user.Username, - IdentityProvider = provider, - AdditionalClaims = additionalLocalClaims - }; - - await HttpContext.SignInAsync(isuser, localSignInProps); - - // delete temporary cookie used during external authentication - await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme); - - // retrieve return URL - var returnUrl = result.Properties.Items["returnUrl"] ?? "~/"; - - // check if external login is in the context of an OIDC request - var context = await _interaction.GetAuthorizationContextAsync(returnUrl); - await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.SubjectId, user.Username, true, context?.Client.ClientId)); - - 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(returnUrl); - } - } - - return Redirect(returnUrl); - } - - // if the external login is OIDC-based, there are certain things we need to preserve to make logout work - // this will be different for WS-Fed, SAML2p or other protocols - private void CaptureExternalLoginContext(AuthenticateResult externalResult, List localClaims, AuthenticationProperties localSignInProps) - { - // if the external system sent a session id claim, copy it over - // so we can use it for single sign-out - var sid = externalResult.Principal.Claims.FirstOrDefault(x => x.Type == JwtClaimTypes.SessionId); - if (sid != null) - { - localClaims.Add(new Claim(JwtClaimTypes.SessionId, sid.Value)); - } - - // if the external provider issued an id_token, we'll keep it for signout - var idToken = externalResult.Properties.GetTokenValue("id_token"); - if (idToken != null) - { - localSignInProps.StoreTokens(new[] { new AuthenticationToken { Name = "id_token", Value = idToken } }); - } - } -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/ExternalLogin/Challenge.cshtml b/src/IdentityServer/Pages/ExternalLogin/Challenge.cshtml deleted file mode 100644 index d6680bdac..000000000 --- a/src/IdentityServer/Pages/ExternalLogin/Challenge.cshtml +++ /dev/null @@ -1,19 +0,0 @@ -@page -@model IdentityServerHost.Pages.ExternalLogin.Challenge - -@{ - Layout = null; -} - - - - - - - - -
    - -
    - - \ No newline at end of file diff --git a/src/IdentityServer/Pages/ExternalLogin/Challenge.cshtml.cs b/src/IdentityServer/Pages/ExternalLogin/Challenge.cshtml.cs deleted file mode 100644 index 47659e974..000000000 --- a/src/IdentityServer/Pages/ExternalLogin/Challenge.cshtml.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Duende.IdentityServer.Services; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace IdentityServerHost.Pages.ExternalLogin; - -[AllowAnonymous] -[SecurityHeaders] -public class Challenge : PageModel -{ - private readonly IIdentityServerInteractionService _interactionService; - - public Challenge(IIdentityServerInteractionService interactionService) - { - _interactionService = interactionService; - } - - public IActionResult OnGet(string scheme, string returnUrl) - { - if (string.IsNullOrEmpty(returnUrl)) returnUrl = "~/"; - - // validate returnUrl - either it is a valid OIDC URL or back to a local page - if (Url.IsLocalUrl(returnUrl) == false && _interactionService.IsValidReturnUrl(returnUrl) == false) - { - // user might have clicked on a malicious link - should be logged - throw new Exception("invalid return URL"); - } - - // start challenge and roundtrip the return URL and scheme - var props = new AuthenticationProperties - { - RedirectUri = Url.Page("/externallogin/callback"), - - Items = - { - { "returnUrl", returnUrl }, - { "scheme", scheme }, - } - }; - - return Challenge(props, scheme); - } -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Grants/Index.cshtml b/src/IdentityServer/Pages/Grants/Index.cshtml deleted file mode 100644 index 9d3ffae6d..000000000 --- a/src/IdentityServer/Pages/Grants/Index.cshtml +++ /dev/null @@ -1,90 +0,0 @@ -@page -@model IdentityServerHost.Pages.Grants.Index -@{ -} - -
    -
    -

    Client Application Permissions

    -

    Below is the list of applications you have given permission to and the resources they have access to.

    -
    - - @if (Model.View.Grants.Any() == false) - { -
    -
    -
    - You have not given access to any applications -
    -
    -
    - } - else - { - foreach (var grant in Model.View.Grants) - { -
    -
    -
    -
    - @if (grant.ClientLogoUrl != null) - { - - } - @grant.ClientName -
    - -
    -
    - - -
    -
    -
    -
    - -
      - @if (grant.Description != null) - { -
    • - @grant.Description -
    • - } -
    • - @grant.Created.ToString("yyyy-MM-dd") -
    • - @if (grant.Expires.HasValue) - { -
    • - @grant.Expires.Value.ToString("yyyy-MM-dd") -
    • - } - @if (grant.IdentityGrantNames.Any()) - { -
    • - -
        - @foreach (var name in grant.IdentityGrantNames) - { -
      • @name
      • - } -
      -
    • - } - @if (grant.ApiGrantNames.Any()) - { -
    • - -
        - @foreach (var name in grant.ApiGrantNames) - { -
      • @name
      • - } -
      -
    • - } -
    -
    - } - } -
    \ No newline at end of file diff --git a/src/IdentityServer/Pages/Grants/Index.cshtml.cs b/src/IdentityServer/Pages/Grants/Index.cshtml.cs deleted file mode 100644 index fc915847c..000000000 --- a/src/IdentityServer/Pages/Grants/Index.cshtml.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Duende.IdentityServer.Events; -using Duende.IdentityServer.Extensions; -using Duende.IdentityServer.Services; -using Duende.IdentityServer.Stores; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace IdentityServerHost.Pages.Grants; - -[SecurityHeaders] -[Authorize] -public class Index : PageModel -{ - private readonly IIdentityServerInteractionService _interaction; - private readonly IClientStore _clients; - private readonly IResourceStore _resources; - private readonly IEventService _events; - - public Index(IIdentityServerInteractionService interaction, - IClientStore clients, - IResourceStore resources, - IEventService events) - { - _interaction = interaction; - _clients = clients; - _resources = resources; - _events = events; - } - - public ViewModel View { get; set; } - - public async Task OnGet() - { - var grants = await _interaction.GetAllUserGrantsAsync(); - - var list = new List(); - foreach (var grant in grants) - { - var client = await _clients.FindClientByIdAsync(grant.ClientId); - if (client != null) - { - var resources = await _resources.FindResourcesByScopeAsync(grant.Scopes); - - var item = new GrantViewModel() - { - ClientId = client.ClientId, - ClientName = client.ClientName ?? client.ClientId, - ClientLogoUrl = client.LogoUri, - ClientUrl = client.ClientUri, - Description = grant.Description, - Created = grant.CreationTime, - Expires = grant.Expiration, - IdentityGrantNames = resources.IdentityResources.Select(x => x.DisplayName ?? x.Name).ToArray(), - ApiGrantNames = resources.ApiScopes.Select(x => x.DisplayName ?? x.Name).ToArray() - }; - - list.Add(item); - } - } - - View = new ViewModel - { - Grants = list - }; - } - - [BindProperty] - [Required] - public string ClientId { get; set; } - - public async Task OnPost() - { - await _interaction.RevokeUserConsentAsync(ClientId); - await _events.RaiseAsync(new GrantsRevokedEvent(User.GetSubjectId(), ClientId)); - - return RedirectToPage("/Grants/Index"); - } -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Grants/ViewModel.cs b/src/IdentityServer/Pages/Grants/ViewModel.cs deleted file mode 100644 index 4036019be..000000000 --- a/src/IdentityServer/Pages/Grants/ViewModel.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace IdentityServerHost.Pages.Grants; - -public class ViewModel -{ - public IEnumerable Grants { get; set; } -} - -public class GrantViewModel -{ - public string ClientId { get; set; } - public string ClientName { get; set; } - public string ClientUrl { get; set; } - public string ClientLogoUrl { get; set; } - public string Description { get; set; } - public DateTime Created { get; set; } - public DateTime? Expires { get; set; } - public IEnumerable IdentityGrantNames { get; set; } - public IEnumerable ApiGrantNames { get; set; } -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Home/Error/Index.cshtml b/src/IdentityServer/Pages/Home/Error/Index.cshtml deleted file mode 100644 index 2d4e83946..000000000 --- a/src/IdentityServer/Pages/Home/Error/Index.cshtml +++ /dev/null @@ -1,35 +0,0 @@ -@page -@model IdentityServerHost.Pages.Error.Index - -
    -
    -

    Error

    -
    - -
    -
    -
    - Sorry, there was an error - - @if (Model.View.Error != null) - { - - - : @Model.View.Error.Error - - - - if (Model.View.Error.ErrorDescription != null) - { -
    @Model.View.Error.ErrorDescription
    - } - } -
    - - @if (Model?.View?.Error?.RequestId != null) - { -
    Request Id: @Model.View.Error.RequestId
    - } -
    -
    -
    diff --git a/src/IdentityServer/Pages/Home/Error/Index.cshtml.cs b/src/IdentityServer/Pages/Home/Error/Index.cshtml.cs deleted file mode 100644 index eae43a6cc..000000000 --- a/src/IdentityServer/Pages/Home/Error/Index.cshtml.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Duende.IdentityServer.Services; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace IdentityServerHost.Pages.Error; - -[AllowAnonymous] -[SecurityHeaders] -public class Index : PageModel -{ - private readonly IIdentityServerInteractionService _interaction; - private readonly IWebHostEnvironment _environment; - - public ViewModel View { get; set; } - - public Index(IIdentityServerInteractionService interaction, IWebHostEnvironment environment) - { - _interaction = interaction; - _environment = environment; - } - - public async Task OnGet(string errorId) - { - View = new ViewModel(); - - // retrieve error details from identityserver - var message = await _interaction.GetErrorContextAsync(errorId); - if (message != null) - { - View.Error = message; - - if (!_environment.IsDevelopment()) - { - // only show in development - message.ErrorDescription = null; - } - } - } -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Home/Error/ViewModel.cs b/src/IdentityServer/Pages/Home/Error/ViewModel.cs deleted file mode 100644 index 6a842b06b..000000000 --- a/src/IdentityServer/Pages/Home/Error/ViewModel.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -using Duende.IdentityServer.Models; - -namespace IdentityServerHost.Pages.Error; - -public class ViewModel -{ - public ViewModel() - { - } - - public ViewModel(string error) - { - Error = new ErrorMessage { Error = error }; - } - - public ErrorMessage Error { get; set; } -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Index.cshtml b/src/IdentityServer/Pages/Index.cshtml deleted file mode 100644 index 440a8fc60..000000000 --- a/src/IdentityServer/Pages/Index.cshtml +++ /dev/null @@ -1,32 +0,0 @@ -@page -@model IdentityServerHost.Pages.Home.Index - -
    -

    - - Welcome to Duende IdentityServer - (version @Model.Version) -

    - -
      -
    • - IdentityServer publishes a - discovery document - where you can find metadata and links to all the endpoints, key material, etc. -
    • -
    • - Click here to see the claims for your current session. -
    • -
    • - Click here to manage your stored grants. -
    • -
    • - Click here to view your pending CIBA login requests. -
    • -
    • - Here are links to the - source code repository, - and ready to use samples. -
    • -
    -
    diff --git a/src/IdentityServer/Pages/Index.cshtml.cs b/src/IdentityServer/Pages/Index.cshtml.cs deleted file mode 100644 index 1c8a457ed..000000000 --- a/src/IdentityServer/Pages/Index.cshtml.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Reflection; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace IdentityServerHost.Pages.Home; - -[AllowAnonymous] -public class Index : PageModel -{ - public string Version; - - public void OnGet() - { - Version = typeof(Duende.IdentityServer.Hosting.IdentityServerMiddleware).Assembly.GetCustomAttribute()?.InformationalVersion.Split('+').First(); - } -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Redirect/Index.cshtml b/src/IdentityServer/Pages/Redirect/Index.cshtml deleted file mode 100644 index f73b43013..000000000 --- a/src/IdentityServer/Pages/Redirect/Index.cshtml +++ /dev/null @@ -1,14 +0,0 @@ -@page -@model IdentityServerHost.Pages.Redirect.IndexModel -@{ -} - -
    -
    -

    You are now being returned to the application

    -

    Once complete, you may close this tab.

    -
    -
    - - - diff --git a/src/IdentityServer/Pages/Redirect/Index.cshtml.cs b/src/IdentityServer/Pages/Redirect/Index.cshtml.cs deleted file mode 100644 index 5fe9dee17..000000000 --- a/src/IdentityServer/Pages/Redirect/Index.cshtml.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace IdentityServerHost.Pages.Redirect; - -[AllowAnonymous] -public class IndexModel : PageModel -{ - public string RedirectUri { get; set; } - - public IActionResult OnGet(string redirectUri) - { - if (!Url.IsLocalUrl(redirectUri)) - { - return RedirectToPage("/Error/Index"); - } - - RedirectUri = redirectUri; - return Page(); - } -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/SecurityHeadersAttribute.cs b/src/IdentityServer/Pages/SecurityHeadersAttribute.cs deleted file mode 100644 index 090817245..000000000 --- a/src/IdentityServer/Pages/SecurityHeadersAttribute.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - - -using Microsoft.AspNetCore.Mvc.Filters; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace IdentityServerHost.Pages; - -public class SecurityHeadersAttribute : ActionFilterAttribute -{ - public override void OnResultExecuting(ResultExecutingContext context) - { - var result = context.Result; - if (result is PageResult) - { - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options - if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Type-Options")) - { - context.HttpContext.Response.Headers.Add("X-Content-Type-Options", "nosniff"); - } - - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options - if (!context.HttpContext.Response.Headers.ContainsKey("X-Frame-Options")) - { - context.HttpContext.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN"); - } - - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy - var csp = "default-src 'self'; object-src 'none'; frame-ancestors 'none'; sandbox allow-forms allow-same-origin allow-scripts; base-uri 'self';"; - // also consider adding upgrade-insecure-requests once you have HTTPS in place for production - //csp += "upgrade-insecure-requests;"; - // also an example if you need client images to be displayed from twitter - // csp += "img-src 'self' https://pbs.twimg.com;"; - - // once for standards compliant browsers - if (!context.HttpContext.Response.Headers.ContainsKey("Content-Security-Policy")) - { - context.HttpContext.Response.Headers.Add("Content-Security-Policy", csp); - } - // and once again for IE - if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Security-Policy")) - { - context.HttpContext.Response.Headers.Add("X-Content-Security-Policy", csp); - } - - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy - var referrer_policy = "no-referrer"; - if (!context.HttpContext.Response.Headers.ContainsKey("Referrer-Policy")) - { - context.HttpContext.Response.Headers.Add("Referrer-Policy", referrer_policy); - } - } - } -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/Shared/_Layout.cshtml b/src/IdentityServer/Pages/Shared/_Layout.cshtml deleted file mode 100644 index 067a0f027..000000000 --- a/src/IdentityServer/Pages/Shared/_Layout.cshtml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - Duende IdentityServer - - - - - - - - - - - -
    - @RenderBody() -
    - - - - - @RenderSection("scripts", required: false) - - diff --git a/src/IdentityServer/Pages/Shared/_Nav.cshtml b/src/IdentityServer/Pages/Shared/_Nav.cshtml deleted file mode 100644 index 405675bc6..000000000 --- a/src/IdentityServer/Pages/Shared/_Nav.cshtml +++ /dev/null @@ -1,32 +0,0 @@ -@using Duende.IdentityServer.Extensions -@{ - string name = null; - if (!true.Equals(ViewData["signed-out"])) - { - name = Context.User?.GetDisplayName(); - } -} - - diff --git a/src/IdentityServer/Pages/Shared/_ValidationSummary.cshtml b/src/IdentityServer/Pages/Shared/_ValidationSummary.cshtml deleted file mode 100644 index 674d68d87..000000000 --- a/src/IdentityServer/Pages/Shared/_ValidationSummary.cshtml +++ /dev/null @@ -1,7 +0,0 @@ -@if (ViewContext.ModelState.IsValid == false) -{ -
    - Error -
    -
    -} \ No newline at end of file diff --git a/src/IdentityServer/Pages/_ViewImports.cshtml b/src/IdentityServer/Pages/_ViewImports.cshtml deleted file mode 100644 index 7537d10a8..000000000 --- a/src/IdentityServer/Pages/_ViewImports.cshtml +++ /dev/null @@ -1,2 +0,0 @@ -@using IdentityServerHost.Pages -@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/src/IdentityServer/Pages/_ViewStart.cshtml b/src/IdentityServer/Pages/_ViewStart.cshtml deleted file mode 100644 index a5f10045d..000000000 --- a/src/IdentityServer/Pages/_ViewStart.cshtml +++ /dev/null @@ -1,3 +0,0 @@ -@{ - Layout = "_Layout"; -} diff --git a/src/IdentityServer/Program.cs b/src/IdentityServer/Program.cs deleted file mode 100644 index 816db5ae8..000000000 --- a/src/IdentityServer/Program.cs +++ /dev/null @@ -1,34 +0,0 @@ -using IdentityServer; -using Newtonsoft.Json; -using Serilog; - -Log.Logger = new LoggerConfiguration() - .WriteTo.Console() - .CreateBootstrapLogger(); - -Log.Information("Starting up"); - -try -{ - var builder = WebApplication.CreateBuilder(args); - - builder.Host.UseSerilog((ctx, lc) => lc - .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}") - .Enrich.FromLogContext() - .ReadFrom.Configuration(ctx.Configuration)); - - var app = builder - .ConfigureServices() - .ConfigurePipeline(); - - app.Run(); -} -catch (Exception ex) -{ - Log.Fatal(ex, "Unhandled exception"); -} -finally -{ - Log.Information("Shut down complete"); - Log.CloseAndFlush(); -} diff --git a/src/IdentityServer/Properties/launchSettings.json b/src/IdentityServer/Properties/launchSettings.json deleted file mode 100644 index 6f874f1ad..000000000 --- a/src/IdentityServer/Properties/launchSettings.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "profiles": { - "SelfHost": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "https://localhost:5001" - } - } -} \ No newline at end of file diff --git a/src/IdentityServer/SameSiteCookiesServiceCollectionExtensions.cs b/src/IdentityServer/SameSiteCookiesServiceCollectionExtensions.cs deleted file mode 100644 index 45b0145f0..000000000 --- a/src/IdentityServer/SameSiteCookiesServiceCollectionExtensions.cs +++ /dev/null @@ -1,129 +0,0 @@ -public static class SameSiteCookiesServiceCollectionExtensions -{ - /// - /// -1 defines the unspecified value, which tells ASPNET Core to NOT - /// send the SameSite attribute. With ASPNET Core 3.1 the - /// enum will have a definition for - /// Unspecified. - /// - private const SameSiteMode Unspecified = (SameSiteMode) (-1); - - /// - /// Configures a cookie policy to properly set the SameSite attribute - /// for Browsers that handle unknown values as Strict. Ensure that you - /// add the - /// into the pipeline before sending any cookies! - /// - /// - /// Minimum ASPNET Core Version required for this code: - /// - 2.1.14 - /// - 2.2.8 - /// - 3.0.1 - /// - 3.1.0-preview1 - /// Starting with version 80 of Chrome (to be released in February 2020) - /// cookies with NO SameSite attribute are treated as SameSite=Lax. - /// In order to always get the cookies send they need to be set to - /// SameSite=None. But since the current standard only defines Lax and - /// Strict as valid values there are some browsers that treat invalid - /// values as SameSite=Strict. We therefore need to check the browser - /// and either send SameSite=None or prevent the sending of SameSite=None. - /// Relevant links: - /// - https://tools.ietf.org/html/draft-west-first-party-cookies-07#section-4.1 - /// - https://tools.ietf.org/html/draft-west-cookie-incrementalism-00 - /// - https://www.chromium.org/updates/same-site - /// - https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/ - /// - https://bugs.webkit.org/show_bug.cgi?id=198181 - /// - /// The service collection to register into. - /// The modified . - public static IServiceCollection ConfigureNonBreakingSameSiteCookies(this IServiceCollection services) - { - services.Configure(options => - { - options.MinimumSameSitePolicy = Unspecified; - options.OnAppendCookie = cookieContext => - CheckSameSite(cookieContext.Context, cookieContext.CookieOptions); - options.OnDeleteCookie = cookieContext => - CheckSameSite(cookieContext.Context, cookieContext.CookieOptions); - }); - - return services; - } - - private static void CheckSameSite(HttpContext httpContext, CookieOptions options) - { - if (options.SameSite == SameSiteMode.None) - { - var userAgent = httpContext.Request.Headers["User-Agent"].ToString(); - - if (DisallowsSameSiteNone(userAgent)) - { - options.SameSite = Unspecified; - } - } - } - - /// - /// Checks if the UserAgent is known to interpret an unknown value as Strict. - /// For those the property should be - /// set to . - /// - /// - /// This code is taken from Microsoft: - /// https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/ - /// - /// The user agent string to check. - /// Whether the specified user agent (browser) accepts SameSite=None or not. - private static bool DisallowsSameSiteNone(string userAgent) - { - // Cover all iOS based browsers here. This includes: - // - Safari on iOS 12 for iPhone, iPod Touch, iPad - // - WkWebview on iOS 12 for iPhone, iPod Touch, iPad - // - Chrome on iOS 12 for iPhone, iPod Touch, iPad - // All of which are broken by SameSite=None, because they use the - // iOS networking stack. - // Notes from Thinktecture: - // Regarding https://caniuse.com/#search=samesite iOS versions lower - // than 12 are not supporting SameSite at all. Starting with version 13 - // unknown values are NOT treated as strict anymore. Therefore we only - // need to check version 12. - if (userAgent.Contains("CPU iPhone OS 12") - || userAgent.Contains("iPad; CPU OS 12")) - { - return true; - } - - // Cover Mac OS X based browsers that use the Mac OS networking stack. - // This includes: - // - Safari on Mac OS X. - // This does not include: - // - Chrome on Mac OS X - // because they do not use the Mac OS networking stack. - // Notes from Thinktecture: - // Regarding https://caniuse.com/#search=samesite MacOS X versions lower - // than 10.14 are not supporting SameSite at all. Starting with version - // 10.15 unknown values are NOT treated as strict anymore. Therefore we - // only need to check version 10.14. - if (userAgent.Contains("Safari") - && userAgent.Contains("Macintosh; Intel Mac OS X 10_14") - && userAgent.Contains("Version/")) - { - return true; - } - - // Cover Chrome 50-69, because some versions are broken by SameSite=None - // and none in this range require it. - // Note: this covers some pre-Chromium Edge versions, - // but pre-Chromium Edge does not require SameSite=None. - // Notes from Thinktecture: - // We can not validate this assumption, but we trust Microsofts - // evaluation. And overall not sending a SameSite value equals to the same - // behavior as SameSite=None for these old versions anyways. - if (userAgent.Contains("Chrome/5") || userAgent.Contains("Chrome/6") || userAgent.Contains("Chrome/10")) - { - return true; - } - - return false; - } -} diff --git a/src/IdentityServer/appsettings.json b/src/IdentityServer/appsettings.json deleted file mode 100644 index 61293ffc3..000000000 --- a/src/IdentityServer/appsettings.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "Serilog": { - "MinimumLevel": { - "Default": "Debug", - "Override": { - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information", - "Microsoft.AspNetCore.Authentication": "Debug", - "System": "Warning" - } - } - } -} \ No newline at end of file diff --git a/src/IdentityServer/global.json b/src/IdentityServer/global.json deleted file mode 100644 index af8797b1f..000000000 --- a/src/IdentityServer/global.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "sdk": { - "version": "6.0.201" - }, - "clr": { - "version": "6.0.3" - } -} - diff --git a/test/AssociationRegistry.Test/Acm.Api.IntegrationTests/When_retrieving_verenigingen_per_rijksregisternummer/Given_a_secured_api_endpoint.cs b/test/AssociationRegistry.Test/Acm.Api.IntegrationTests/When_retrieving_verenigingen_per_rijksregisternummer/Given_a_secured_api_endpoint.cs index 6b77c8ebe..ae2dbf065 100644 --- a/test/AssociationRegistry.Test/Acm.Api.IntegrationTests/When_retrieving_verenigingen_per_rijksregisternummer/Given_a_secured_api_endpoint.cs +++ b/test/AssociationRegistry.Test/Acm.Api.IntegrationTests/When_retrieving_verenigingen_per_rijksregisternummer/Given_a_secured_api_endpoint.cs @@ -16,7 +16,6 @@ public Given_a_secured_api_endpoint(VerenigingAcmApiFixture fixture) _testHelper = new AcmIntegrationTestHelper(fixture); } - //[Fact(Skip = "Ignore until we have an identity server image that we can use in the CI for association registry")] [Fact] public async Task When_authenticated_with_correct_scope_Then_we_get_a_successful_response() { @@ -25,7 +24,6 @@ public async Task When_authenticated_with_correct_scope_Then_we_get_a_successful response.Should().BeSuccessful(); } - //[Fact(Skip = "Ignore until we have an identity server image that we can use in the CI for association registry")] [Fact] public async Task When_authenticated_with_incorrect_scope_Then_we_get_an_unauthorized_response() { @@ -34,7 +32,6 @@ public async Task When_authenticated_with_incorrect_scope_Then_we_get_an_unautho response.Should().HaveStatusCode(HttpStatusCode.Unauthorized); } - //[Fact(Skip = "Ignore until we have an identity server image that we can use in the CI for association registry")] [Fact] public async Task When_not_authenticated_Then_we_get_an_unauthorized_response() { diff --git a/test/AssociationRegistry.Test/Admin.Api.IntegrationTests/EventStoreTests/When_storing_an_event/Given_An_Event.cs b/test/AssociationRegistry.Test/Admin.Api.IntegrationTests/EventStoreTests/When_storing_an_event/Given_An_Event.cs new file mode 100644 index 000000000..4d6ef75eb --- /dev/null +++ b/test/AssociationRegistry.Test/Admin.Api.IntegrationTests/EventStoreTests/When_storing_an_event/Given_An_Event.cs @@ -0,0 +1,42 @@ +namespace AssociationRegistry.Test.Admin.Api.IntegrationTests.EventStoreTests.When_storing_an_event; + +using Fixtures; +using FluentAssertions; +using Xunit; +using IEvent = AssociationRegistry.Admin.Api.Events.IEvent; + +[Collection(VerenigingDbCollection.Name)] +public class Given_An_Event +{ + [Fact] + public async Task Then_it_is_persisted_in_the_database() + { + // arrange + var streamId = Guid.NewGuid().ToString(); + var someEvent = new SomeEvent("some event"); + var eventStore = await VerenigingDbFixture.CreateEventStore(); + + // act + await eventStore.Save(streamId, someEvent); + + // assert + var events = await GetEventsFromDb(streamId); + events.Should().HaveCount(1); + var single = events.Single(); + single.Data.As().Should().BeEquivalentTo(someEvent); + // verify assumptions about marten + single.StreamKey.Should().Be(streamId); + single.EventType.Should().Be(); + } + + private static async Task> GetEventsFromDb(string streamId) + { + using var documentStore = await VerenigingDbFixture.CreateDocumentStore(); + await using var session = documentStore.LightweightSession(); + + return await session.Events.FetchStreamAsync(streamId); + } + + // ReSharper disable once NotAccessedPositionalProperty.Local + private record SomeEvent(string Name) : IEvent; +} diff --git a/test/AssociationRegistry.Test/Admin.Api.IntegrationTests/Fixtures/VerenigingDbCollection.cs b/test/AssociationRegistry.Test/Admin.Api.IntegrationTests/Fixtures/VerenigingDbCollection.cs new file mode 100644 index 000000000..d9d0e3857 --- /dev/null +++ b/test/AssociationRegistry.Test/Admin.Api.IntegrationTests/Fixtures/VerenigingDbCollection.cs @@ -0,0 +1,9 @@ +namespace AssociationRegistry.Test.Admin.Api.IntegrationTests.Fixtures; + +using Xunit; + +[CollectionDefinition(Name)] +public class VerenigingDbCollection : ICollectionFixture +{ + public const string Name = "Vereniging admin db collection"; +} diff --git a/test/AssociationRegistry.Test/Admin.Api.IntegrationTests/Fixtures/VerenigingDbFixture.cs b/test/AssociationRegistry.Test/Admin.Api.IntegrationTests/Fixtures/VerenigingDbFixture.cs new file mode 100644 index 000000000..bd3097baa --- /dev/null +++ b/test/AssociationRegistry.Test/Admin.Api.IntegrationTests/Fixtures/VerenigingDbFixture.cs @@ -0,0 +1,46 @@ +namespace AssociationRegistry.Test.Admin.Api.IntegrationTests.Fixtures; + +using AssociationRegistry.Admin.Api.Events; +using Helpers; +using Marten; +using Marten.Events; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +public class VerenigingDbFixture:IDisposable +{ + public static async Task CreateEventStore() + => new(await CreateDocumentStore()); + + public static async Task CreateDocumentStore() + { + var documentStore = DocumentStore.For( + opts => + { + opts.Connection(GetConnectionString()); + opts.Events.StreamIdentity = StreamIdentity.AsString; + }); + await WaitFor.PostGreSQLToBecomeAvailable(documentStore, LoggerFactory.Create(opt => opt.AddConsole()).CreateLogger("waitForPostgresTestLogger")); + return documentStore; + } + + private static string GetConnectionString() + { + var currentDirectory = Directory.GetCurrentDirectory(); + Console.WriteLine(currentDirectory); + var builder = new ConfigurationBuilder() + .SetBasePath(currentDirectory) + .AddJsonFile("appsettings.json", optional: true) + .AddJsonFile($"appsettings.{Environment.MachineName.ToLowerInvariant()}.json", optional: true); + + var configurationRoot = builder.Build(); + var connectionString = configurationRoot + .GetValue("eventstore_connectionstring"); + return connectionString; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + } +} diff --git a/test/AssociationRegistry.Test/Admin.Api.IntegrationTests/Fixtures/VerenigingPublicApiCollection.cs b/test/AssociationRegistry.Test/Admin.Api.IntegrationTests/Fixtures/VerenigingPublicApiCollection.cs new file mode 100644 index 000000000..ccee74864 --- /dev/null +++ b/test/AssociationRegistry.Test/Admin.Api.IntegrationTests/Fixtures/VerenigingPublicApiCollection.cs @@ -0,0 +1,9 @@ +namespace AssociationRegistry.Test.Admin.Api.IntegrationTests.Fixtures; + +using Xunit; + +[CollectionDefinition(Name)] +public class VerenigingAdminApiCollection : ICollectionFixture +{ + public const string Name = "Vereniging admin api collection"; +} diff --git a/test/AssociationRegistry.Test/Admin.Api.IntegrationTests/Fixtures/VerenigingPublicApiFixture.cs b/test/AssociationRegistry.Test/Admin.Api.IntegrationTests/Fixtures/VerenigingPublicApiFixture.cs new file mode 100644 index 000000000..476352edd --- /dev/null +++ b/test/AssociationRegistry.Test/Admin.Api.IntegrationTests/Fixtures/VerenigingPublicApiFixture.cs @@ -0,0 +1,58 @@ +namespace AssociationRegistry.Test.Admin.Api.IntegrationTests.Fixtures; + +using System.Reflection; +using AssociationRegistry.Admin.Api; +using Helpers; +using Marten; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +public class VerenigingAdminApiFixture : IDisposable +{ + public HttpClient HttpClient { get; private set; } + public IDocumentStore DocumentStore { get; private set; } + private readonly TestServer _testServer; + + public VerenigingAdminApiFixture() + { + var maybeRootDirectory = Directory + .GetParent(typeof(Startup).GetTypeInfo().Assembly.Location)?.Parent?.Parent?.Parent?.FullName; + if (maybeRootDirectory is not { } rootDirectory) + throw new NullReferenceException("Root directory cannot be null"); + + Directory.SetCurrentDirectory(rootDirectory); + + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: true) + .AddJsonFile($"appsettings.{Environment.MachineName.ToLowerInvariant()}.json", optional: true); + + var configurationRoot = builder.Build(); + + IWebHostBuilder hostBuilder = new WebHostBuilder(); + + hostBuilder.UseConfiguration(configurationRoot); + hostBuilder.UseStartup(); + + hostBuilder.ConfigureLogging(loggingBuilder => loggingBuilder.AddConsole()); + + hostBuilder.UseTestServer(); + + _testServer = new TestServer(hostBuilder); + + HttpClient = _testServer.CreateClient(); + DocumentStore = ((IDocumentStore?)_testServer.Services.GetService(typeof(IDocumentStore)))!; + + WaitFor.PostGreSQLToBecomeAvailable(DocumentStore, LoggerFactory.Create(opt => opt.AddConsole()).CreateLogger("waitForPostgresTestLogger")) + .GetAwaiter().GetResult(); + } + + public void Dispose() + { + GC.SuppressFinalize(this); + HttpClient.Dispose(); + _testServer.Dispose(); + } +} diff --git a/test/AssociationRegistry.Test/Admin.Api.IntegrationTests/When_posting_a_new_vereniging/Given_An_Api.cs b/test/AssociationRegistry.Test/Admin.Api.IntegrationTests/When_posting_a_new_vereniging/Given_An_Api.cs new file mode 100644 index 000000000..5a4282d15 --- /dev/null +++ b/test/AssociationRegistry.Test/Admin.Api.IntegrationTests/When_posting_a_new_vereniging/Given_An_Api.cs @@ -0,0 +1,66 @@ +namespace AssociationRegistry.Test.Admin.Api.IntegrationTests.When_posting_a_new_vereniging; + +using System.Net; +using System.Text; +using AssociationRegistry.Admin.Api.Verenigingen; +using AutoFixture; +using Fixtures; +using FluentAssertions; +using Xunit; + +[Collection(VerenigingAdminApiCollection.Name)] +public class Given_An_Api : IDisposable +{ + private readonly VerenigingAdminApiFixture _apiFixture; + private readonly Fixture _fixture; + + public Given_An_Api(VerenigingAdminApiFixture apiFixture) + { + _apiFixture = apiFixture; + _fixture = new Fixture(); + } + + [Fact] + public async Task Given_an_empty_Naam_Then_it_returns_a_xxx() + { + var content = GetContent(string.Empty); + var response = await _apiFixture.HttpClient.PostAsync("/v1/verenigingen", content); + response.StatusCode.Should().Be(HttpStatusCode.Accepted); + } + + [Fact] + public async Task Then_it_returns_an_accepted_response() + { + var content = GetContent(_fixture.Create()); + var response = await _apiFixture.HttpClient.PostAsync("/v1/verenigingen", content); + response.StatusCode.Should().Be(HttpStatusCode.Accepted); + } + + [Fact] + public async Task Then_it_saves_the_events() + { + var expectedNaam = _fixture.Create(); + var content = GetContent(expectedNaam); + await _apiFixture.HttpClient.PostAsync("/v1/verenigingen", content); + + _apiFixture.DocumentStore.LightweightSession().Events.QueryRawEventDataOnly() + .Where(e => e.Naam == expectedNaam) + .Should().HaveCount(1); + } + + private StringContent GetContent(string naam) + => new( + GetJsonBody(naam), + Encoding.UTF8, + "application/json"); + + private string GetJsonBody(string naam) + => GetType() + .GetAssociatedResourceJson($"{nameof(Given_An_Api)}_{nameof(Then_it_returns_an_accepted_response)}") + .Replace("{{vereniging.naam}}", naam); + + public void Dispose() + { + GC.SuppressFinalize(this); + } +} diff --git a/test/AssociationRegistry.Test/Admin.Api.IntegrationTests/When_posting_a_new_vereniging/Given_An_Api_Then_it_returns_an_accepted_response.json b/test/AssociationRegistry.Test/Admin.Api.IntegrationTests/When_posting_a_new_vereniging/Given_An_Api_Then_it_returns_an_accepted_response.json new file mode 100644 index 000000000..29c175f03 --- /dev/null +++ b/test/AssociationRegistry.Test/Admin.Api.IntegrationTests/When_posting_a_new_vereniging/Given_An_Api_Then_it_returns_an_accepted_response.json @@ -0,0 +1,3 @@ +{ + "naam": "{{vereniging.naam}}" +} diff --git a/test/AssociationRegistry.Test/Admin.Api.UnitTests/CommandHandlerTests/When_a_CreateVerenigingCommand_is_received/Given_A_CreateVerenigingCommandHandler.cs b/test/AssociationRegistry.Test/Admin.Api.UnitTests/CommandHandlerTests/When_a_CreateVerenigingCommand_is_received/Given_A_CreateVerenigingCommandHandler.cs new file mode 100644 index 000000000..d8f5ac46b --- /dev/null +++ b/test/AssociationRegistry.Test/Admin.Api.UnitTests/CommandHandlerTests/When_a_CreateVerenigingCommand_is_received/Given_A_CreateVerenigingCommandHandler.cs @@ -0,0 +1,38 @@ +namespace AssociationRegistry.Test.Admin.Api.UnitTests.CommandHandlerTests.When_a_CreateVerenigingCommand_is_received; + +using AssociationRegistry.Admin.Api.Verenigingen; +using FluentAssertions; +using Xunit; + +public class VerenigingRepositoryMock : IVerenigingsRepository +{ + public record Invocation(Vereniging Vereniging); + + public readonly List Invocations = new(); + + public async Task Save(Vereniging vereniging) + { + Invocations.Add(new Invocation(vereniging)); + await Task.CompletedTask; + } +} + +public class Given_A_CreateVerenigingCommandHandler +{ + [Fact] + public async Task Then_a_new_vereniging_is_saved_in_the_repository() + { + var vNummerService = new SequentialVCodeService(); + var verenigingsRepository = new VerenigingRepositoryMock(); + + var handler = new CreateVerenigingCommandHandler(verenigingsRepository, vNummerService); + var createVerenigingCommand = new CommandEnvelope(new CreateVerenigingCommand("naam1")); + + await handler.Handle(createVerenigingCommand, CancellationToken.None); + + var invocation = verenigingsRepository.Invocations.Single(); + invocation.Vereniging.VCode.Should().Be(SequentialVCodeService.GetLast()); + var events = invocation.Vereniging.Events; + events.Single().Should().BeEquivalentTo(new VerenigingWerdGeregistreerd(SequentialVCodeService.GetLast(), "naam1")); + } +} diff --git a/test/AssociationRegistry.Test/Admin.Api.UnitTests/VerenigingsRepositoryTests/When_saving_a_vereniging/Given_A_New_Vereniging.cs b/test/AssociationRegistry.Test/Admin.Api.UnitTests/VerenigingsRepositoryTests/When_saving_a_vereniging/Given_A_New_Vereniging.cs new file mode 100644 index 000000000..3c96a484f --- /dev/null +++ b/test/AssociationRegistry.Test/Admin.Api.UnitTests/VerenigingsRepositoryTests/When_saving_a_vereniging/Given_A_New_Vereniging.cs @@ -0,0 +1,41 @@ +namespace AssociationRegistry.Test.Admin.Api.UnitTests.VerenigingsRepositoryTests.When_saving_a_vereniging; + +using AssociationRegistry.Admin.Api.Events; +using AssociationRegistry.Admin.Api.Verenigingen; +using FluentAssertions; +using Xunit; + +public class EventStoreMock : IEventStore +{ + public record Invocation(string AggregateId, IEvent[] Events); + + public readonly List Invocations = new(); + + public async Task Save(string aggregateId, params IEvent[] events) + { + Invocations.Add(new Invocation(aggregateId, events)); + await Task.CompletedTask; + } +} + +public class Given_A_New_Vereniging +{ + [Fact] + public async Task Then_the_verenigingcreatedevent_is_stored_in_the_EventStore() + { + var eventStore = new EventStoreMock(); + + var repo = new VerenigingsRepository(eventStore); + + var vCode = "V000001"; + var naam = "Vereniging 1"; + var vereniging = new Vereniging(vCode, naam); + + await repo.Save(vereniging); + + eventStore.Invocations.Should().HaveCount(1); + var invocation = eventStore.Invocations.Single(); + invocation.AggregateId.Should().Be(vCode); + invocation.Events.Single().Should().BeEquivalentTo(new VerenigingWerdGeregistreerd(vCode, naam)); + } +} diff --git a/test/AssociationRegistry.Test/AssociationRegistry.Test.csproj b/test/AssociationRegistry.Test/AssociationRegistry.Test.csproj index b85985d69..60e5312b0 100644 --- a/test/AssociationRegistry.Test/AssociationRegistry.Test.csproj +++ b/test/AssociationRegistry.Test/AssociationRegistry.Test.csproj @@ -7,12 +7,19 @@ + + + + + + Always + diff --git a/test/AssociationRegistry.Test/Helpers/WaitFor.cs b/test/AssociationRegistry.Test/Helpers/WaitFor.cs new file mode 100644 index 000000000..8fb0a8c6c --- /dev/null +++ b/test/AssociationRegistry.Test/Helpers/WaitFor.cs @@ -0,0 +1,50 @@ +namespace AssociationRegistry.Test.Helpers; + +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Marten; +using Microsoft.Extensions.Logging; + +public static class WaitFor +{ + public static async Task PostGreSQLToBecomeAvailable(IDocumentStore store, ILogger logger, CancellationToken cancellationToken = default) + { + if (store is DocumentStore) + { + var watch = Stopwatch.StartNew(); + var tryCount = 0; + var exit = false; + while (!exit) + { + try + { + if (logger.IsEnabled(LogLevel.Information)) + { + logger.LogInformation("Waiting until PostGreSql store becomes available ... ({WatchElapsed})", watch.Elapsed); + } + + await store.LightweightSession().Events.QueryAllRawEvents().Take(1).SingleOrDefaultAsync(cancellationToken); + exit = true; + } + catch (Exception exception) + { + if (tryCount >= 5) + exit = true; + + tryCount++; + if (logger.IsEnabled(LogLevel.Warning)) + { + logger.LogWarning(exception, "Encountered an exception while waiting for PostGreSql store to become available"); + } + + await Task.Delay(1000, cancellationToken); + } + } + } + } +} + + diff --git a/test/AssociationRegistry.Test/Public.Api.IntegrationTests/Fixtures/VerenigingPublicApiFixture.cs b/test/AssociationRegistry.Test/Public.Api.IntegrationTests/Fixtures/VerenigingPublicApiFixture.cs index 24d2ae20f..299d9aa72 100644 --- a/test/AssociationRegistry.Test/Public.Api.IntegrationTests/Fixtures/VerenigingPublicApiFixture.cs +++ b/test/AssociationRegistry.Test/Public.Api.IntegrationTests/Fixtures/VerenigingPublicApiFixture.cs @@ -7,11 +7,6 @@ namespace AssociationRegistry.Test.Public.Api.IntegrationTests.Fixtures; -using AssociationRegistry.Public.Api.S3; -using Be.Vlaanderen.Basisregisters.BlobStore; -using Microsoft.Extensions.DependencyInjection; -using UnitTests; - public class VerenigingPublicApiFixture : IDisposable { public HttpClient HttpClient { get; private set; } diff --git a/test/AssociationRegistry.Test/Public.Api.IntegrationTests/When_retrieving_a_detail_of_a_vereniging/Given_a_vereniging_with_minimal_fields.cs b/test/AssociationRegistry.Test/Public.Api.IntegrationTests/When_retrieving_a_detail_of_a_vereniging/Given_a_vereniging_with_minimal_fields.cs index 968ce8f86..403ec8cca 100644 --- a/test/AssociationRegistry.Test/Public.Api.IntegrationTests/When_retrieving_a_detail_of_a_vereniging/Given_a_vereniging_with_minimal_fields.cs +++ b/test/AssociationRegistry.Test/Public.Api.IntegrationTests/When_retrieving_a_detail_of_a_vereniging/Given_a_vereniging_with_minimal_fields.cs @@ -1,10 +1,8 @@ -using AssociationRegistry.Public.Api; -using AssociationRegistry.Test.Public.Api.IntegrationTests.Fixtures; -using FluentAssertions; -using Xunit; - namespace AssociationRegistry.Test.Public.Api.IntegrationTests.When_retrieving_a_detail_of_a_vereniging; +using Fixtures; +using FluentAssertions; +using Xunit; using AssociationRegistry.Public.Api.Constants; [Collection(VerenigingPublicApiCollection.Name)] diff --git a/test/AssociationRegistry.Test/Public.Api.IntegrationTests/When_retrieving_verenigingen_without_explicit_limit/Given_72_verenigingen.cs b/test/AssociationRegistry.Test/Public.Api.IntegrationTests/When_retrieving_verenigingen_without_explicit_limit/Given_72_verenigingen.cs index 8d15c83e1..2d6543a09 100644 --- a/test/AssociationRegistry.Test/Public.Api.IntegrationTests/When_retrieving_verenigingen_without_explicit_limit/Given_72_verenigingen.cs +++ b/test/AssociationRegistry.Test/Public.Api.IntegrationTests/When_retrieving_verenigingen_without_explicit_limit/Given_72_verenigingen.cs @@ -18,12 +18,12 @@ public Given_72_verenigingen(VerenigingPublicApiFixtureWith72Verenigingen fixtur // TODO To implement when DB is Connected public async Task Then_verenigingen_0_49_are_returned() { - var responseMessage = await _httpClient.GetAsync("/v1/verenigingen"); - var content = await responseMessage.Content.ReadAsStringAsync(); + //var responseMessage = await _httpClient.GetAsync("/v1/verenigingen"); + //var content = await responseMessage.Content.ReadAsStringAsync(); // var goldenMaster = GetType().GetAssociatedResourceJson( // $"{nameof(Given_72_verenigingen)}_{nameof(Then_verenigingen_0_49_are_returned)}"); - var deserializedContent = JToken.Parse(content); + //var deserializedContent = JToken.Parse(content); // var deserializedGoldenMaster = JToken.Parse(goldenMaster); //deserializedContent.Should().BeEquivalentTo(deserializedGoldenMaster); diff --git a/test/AssociationRegistry.Test/Public.Api.IntegrationTests/When_retrieving_verenigingen_without_explicit_limit/Given_an_api.cs b/test/AssociationRegistry.Test/Public.Api.IntegrationTests/When_retrieving_verenigingen_without_explicit_limit/Given_an_api.cs index 91b0c50e7..a95494136 100644 --- a/test/AssociationRegistry.Test/Public.Api.IntegrationTests/When_retrieving_verenigingen_without_explicit_limit/Given_an_api.cs +++ b/test/AssociationRegistry.Test/Public.Api.IntegrationTests/When_retrieving_verenigingen_without_explicit_limit/Given_an_api.cs @@ -1,11 +1,9 @@ -using AssociationRegistry.Public.Api; -using AssociationRegistry.Test.Public.Api.IntegrationTests.Fixtures; +namespace AssociationRegistry.Test.Public.Api.IntegrationTests.When_retrieving_verenigingen_without_explicit_limit; + +using Fixtures; using FluentAssertions; using Newtonsoft.Json.Linq; using Xunit; - -namespace AssociationRegistry.Test.Public.Api.IntegrationTests.When_retrieving_verenigingen_without_explicit_limit; - using AssociationRegistry.Public.Api.Constants; [Collection(VerenigingPublicApiCollection.Name)] diff --git a/test/AssociationRegistry.Test/Public.Api.UnitTests/When_retrieving_a_vereniging_detail/Given_no_verenigingen.cs b/test/AssociationRegistry.Test/Public.Api.UnitTests/When_retrieving_a_vereniging_detail/Given_no_verenigingen.cs index 0c2315922..79dfc665b 100644 --- a/test/AssociationRegistry.Test/Public.Api.UnitTests/When_retrieving_a_vereniging_detail/Given_no_verenigingen.cs +++ b/test/AssociationRegistry.Test/Public.Api.UnitTests/When_retrieving_a_vereniging_detail/Given_no_verenigingen.cs @@ -3,11 +3,10 @@ using AssociationRegistry.Public.Api; using AssociationRegistry.Public.Api.DetailVerenigingen; using AssociationRegistry.Public.Api.ListVerenigingen; -using AssociationRegistry.Test.Stubs; +using Stubs; using AutoFixture; using FluentAssertions; using Microsoft.AspNetCore.Mvc; -using Newtonsoft.Json; using Xunit; public class Given_no_verenigingen diff --git a/test/AssociationRegistry.Test/appsettings.json b/test/AssociationRegistry.Test/appsettings.json index b901a0349..4f12d396c 100644 --- a/test/AssociationRegistry.Test/appsettings.json +++ b/test/AssociationRegistry.Test/appsettings.json @@ -30,5 +30,6 @@ "ClientSecret": "a_very=Secr3t*Key", "Authority": "http://localhost:5051", "IntrospectionEndpoint": "http://localhost:5051/connect/introspect" - } + }, + "eventstore_connectionstring": "host=localhost;database=verenigingsregister;password=root;username=root" } diff --git a/test/AssociationRegistry.Test/paket.references b/test/AssociationRegistry.Test/paket.references index 4504b5b8f..4e1c6430f 100644 --- a/test/AssociationRegistry.Test/paket.references +++ b/test/AssociationRegistry.Test/paket.references @@ -14,3 +14,5 @@ Be.Vlaanderen.Basisregisters.AggregateSource.Testing.SqlStreamStore.Autofac Be.Vlaanderen.Basisregisters.AggregateSource.Testing.Xunit Be.Vlaanderen.Basisregisters.EventHandling.Autofac + +Marten