From ef09208017daac52348a90be8bc58bb4db66bb22 Mon Sep 17 00:00:00 2001 From: Olivier Lefebvre Date: Sat, 18 Nov 2023 17:42:48 +0100 Subject: [PATCH] 8.0.0 preview (#1136) BREAKING CHANGE : .Net 8 --- .github/workflows/check-dependencies.yml | 7 +- .github/workflows/codeql-analysis-cs.yml | 11 +- .github/workflows/codeql-analysis-js.yml | 6 +- .github/workflows/codesee-arch-diagram.yml | 2 +- .github/workflows/deploy-doc.yml | 4 +- .github/workflows/deploy-to-heroku.yml | 38 + .github/workflows/docker.yml | 16 +- THIRD-PARTY-NOTICES | 45 + TheIdServer.sln | 128 +- appveyor.yml | 10 +- build.ps1 | 8 +- bump-ms-dependencies.ps1 | 16 +- bump-system-dependencies.ps1 | 15 +- doc/ADMINAPP.md | 179 ++ doc/INTRODUCTION.md | 11 + doc/SERVER.md | 1093 ++++++++++++ doc/SETUP.md | 9 + .../Aguacongas.TheIdServer.ApiSample.csproj | 8 +- .../Aguacongas.TheIdServer.CibaSample.csproj | 2 +- ....TheIdServer.ClientCredentialSample.csproj | 2 +- ...s.TheIdServer.CustomClaimsProviders.csproj | 6 +- ...congas.TheIdServer.DeviceFlowSample.csproj | 2 +- .../Aguacongas.TheIdServer.MvcClient.csproj | 8 +- ...ngas.TheIdServer.WsFederationSample.csproj | 8 +- ...acongas.TheIdentityServer.SpaSample.csproj | 12 +- sample/DPoP/Api/ApiHost.csproj | 6 +- sample/DPoP/Api/DPoP/DPoPJwtBearerEvents.cs | 4 +- sample/DPoP/Api/DPoP/DPoPProofValidator.cs | 10 +- .../ClientCredentials.csproj | 4 +- sample/DPoP/WebClient/WebClient.csproj | 6 +- .../Aguacongas.TheIdServer.Api.csproj | 32 +- .../Aguacongas.TheIdServer.Private.csproj | 52 +- .../Account/InvalidReturnUrlException.cs | 5 - .../Aguacongas.TheIdServer.Public.csproj | 42 +- .../Account/InvalidReturnUrlException.cs | 7 +- ...uacongas.TheIdServer.Authentication.csproj | 4 +- .../Aguacongas.TheIdServer.BlazorApp.csproj | 57 +- .../App.razor.cs | 21 +- .../Dockerfile | 13 +- .../README.md | 176 +- .../Roots.xml | 6 + .../wwwroot/appsettings.heroku-duende.json | 2 +- .../wwwroot/appsettings.heroku.json | 2 +- .../wwwroot/appsettings.json | 2 +- .../Aguacongas.TheIdServer.Duende.csproj | 16 +- .../Pages/Account/LoginWith2fa.cshtml | 14 +- .../Pages/Account/LoginWith2fa.cshtml.cs | 12 +- .../Identity/Pages/Account/Manage/Ciba.cshtml | 2 +- .../Pages/Account/Manage/Ciba.cshtml.cs | 6 +- .../Pages/Account/Manage/Grants.cshtml | 8 +- .../Pages/Account/Manage/Grants.cshtml.cs | 14 +- .../Pages/Account/Manage/Sessions.cshtml.cs | 14 +- src/Aguacongas.TheIdServer.Duende/Config.cs | 6 +- src/Aguacongas.TheIdServer.Duende/Dockerfile | 95 +- .../ApplicationBuilderExtensions.cs | 111 +- .../IdentityServerBuilderExtensions.cs | 12 +- .../Extensions/ServiceCollectionExtensions.cs | 41 +- src/Aguacongas.TheIdServer.Duende/Program.cs | 10 +- .../Quickstart/Account/AccountController.cs | 44 +- .../Quickstart/Account/ExternalController.cs | 37 +- .../Quickstart/Account/ExternalProvider.cs | 4 +- .../Account/InvalidReturnUrlException.cs | 9 +- .../Quickstart/Account/LoggedOutViewModel.cs | 10 +- .../Quickstart/Account/LoginInputModel.cs | 6 +- .../Quickstart/Account/LoginViewModel.cs | 6 +- .../Quickstart/Account/LogoutInputModel.cs | 2 +- .../Quickstart/Account/RedirectViewModel.cs | 2 +- .../Quickstart/Ciba/CibaController.cs | 20 +- .../Quickstart/Ciba/InputModel.cs | 8 +- .../Quickstart/Ciba/ResourceViewModel.cs | 4 +- .../Quickstart/Ciba/ScopeViewModel.cs | 10 +- .../Quickstart/Ciba/ViewModel.cs | 18 +- .../Quickstart/Consent/ConsentController.cs | 18 +- .../Quickstart/Consent/ConsentInputModel.cs | 8 +- .../Quickstart/Consent/ConsentViewModel.cs | 14 +- .../Consent/ProcessConsentResult.cs | 8 +- .../Quickstart/Consent/ScopeViewModel.cs | 6 +- .../Device/DeviceAuthorizationInputModel.cs | 2 +- .../Device/DeviceAuthorizationViewModel.cs | 2 +- .../Quickstart/Device/DeviceController.cs | 36 +- .../Diagnostics/DiagnosticsController.cs | 4 +- .../Diagnostics/DiagnosticsViewModel.cs | 8 +- .../Quickstart/Extensions.cs | 5 +- .../Quickstart/Home/ErrorViewModel.cs | 2 +- .../Quickstart/SecurityHeadersAttribute.cs | 10 +- src/Aguacongas.TheIdServer.Duende/README.md | 1005 +----------- src/Aguacongas.TheIdServer.Duende/SeedData.cs | 44 +- .../Views/Account/LoggedOut.cshtml | 2 +- .../Views/Account/Login.cshtml | 4 +- .../Views/Ciba/Consent.cshtml | 8 +- .../Views/Consent/Index.cshtml | 6 +- .../Views/Device/UserCodeConfirmation.cshtml | 8 +- .../Views/Diagnostics/Index.cshtml | 8 +- .../Views/Shared/Error.cshtml | 4 +- .../Views/Shared/_Layout.cshtml | 8 +- .../Views/Shared/_LoginPartial.cshtml | 2 +- .../appsettings.json | 28 +- .../Aguacongas.TheIdServer.Identity.csproj | 4 +- .../Extensions/IdentityBuilderExtensions.cs | 3 - .../Data/WeatherForecast.cs | 13 - .../Data/WeatherForecastService.cs | 20 - .../Pages/Counter.razor | 16 - .../Pages/FetchData.razor | 46 - .../Pages/Index.razor | 7 - .../Platforms/Tizen/Main.cs | 17 - .../Platforms/Tizen/tizen-manifest.xml | 15 - .../Resources/AppIcon/appicon.svg | 4 - .../Resources/AppIcon/appiconfg.svg | 8 - .../Resources/Raw/AboutAssets.txt | 15 - .../Resources/Splash/splash.svg | 8 - .../Shared/MainLayout.razor.css | 75 - .../Shared/NavMenu.razor | 39 - .../Shared/NavMenu.razor.css | 62 - .../Shared/SurveyPrompt.razor | 16 - .../.config/dotnet-tools.json | 12 - .../Aguacongas.TheIdServer.csproj | 50 +- .../Manage/DownloadPersonalData.cshtml.cs | 2 +- src/Aguacongas.TheIdServer/Dockerfile-windows | 67 - .../TracerProviderBuilderExtensions.cs | 1 - .../Models/SiteOptions.cs | 7 +- .../Options/OpenTelemetry/RedisOptions.cs | 2 +- src/Aguacongas.TheIdServer/Pages/_Host.cshtml | 6 +- ...as.TheIdServer.BlazorApp.Components.csproj | 11 +- .../Form/AuthorizeNumber.razor | 13 + .../Form/AuthorizeNumber.razor.cs | 20 + ...heIdServer.BlazorApp.Infrastructure.csproj | 14 +- .../Models/ApiAuthentication.cs | 1 - .../Models/OpenIdConnectConfiguration.cs | 231 +++ ...gas.TheIdServer.BlazorApp.Pages.Api.csproj | 4 +- ...heIdServer.BlazorApp.Pages.ApiScope.csproj | 4 +- ...eIdServer.BlazorApp.Pages.ApiScopes.csproj | 4 +- ...as.TheIdServer.BlazorApp.Pages.Apis.csproj | 4 +- ....TheIdServer.BlazorApp.Pages.Client.csproj | 4 +- .../Components/ClientTokens.razor | 9 + .../Components/ClientTokens.razor.cs | 2 - ...TheIdServer.BlazorApp.Pages.Clients.csproj | 4 +- ...TheIdServer.BlazorApp.Pages.Culture.csproj | 4 +- ...heIdServer.BlazorApp.Pages.Cultures.csproj | 4 +- ...er.BlazorApp.Pages.ExternalProvider.csproj | 4 +- ...r.BlazorApp.Pages.ExternalProviders.csproj | 4 +- ...IdServer.BlazorApp.Pages.Identities.csproj | 4 +- ...heIdServer.BlazorApp.Pages.Identity.csproj | 4 +- ....TheIdServer.BlazorApp.Pages.Import.csproj | 4 +- ...as.TheIdServer.BlazorApp.Pages.Keys.csproj | 4 +- ...rver.BlazorApp.Pages.RelyingParties.csproj | 4 +- ...Server.BlazorApp.Pages.RelyingParty.csproj | 4 +- .../RelyingParty.razor | 6 +- .../RelyingParty.razor.cs | 5 +- ...as.TheIdServer.BlazorApp.Pages.Role.csproj | 4 +- ...s.TheIdServer.BlazorApp.Pages.Roles.csproj | 4 +- ...heIdServer.BlazorApp.Pages.Settings.csproj | 6 +- ...as.TheIdServer.BlazorApp.Pages.User.csproj | 4 +- ...s.TheIdServer.BlazorApp.Pages.Users.csproj | 4 +- ...acongas.TheIdServer.BlazorApp.Pages.csproj | 4 +- ...erver.Identity.Argon2PasswordHasher.csproj | 41 + .../Argon2Id.cs | 31 + .../Argon2PasswordHasher.cs | 67 + .../Argon2PasswordHasherOptions.cs | 25 + .../AssemblyInfo.cs | 3 + .../Extensions/IdentityBuilderExtensions.cs | 37 + .../Extensions/ServiceCollectionExtensions.cs | 39 + .../IArgon2Id.cs | 28 + .../README.md | 34 + .../THIRD-PARTY-NOTICES | 36 + .../package-icon.png | Bin 0 -> 12354 bytes .../vcruntime140.dll | Bin 0 -> 119192 bytes ...erver.Identity.BcryptPasswordHasher.csproj | 39 + .../BcryptPasswordHasher.cs | 64 + .../BcryptPasswordHasherOptions.cs | 17 + .../Extensions/IdentityBuilderExtensions.cs | 37 + .../Extensions/ServiceCollectionExtensions.cs | 37 + .../README.md | 32 + .../THIRD-PARTY-NOTICES | 29 + .../package-icon.png | Bin 0 -> 12354 bytes ...erver.Identity.ScryptPasswordHasher.csproj | 35 + .../Extensions/IdentityBuilderExtensions.cs | 37 + .../Extensions/ServiceCollectionExtensions.cs | 58 + .../README.md | 36 + .../ScryptPasswordHasher.cs | 91 + .../ScryptPasswordHasherOptions.cs | 28 + .../package-icon.png | Bin 0 -> 12354 bytes ...rver.Identity.UpgradePasswordHasher.csproj | 34 + .../Extensions/IdentityBuilderExtensions.cs | 37 + .../Extensions/ServiceCollectionExtensions.cs | 46 + .../README.md | 48 + .../UpgradePasswordHasher.cs | 61 + .../UpgradePasswordHasherOptions.cs | 22 + .../package-icon.png | Bin 0 -> 12354 bytes .../AdminStore.cs | 16 +- ...gas.IdentityServer.Admin.Http.Store.csproj | 12 +- .../HttpStoreBase.cs | 32 +- .../ProblemException.cs | 10 +- .../Aguacongas.IdentityServer.Admin.csproj | 97 +- .../CertificateController.cs | 3 +- .../ClaimsProviderController.cs | 3 +- .../Configuration/ApiPath.cs | 12 + .../Configuration/ApiRouteAttribute.cs | 49 + .../EmailController.cs | 3 +- .../ExternalProviderKindController.cs | 3 +- .../GenericApiController.cs | 3 +- .../GenericKeyController.cs | 3 +- .../ImportController.cs | 3 +- .../Models/RegistrationException.cs | 12 +- .../WindowsAuthentication/WindowsHandler.cs | 6 +- .../TokenController.cs | 3 +- .../WelcomeFragmentController.cs | 3 +- .../AdminStore.cs | 5 +- ...dentityServer.EntityFramework.Store.csproj | 4 +- .../OperationalDbContext.cs | 10 +- ...guacongas.IdentityServer.Http.Store.csproj | 6 +- ...acongas.IdentityServer.KeysRotation.csproj | 14 +- .../EntityFrameworkCoreXmlRepository.cs | 7 +- .../AdminStores/AdminStore.cs | 5 +- ...congas.IdentityServer.MongoDb.Store.csproj | 2 +- ...congas.IdentityServer.RavenDb.Store.csproj | 4 +- .../Aguacongas.IdentityServer.Store.csproj | 2 +- .../Entity/Client.cs | 10 + .../Entity/PushedAuthorizationRequest.cs | 36 + ...acongas.IdentityServer.WsFederation.csproj | 2 +- .../Aguacongas.IdentityServer.csproj | 10 +- .../IdentityException.cs | 8 +- ...acongas.IdentityServer.Admin.Duende.csproj | 4 +- ...ointRoutingApplicationBuilderExtensions.cs | 107 +- .../Models/ClientRegisteration.cs | 8 +- .../RegisterController.cs | 3 +- .../Services/RegisterClientService.cs | 6 +- .../Aguacongas.IdentityServer.Duende.csproj | 4 +- .../Extensions/EntityExtensions.cs | 4 +- .../Extensions/ServiceCollectionExtensions.cs | 3 +- .../Store/GrantStore.cs | 2 +- .../Store/PushedAuthorizationRequestStore.cs | 39 + .../Store/ReferenceTokenStore.cs | 20 +- ...Server.EntityFramework.Store.Duende.csproj | 2 +- ....IdentityServer.KeysRotation.Duende.csproj | 4 +- ...IdentityServer.MongoDb.Store.Duende.csproj | 2 +- .../ServiceCollectionExtensions.cs | 2 - ...IdentityServer.RavenDb.Store.Duende.csproj | 2 +- .../ServiceCollectionExtensions.cs | 4 +- ...congas.IdentityServer.Saml2p.Duende.csproj | 6 +- .../Signin/SignInResponseGenerator.cs | 13 +- ....IdentityServer.WsFederation.Duende.csproj | 2 +- ...congas.TheIdServer.Migrations.MySql.csproj | 5 +- ...ongas.TheIdServer.Migrations.Oracle.csproj | 6 +- ...s.TheIdServer.Migrations.PostgreSQL.csproj | 4 +- ...10245_PushAuthorizationRequest.Designer.cs | 1459 ++++++++++++++++ ...20231116210245_PushAuthorizationRequest.cs | 40 + .../ConfigurationDbContextModelSnapshot.cs | 10 +- ...05225_PushAuthorizationRequest.Designer.cs | 404 +++++ ...20231116205225_PushAuthorizationRequest.cs | 37 + .../OperationalDbContextModelSnapshot.cs | 24 +- ...as.TheIdServer.Migrations.SqlServer.csproj | 4 +- ...10305_PushAuthorizationRequest.Designer.cs | 1460 +++++++++++++++++ ...20231116210305_PushAuthorizationRequest.cs | 40 + .../ConfigurationDbContextModelSnapshot.cs | 10 +- ...04418_PushAuthorizationRequest.Designer.cs | 404 +++++ ...20231116204418_PushAuthorizationRequest.cs | 37 + .../OperationalDbContextModelSnapshot.cs | 24 +- ...ongas.TheIdServer.Migrations.Sqlite.csproj | 4 +- ...10255_PushAuthorizationRequest.Designer.cs | 1454 ++++++++++++++++ ...20231116210255_PushAuthorizationRequest.cs | 40 + .../ConfigurationDbContextModelSnapshot.cs | 10 +- ...04408_PushAuthorizationRequest.Designer.cs | 395 +++++ ...20231116204408_PushAuthorizationRequest.cs | 37 + .../OperationalDbContextModelSnapshot.cs | 24 +- ...guacongas.TheIdServer.MySql.Startup.csproj | 6 +- ...uacongas.TheIdServer.Oracle.Startup.csproj | 6 +- ...ngas.TheIdServer.PostgreSQL.Startup.csproj | 6 +- ...ongas.TheIdServer.SqlServer.Startup.csproj | 6 +- ...uacongas.TheIdServer.Sqlite.Startup.csproj | 6 +- ...dentityServer.Admin.Http.Store.Test.csproj | 6 +- ...uacongas.TheIdServer.BlazorApp.Test.csproj | 6 +- .../Aguacongas.TheIdServer.Test.csproj | 6 +- .../TracerProviderBuilderExtensionsTest.cs | 3 +- ...as.IdentityServer.Admin.Duende.Test.csproj | 10 +- .../WindowsHandlerTest.cs | 4 +- ...uacongas.IdentityServer.Duende.Test.csproj | 6 +- ...r.EntityFramework.Store.Duende.Test.csproj | 10 +- .../PushedAuthorizationRequestStoreTest.cs | 98 ++ ...entityServer.Http.Store.Duende.Test.csproj | 6 +- ...tityServer.KeysRotation.Duende.Test.csproj | 8 +- ...ityServer.MongoDb.Store.Duende.Test.csproj | 6 +- ...ityServer.RavenDb.Store.Duende.Test.csproj | 10 +- ...s.IdentityServer.Saml2P.Duende.Test.csproj | 6 +- ...hentication.Integration.Duende.Test.csproj | 21 +- .../TestUtils.cs | 61 - .../TheIdServerTestFixture.cs | 35 +- .../Aguacongas.TheIdServer.Duende.Test.csproj | 13 +- .../StartupTest.cs | 818 +++++---- ...as.TheIdServer.Identity.Duende.Test.csproj | 6 +- ...er.Identity.Integration.Duende.Test.csproj | 15 +- .../TestUtils.cs | 67 - .../TheIdServerTestFixture.cs | 51 +- .../UserStoreTest.cs | 1 - ...TheIdServer.Integration.Duende.Test.csproj | 18 +- .../BlazorApp/Pages/RelyingPartyTest.cs | 3 - .../Controllers/RegisterControllerTest.cs | 2 +- .../TestUtils.cs | 5 +- .../appsettings.json | 25 + ....Identity.Argon2PasswordHasher.Test.csproj | 30 + .../Argon2PasswordHasherTest.cs | 75 + .../IdentityBuilderExtensionsTest.cs | 19 + .../ServiceCollectionExtentsionsTest.cs | 20 + .../GlobalUsings.cs | 1 + ...r.Identity.BcryptPasswordHasherTest.csproj | 29 + .../BcryptPasswordHasherTest.cs | 76 + .../IdentityBuilderExtensionsTest.cs | 19 + .../ServiceCollectionExtentsionsTest.cs | 21 + .../GlobalUsings.cs | 1 + ...r.Identity.ScryptPasswordHasherTest.csproj | 29 + .../IdentityBuilderExtensionsTest.cs | 19 + .../ServiceCollectionExtentsionsTest.cs | 20 + .../GlobalUsings.cs | 1 + .../ScryptPasswordHasherTest.cs | 77 + ....Identity.UpgradePasswordHasherTest.csproj | 32 + .../IdentityBuilderExtensionsTest.cs | 19 + .../ServiceCollectionExtensionsTest.cs | 41 + .../GlobalUsings.cs | 1 + .../UpgradePasswordHasherTest.cs | 120 ++ .../ContainerComponent.cs | 75 - .../EventDispatchExtensions.cs | 50 - .../Htmlizer.cs | 231 --- ...osoft.AspNetCore.Components.Testing.csproj | 16 - .../MockHttpExtensions.cs | 51 - .../RenderedComponent.cs | 60 - .../TestHost.cs | 139 -- .../TestHtmlDocument.cs | 16 - .../TestNavigationManager.cs | 29 - .../TestRenderer.cs | 72 - .../HttpClient/WasmHttpMessageHandler.cs | 12 - .../WebAssembly.Net.Http.csproj | 20 - .../WebAssembly.Net.Http.ruleset | 3 - update-dependencies.ps1 | 93 +- 332 files changed, 11342 insertions(+), 4032 deletions(-) create mode 100644 .github/workflows/deploy-to-heroku.yml create mode 100644 doc/ADMINAPP.md create mode 100644 doc/INTRODUCTION.md create mode 100644 doc/SERVER.md create mode 100644 doc/SETUP.md create mode 100644 src/Aguacongas.TheIdServer.BlazorApp/Roots.xml delete mode 100644 src/Aguacongas.TheIdServer.MauiApp/Data/WeatherForecast.cs delete mode 100644 src/Aguacongas.TheIdServer.MauiApp/Data/WeatherForecastService.cs delete mode 100644 src/Aguacongas.TheIdServer.MauiApp/Pages/Counter.razor delete mode 100644 src/Aguacongas.TheIdServer.MauiApp/Pages/FetchData.razor delete mode 100644 src/Aguacongas.TheIdServer.MauiApp/Pages/Index.razor delete mode 100644 src/Aguacongas.TheIdServer.MauiApp/Platforms/Tizen/Main.cs delete mode 100644 src/Aguacongas.TheIdServer.MauiApp/Platforms/Tizen/tizen-manifest.xml delete mode 100644 src/Aguacongas.TheIdServer.MauiApp/Resources/AppIcon/appicon.svg delete mode 100644 src/Aguacongas.TheIdServer.MauiApp/Resources/AppIcon/appiconfg.svg delete mode 100644 src/Aguacongas.TheIdServer.MauiApp/Resources/Raw/AboutAssets.txt delete mode 100644 src/Aguacongas.TheIdServer.MauiApp/Resources/Splash/splash.svg delete mode 100644 src/Aguacongas.TheIdServer.MauiApp/Shared/MainLayout.razor.css delete mode 100644 src/Aguacongas.TheIdServer.MauiApp/Shared/NavMenu.razor delete mode 100644 src/Aguacongas.TheIdServer.MauiApp/Shared/NavMenu.razor.css delete mode 100644 src/Aguacongas.TheIdServer.MauiApp/Shared/SurveyPrompt.razor delete mode 100644 src/Aguacongas.TheIdServer/.config/dotnet-tools.json delete mode 100644 src/Aguacongas.TheIdServer/Dockerfile-windows create mode 100644 src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Components/Form/AuthorizeNumber.razor create mode 100644 src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Components/Form/AuthorizeNumber.razor.cs create mode 100644 src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Infrastructure/Models/OpenIdConnectConfiguration.cs create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.csproj create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/Argon2Id.cs create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/Argon2PasswordHasher.cs create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/Argon2PasswordHasherOptions.cs create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/AssemblyInfo.cs create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/Extensions/IdentityBuilderExtensions.cs create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/Extensions/ServiceCollectionExtensions.cs create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/IArgon2Id.cs create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/README.md create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/THIRD-PARTY-NOTICES create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/package-icon.png create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/vcruntime140.dll create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher.csproj create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/BcryptPasswordHasher.cs create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/BcryptPasswordHasherOptions.cs create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/Extensions/IdentityBuilderExtensions.cs create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/Extensions/ServiceCollectionExtensions.cs create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/README.md create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/THIRD-PARTY-NOTICES create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/package-icon.png create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasher/Aguacongas.TheIdServer.Identity.ScryptPasswordHasher.csproj create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasher/Extensions/IdentityBuilderExtensions.cs create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasher/Extensions/ServiceCollectionExtensions.cs create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasher/README.md create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasher/ScryptPasswordHasher.cs create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasher/ScryptPasswordHasherOptions.cs create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasher/package-icon.png create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher.csproj create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/Extensions/IdentityBuilderExtensions.cs create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/Extensions/ServiceCollectionExtensions.cs create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/README.md create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/UpgradePasswordHasher.cs create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/UpgradePasswordHasherOptions.cs create mode 100644 src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/package-icon.png create mode 100644 src/IdentityServer/Aguacongas.IdentityServer.Admin/Configuration/ApiPath.cs create mode 100644 src/IdentityServer/Aguacongas.IdentityServer.Admin/Configuration/ApiRouteAttribute.cs create mode 100644 src/IdentityServer/Aguacongas.IdentityServer.Store/Entity/PushedAuthorizationRequest.cs create mode 100644 src/IdentityServer/Duende/Aguacongas.IdentityServer.Duende/Store/PushedAuthorizationRequestStore.cs create mode 100644 src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/Migrations/ConfigurationDb/20231116210245_PushAuthorizationRequest.Designer.cs create mode 100644 src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/Migrations/ConfigurationDb/20231116210245_PushAuthorizationRequest.cs create mode 100644 src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/Migrations/OperationalDb/20231116205225_PushAuthorizationRequest.Designer.cs create mode 100644 src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/Migrations/OperationalDb/20231116205225_PushAuthorizationRequest.cs create mode 100644 src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Migrations/ConfigurationDb/20231116210305_PushAuthorizationRequest.Designer.cs create mode 100644 src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Migrations/ConfigurationDb/20231116210305_PushAuthorizationRequest.cs create mode 100644 src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Migrations/OperationalDb/20231116204418_PushAuthorizationRequest.Designer.cs create mode 100644 src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Migrations/OperationalDb/20231116204418_PushAuthorizationRequest.cs create mode 100644 src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Migrations/ConfigurationDb/20231116210255_PushAuthorizationRequest.Designer.cs create mode 100644 src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Migrations/ConfigurationDb/20231116210255_PushAuthorizationRequest.cs create mode 100644 src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Migrations/OperationalDb/20231116204408_PushAuthorizationRequest.Designer.cs create mode 100644 src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Migrations/OperationalDb/20231116204408_PushAuthorizationRequest.cs create mode 100644 test/Duende/Aguacongas.IdentityServer.EntityFramework.Store.Duende.Test/PushedAuthorizationRequestStoreTest.cs delete mode 100644 test/Duende/Aguacongas.TheIdServer.Authentication.Integration.Duende.Test/TestUtils.cs delete mode 100644 test/Duende/Aguacongas.TheIdServer.Identity.Integration.Duende.Test/TestUtils.cs create mode 100644 test/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Test/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Test.csproj create mode 100644 test/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Test/Argon2PasswordHasherTest.cs create mode 100644 test/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Test/Extensions/IdentityBuilderExtensionsTest.cs create mode 100644 test/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Test/Extensions/ServiceCollectionExtentsionsTest.cs create mode 100644 test/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Test/GlobalUsings.cs create mode 100644 test/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasherTest/Aguacongas.TheIdServer.Identity.BcryptPasswordHasherTest.csproj create mode 100644 test/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasherTest/BcryptPasswordHasherTest.cs create mode 100644 test/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasherTest/Extensions/IdentityBuilderExtensionsTest.cs create mode 100644 test/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasherTest/Extensions/ServiceCollectionExtentsionsTest.cs create mode 100644 test/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasherTest/GlobalUsings.cs create mode 100644 test/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasherTest/Aguacongas.TheIdServer.Identity.ScryptPasswordHasherTest.csproj create mode 100644 test/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasherTest/Extensions/IdentityBuilderExtensionsTest.cs create mode 100644 test/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasherTest/Extensions/ServiceCollectionExtentsionsTest.cs create mode 100644 test/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasherTest/GlobalUsings.cs create mode 100644 test/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasherTest/ScryptPasswordHasherTest.cs create mode 100644 test/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasherTest/Aguacongas.TheIdServer.Identity.UpgradePasswordHasherTest.csproj create mode 100644 test/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasherTest/Extensions/IdentityBuilderExtensionsTest.cs create mode 100644 test/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasherTest/Extensions/ServiceCollectionExtensionsTest.cs create mode 100644 test/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasherTest/GlobalUsings.cs create mode 100644 test/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasherTest/UpgradePasswordHasherTest.cs delete mode 100644 test/Microsoft.AspNetCore.Components.Testing/ContainerComponent.cs delete mode 100644 test/Microsoft.AspNetCore.Components.Testing/EventDispatchExtensions.cs delete mode 100644 test/Microsoft.AspNetCore.Components.Testing/Htmlizer.cs delete mode 100644 test/Microsoft.AspNetCore.Components.Testing/Microsoft.AspNetCore.Components.Testing.csproj delete mode 100644 test/Microsoft.AspNetCore.Components.Testing/MockHttpExtensions.cs delete mode 100644 test/Microsoft.AspNetCore.Components.Testing/RenderedComponent.cs delete mode 100644 test/Microsoft.AspNetCore.Components.Testing/TestHost.cs delete mode 100644 test/Microsoft.AspNetCore.Components.Testing/TestHtmlDocument.cs delete mode 100644 test/Microsoft.AspNetCore.Components.Testing/TestNavigationManager.cs delete mode 100644 test/Microsoft.AspNetCore.Components.Testing/TestRenderer.cs delete mode 100644 test/WebAssembly.Net.Http/HttpClient/WasmHttpMessageHandler.cs delete mode 100644 test/WebAssembly.Net.Http/WebAssembly.Net.Http.csproj delete mode 100644 test/WebAssembly.Net.Http/WebAssembly.Net.Http.ruleset diff --git a/.github/workflows/check-dependencies.yml b/.github/workflows/check-dependencies.yml index 19160c048..706251e88 100644 --- a/.github/workflows/check-dependencies.yml +++ b/.github/workflows/check-dependencies.yml @@ -19,7 +19,7 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checkout the branch fix/dependencies with the PAT - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: ref: fix/dependencies token: ${{ secrets.PAT }} @@ -31,9 +31,10 @@ jobs: git merge origin/master --allow-unrelated-histories # Setup .NET Core SDK - name: Setup .NET Core SDK - uses: actions/setup-dotnet@v2.1.0 + uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.* + dotnet-version: 8 + include-prerelease: true # Restore workload - name: Restore workload run: dotnet workload restore diff --git a/.github/workflows/codeql-analysis-cs.yml b/.github/workflows/codeql-analysis-cs.yml index 06cf02bf4..dca4b1426 100644 --- a/.github/workflows/codeql-analysis-cs.yml +++ b/.github/workflows/codeql-analysis-cs.yml @@ -39,18 +39,19 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install .NET sdk - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.* + dotnet-version: 8 + include-prerelease: true - name: Setup wasm tools run: dotnet workload install wasm-tools env: GITHIB_FEED_TOKEN: ${{ secrets.GITHIB_FEED_TOKEN }} # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 env: GITHIB_FEED_TOKEN: ${{ secrets.GITHIB_FEED_TOKEN }} with: @@ -68,4 +69,4 @@ jobs: name: Build - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/codeql-analysis-js.yml b/.github/workflows/codeql-analysis-js.yml index f69324b1b..b0ea66edb 100644 --- a/.github/workflows/codeql-analysis-js.yml +++ b/.github/workflows/codeql-analysis-js.yml @@ -39,11 +39,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install .NET sdk - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v3 with: - dotnet-version: '7.0.200' + dotnet-version: 8 include-prerelease: true - name: Setup wasm tools run: dotnet workload restore diff --git a/.github/workflows/codesee-arch-diagram.yml b/.github/workflows/codesee-arch-diagram.yml index a72f58bac..6f36efd87 100644 --- a/.github/workflows/codesee-arch-diagram.yml +++ b/.github/workflows/codesee-arch-diagram.yml @@ -1,5 +1,5 @@ # This workflow was added by CodeSee. Learn more at https://codesee.io/ -# This is v2.0 of this workflow file +# This is v3.0 of this workflow file on: push: branches: diff --git a/.github/workflows/deploy-doc.yml b/.github/workflows/deploy-doc.yml index 45dfca227..2090d835a 100644 --- a/.github/workflows/deploy-doc.yml +++ b/.github/workflows/deploy-doc.yml @@ -33,10 +33,10 @@ jobs: - name: Generate HTML from Markdown uses: ldeluigi/markdown-docs@latest with: - src: . + src: doc dst: generated - name: Upload artifact - uses: actions/upload-pages-artifact@v1 + uses: actions/upload-pages-artifact@v2 with: path: generated diff --git a/.github/workflows/deploy-to-heroku.yml b/.github/workflows/deploy-to-heroku.yml new file mode 100644 index 000000000..f83e9c9be --- /dev/null +++ b/.github/workflows/deploy-to-heroku.yml @@ -0,0 +1,38 @@ +# This is a basic workflow to help you get started with Actions + +name: Deploy to Heroku + +on: + # Runs on pushes targeting the default branch + push: + branches: ["master"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + # Build job + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Install .NET sdk + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 8 + include-prerelease: true + - name: Setup wasm tools + run: dotnet workload install wasm-tools + env: + GITHIB_FEED_TOKEN: ${{ secrets.GITHIB_FEED_TOKEN }} + - name: Publish + run: dotnet publish ./src/Aguacongas.TheIdServer.Duende/Aguacongas.TheIdServer.Duende.csproj --output ./heroku_output --configuration Release --runtime linux-x64 --sc -p:SourceRevisionId=$GITHUB_SHA /nr:false /m:1 + - name: Deploy + uses: akhileshns/heroku-deploy@v3.12.14 + with: + heroku_api_key: ${{ secrets.HEROKU_API_KEY }} + heroku_app_name: "theidserver-duende" + heroku_email: "aguacongas@gmail.com" + buildpack: https://github.com/Aguafrommars/theidserver-buildpack + diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index a4d0c7895..e72df8760 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -20,11 +20,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v3 - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v3 with: username: theidserverowner password: ${{ secrets.DOCKER_HUB_PAT }} @@ -39,7 +39,7 @@ jobs: fallback: 1.0.0 - name: Build and push TheIdServer Duende id: docker_build_theidserver_duende - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v3 env: GITHIB_FEED_TOKEN: ${{ secrets.GITHIB_FEED_TOKEN }} with: @@ -51,11 +51,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v3 - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v3 with: username: theidserverowner password: ${{ secrets.DOCKER_HUB_PAT }} @@ -70,7 +70,7 @@ jobs: fallback: 1.0.0 - name: Build and push TheIdServerApp id: docker_build_theidserverapp - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v3 env: GITHIB_FEED_TOKEN: ${{ secrets.GITHIB_FEED_TOKEN }} with: diff --git a/THIRD-PARTY-NOTICES b/THIRD-PARTY-NOTICES index d83055aff..391e95c94 100644 --- a/THIRD-PARTY-NOTICES +++ b/THIRD-PARTY-NOTICES @@ -163,3 +163,48 @@ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +License notice for samuel-lucas6/Geralt +--------------------------------------- + +MIT License + +Copyright (c) 2022 Samuel Lucas + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +License notice for BcryptNet/bcrypt.net +--------------------------------------- + +The MIT License (MIT) +Copyright (c) 2006 Damien Miller djm@mindrot.org (jBCrypt) +Copyright (c) 2013 Ryan D. Emerle (.Net port) +Copyright (c) 2016/2021 Chris McKee (.Net-core port / patches) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/TheIdServer.sln b/TheIdServer.sln index fdeb47a4b..298215312 100644 --- a/TheIdServer.sln +++ b/TheIdServer.sln @@ -240,6 +240,26 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Identity", "Identity", "{1FC87EDC-78E7-465E-9721-D7B481D64ED7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Identity", "Identity", "{5CDF0690-B16C-4A75-80C2-F1EDC7C26514}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aguacongas.TheIdServer.Identity.Argon2PasswordHasher", "src\Identity\Aguacongas.TheIdServer.Identity.Argon2PasswordHasher\Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.csproj", "{90BF8847-F021-470D-A2ED-37C0B0A6E388}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Test", "test\Identity\Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Test\Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Test.csproj", "{4ED9CAC7-0113-4B25-A580-6B1D5DA05173}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aguacongas.TheIdServer.Identity.ScryptPasswordHasher", "src\Identity\Aguacongas.TheIdServer.Identity.ScryptPasswordHasher\Aguacongas.TheIdServer.Identity.ScryptPasswordHasher.csproj", "{916046EF-085E-44F7-9DC4-4D7C41768C5C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aguacongas.TheIdServer.Identity.ScryptPasswordHasherTest", "test\Identity\Aguacongas.TheIdServer.Identity.ScryptPasswordHasherTest\Aguacongas.TheIdServer.Identity.ScryptPasswordHasherTest.csproj", "{717AC678-2735-4959-84B2-BD399FB59C23}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aguacongas.TheIdServer.Identity.BcryptPasswordHasher", "src\Identity\Aguacongas.TheIdServer.Identity.BcryptPasswordHasher\Aguacongas.TheIdServer.Identity.BcryptPasswordHasher.csproj", "{6024B537-CE0D-496E-9ECE-4D9F7C8B5DA3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aguacongas.TheIdServer.Identity.BcryptPasswordHasherTest", "test\Identity\Aguacongas.TheIdServer.Identity.BcryptPasswordHasherTest\Aguacongas.TheIdServer.Identity.BcryptPasswordHasherTest.csproj", "{34F746F3-152C-426F-B91F-02DE10F15518}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aguacongas.TheIdServer.Identity.UpgradePasswordHasher", "src\Identity\Aguacongas.TheIdServer.Identity.UpgradePasswordHasher\Aguacongas.TheIdServer.Identity.UpgradePasswordHasher.csproj", "{C04EAFE6-0E01-4792-A113-B98AE76466D1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aguacongas.TheIdServer.Identity.UpgradePasswordHasherTest", "test\Identity\Aguacongas.TheIdServer.Identity.UpgradePasswordHasherTest\Aguacongas.TheIdServer.Identity.UpgradePasswordHasherTest.csproj", "{18F49B30-17EC-4365-9F34-4D963D23FFE1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1318,6 +1338,102 @@ Global {D2B86706-5A7D-4414-8AE4-E579B4DEA462}.Release|x64.Build.0 = Release|Any CPU {D2B86706-5A7D-4414-8AE4-E579B4DEA462}.Release|x86.ActiveCfg = Release|Any CPU {D2B86706-5A7D-4414-8AE4-E579B4DEA462}.Release|x86.Build.0 = Release|Any CPU + {90BF8847-F021-470D-A2ED-37C0B0A6E388}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90BF8847-F021-470D-A2ED-37C0B0A6E388}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90BF8847-F021-470D-A2ED-37C0B0A6E388}.Debug|x64.ActiveCfg = Debug|Any CPU + {90BF8847-F021-470D-A2ED-37C0B0A6E388}.Debug|x64.Build.0 = Debug|Any CPU + {90BF8847-F021-470D-A2ED-37C0B0A6E388}.Debug|x86.ActiveCfg = Debug|Any CPU + {90BF8847-F021-470D-A2ED-37C0B0A6E388}.Debug|x86.Build.0 = Debug|Any CPU + {90BF8847-F021-470D-A2ED-37C0B0A6E388}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90BF8847-F021-470D-A2ED-37C0B0A6E388}.Release|Any CPU.Build.0 = Release|Any CPU + {90BF8847-F021-470D-A2ED-37C0B0A6E388}.Release|x64.ActiveCfg = Release|Any CPU + {90BF8847-F021-470D-A2ED-37C0B0A6E388}.Release|x64.Build.0 = Release|Any CPU + {90BF8847-F021-470D-A2ED-37C0B0A6E388}.Release|x86.ActiveCfg = Release|Any CPU + {90BF8847-F021-470D-A2ED-37C0B0A6E388}.Release|x86.Build.0 = Release|Any CPU + {4ED9CAC7-0113-4B25-A580-6B1D5DA05173}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4ED9CAC7-0113-4B25-A580-6B1D5DA05173}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4ED9CAC7-0113-4B25-A580-6B1D5DA05173}.Debug|x64.ActiveCfg = Debug|Any CPU + {4ED9CAC7-0113-4B25-A580-6B1D5DA05173}.Debug|x64.Build.0 = Debug|Any CPU + {4ED9CAC7-0113-4B25-A580-6B1D5DA05173}.Debug|x86.ActiveCfg = Debug|Any CPU + {4ED9CAC7-0113-4B25-A580-6B1D5DA05173}.Debug|x86.Build.0 = Debug|Any CPU + {4ED9CAC7-0113-4B25-A580-6B1D5DA05173}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4ED9CAC7-0113-4B25-A580-6B1D5DA05173}.Release|Any CPU.Build.0 = Release|Any CPU + {4ED9CAC7-0113-4B25-A580-6B1D5DA05173}.Release|x64.ActiveCfg = Release|Any CPU + {4ED9CAC7-0113-4B25-A580-6B1D5DA05173}.Release|x64.Build.0 = Release|Any CPU + {4ED9CAC7-0113-4B25-A580-6B1D5DA05173}.Release|x86.ActiveCfg = Release|Any CPU + {4ED9CAC7-0113-4B25-A580-6B1D5DA05173}.Release|x86.Build.0 = Release|Any CPU + {916046EF-085E-44F7-9DC4-4D7C41768C5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {916046EF-085E-44F7-9DC4-4D7C41768C5C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {916046EF-085E-44F7-9DC4-4D7C41768C5C}.Debug|x64.ActiveCfg = Debug|Any CPU + {916046EF-085E-44F7-9DC4-4D7C41768C5C}.Debug|x64.Build.0 = Debug|Any CPU + {916046EF-085E-44F7-9DC4-4D7C41768C5C}.Debug|x86.ActiveCfg = Debug|Any CPU + {916046EF-085E-44F7-9DC4-4D7C41768C5C}.Debug|x86.Build.0 = Debug|Any CPU + {916046EF-085E-44F7-9DC4-4D7C41768C5C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {916046EF-085E-44F7-9DC4-4D7C41768C5C}.Release|Any CPU.Build.0 = Release|Any CPU + {916046EF-085E-44F7-9DC4-4D7C41768C5C}.Release|x64.ActiveCfg = Release|Any CPU + {916046EF-085E-44F7-9DC4-4D7C41768C5C}.Release|x64.Build.0 = Release|Any CPU + {916046EF-085E-44F7-9DC4-4D7C41768C5C}.Release|x86.ActiveCfg = Release|Any CPU + {916046EF-085E-44F7-9DC4-4D7C41768C5C}.Release|x86.Build.0 = Release|Any CPU + {717AC678-2735-4959-84B2-BD399FB59C23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {717AC678-2735-4959-84B2-BD399FB59C23}.Debug|Any CPU.Build.0 = Debug|Any CPU + {717AC678-2735-4959-84B2-BD399FB59C23}.Debug|x64.ActiveCfg = Debug|Any CPU + {717AC678-2735-4959-84B2-BD399FB59C23}.Debug|x64.Build.0 = Debug|Any CPU + {717AC678-2735-4959-84B2-BD399FB59C23}.Debug|x86.ActiveCfg = Debug|Any CPU + {717AC678-2735-4959-84B2-BD399FB59C23}.Debug|x86.Build.0 = Debug|Any CPU + {717AC678-2735-4959-84B2-BD399FB59C23}.Release|Any CPU.ActiveCfg = Release|Any CPU + {717AC678-2735-4959-84B2-BD399FB59C23}.Release|Any CPU.Build.0 = Release|Any CPU + {717AC678-2735-4959-84B2-BD399FB59C23}.Release|x64.ActiveCfg = Release|Any CPU + {717AC678-2735-4959-84B2-BD399FB59C23}.Release|x64.Build.0 = Release|Any CPU + {717AC678-2735-4959-84B2-BD399FB59C23}.Release|x86.ActiveCfg = Release|Any CPU + {717AC678-2735-4959-84B2-BD399FB59C23}.Release|x86.Build.0 = Release|Any CPU + {6024B537-CE0D-496E-9ECE-4D9F7C8B5DA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6024B537-CE0D-496E-9ECE-4D9F7C8B5DA3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6024B537-CE0D-496E-9ECE-4D9F7C8B5DA3}.Debug|x64.ActiveCfg = Debug|Any CPU + {6024B537-CE0D-496E-9ECE-4D9F7C8B5DA3}.Debug|x64.Build.0 = Debug|Any CPU + {6024B537-CE0D-496E-9ECE-4D9F7C8B5DA3}.Debug|x86.ActiveCfg = Debug|Any CPU + {6024B537-CE0D-496E-9ECE-4D9F7C8B5DA3}.Debug|x86.Build.0 = Debug|Any CPU + {6024B537-CE0D-496E-9ECE-4D9F7C8B5DA3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6024B537-CE0D-496E-9ECE-4D9F7C8B5DA3}.Release|Any CPU.Build.0 = Release|Any CPU + {6024B537-CE0D-496E-9ECE-4D9F7C8B5DA3}.Release|x64.ActiveCfg = Release|Any CPU + {6024B537-CE0D-496E-9ECE-4D9F7C8B5DA3}.Release|x64.Build.0 = Release|Any CPU + {6024B537-CE0D-496E-9ECE-4D9F7C8B5DA3}.Release|x86.ActiveCfg = Release|Any CPU + {6024B537-CE0D-496E-9ECE-4D9F7C8B5DA3}.Release|x86.Build.0 = Release|Any CPU + {34F746F3-152C-426F-B91F-02DE10F15518}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {34F746F3-152C-426F-B91F-02DE10F15518}.Debug|Any CPU.Build.0 = Debug|Any CPU + {34F746F3-152C-426F-B91F-02DE10F15518}.Debug|x64.ActiveCfg = Debug|Any CPU + {34F746F3-152C-426F-B91F-02DE10F15518}.Debug|x64.Build.0 = Debug|Any CPU + {34F746F3-152C-426F-B91F-02DE10F15518}.Debug|x86.ActiveCfg = Debug|Any CPU + {34F746F3-152C-426F-B91F-02DE10F15518}.Debug|x86.Build.0 = Debug|Any CPU + {34F746F3-152C-426F-B91F-02DE10F15518}.Release|Any CPU.ActiveCfg = Release|Any CPU + {34F746F3-152C-426F-B91F-02DE10F15518}.Release|Any CPU.Build.0 = Release|Any CPU + {34F746F3-152C-426F-B91F-02DE10F15518}.Release|x64.ActiveCfg = Release|Any CPU + {34F746F3-152C-426F-B91F-02DE10F15518}.Release|x64.Build.0 = Release|Any CPU + {34F746F3-152C-426F-B91F-02DE10F15518}.Release|x86.ActiveCfg = Release|Any CPU + {34F746F3-152C-426F-B91F-02DE10F15518}.Release|x86.Build.0 = Release|Any CPU + {C04EAFE6-0E01-4792-A113-B98AE76466D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C04EAFE6-0E01-4792-A113-B98AE76466D1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C04EAFE6-0E01-4792-A113-B98AE76466D1}.Debug|x64.ActiveCfg = Debug|Any CPU + {C04EAFE6-0E01-4792-A113-B98AE76466D1}.Debug|x64.Build.0 = Debug|Any CPU + {C04EAFE6-0E01-4792-A113-B98AE76466D1}.Debug|x86.ActiveCfg = Debug|Any CPU + {C04EAFE6-0E01-4792-A113-B98AE76466D1}.Debug|x86.Build.0 = Debug|Any CPU + {C04EAFE6-0E01-4792-A113-B98AE76466D1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C04EAFE6-0E01-4792-A113-B98AE76466D1}.Release|Any CPU.Build.0 = Release|Any CPU + {C04EAFE6-0E01-4792-A113-B98AE76466D1}.Release|x64.ActiveCfg = Release|Any CPU + {C04EAFE6-0E01-4792-A113-B98AE76466D1}.Release|x64.Build.0 = Release|Any CPU + {C04EAFE6-0E01-4792-A113-B98AE76466D1}.Release|x86.ActiveCfg = Release|Any CPU + {C04EAFE6-0E01-4792-A113-B98AE76466D1}.Release|x86.Build.0 = Release|Any CPU + {18F49B30-17EC-4365-9F34-4D963D23FFE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {18F49B30-17EC-4365-9F34-4D963D23FFE1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {18F49B30-17EC-4365-9F34-4D963D23FFE1}.Debug|x64.ActiveCfg = Debug|Any CPU + {18F49B30-17EC-4365-9F34-4D963D23FFE1}.Debug|x64.Build.0 = Debug|Any CPU + {18F49B30-17EC-4365-9F34-4D963D23FFE1}.Debug|x86.ActiveCfg = Debug|Any CPU + {18F49B30-17EC-4365-9F34-4D963D23FFE1}.Debug|x86.Build.0 = Debug|Any CPU + {18F49B30-17EC-4365-9F34-4D963D23FFE1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {18F49B30-17EC-4365-9F34-4D963D23FFE1}.Release|Any CPU.Build.0 = Release|Any CPU + {18F49B30-17EC-4365-9F34-4D963D23FFE1}.Release|x64.ActiveCfg = Release|Any CPU + {18F49B30-17EC-4365-9F34-4D963D23FFE1}.Release|x64.Build.0 = Release|Any CPU + {18F49B30-17EC-4365-9F34-4D963D23FFE1}.Release|x86.ActiveCfg = Release|Any CPU + {18F49B30-17EC-4365-9F34-4D963D23FFE1}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1342,7 +1458,7 @@ Global {D5F297EF-4649-46D4-994A-03F26CF4D5DE} = {F6FFE02C-E4E0-43C3-A2EC-34B62E7281C6} {86767A50-87BE-4A2A-B53E-CDAD29DCD67E} = {F6FFE02C-E4E0-43C3-A2EC-34B62E7281C6} {A36931C8-4A4E-4A92-961C-D0AA6E8CB9C7} = {98BBB159-779B-497B-927B-723592E84860} - {837ED9F5-91A3-4AEB-9A29-22881643F6C2} = {DF545B54-78FB-42D0-A842-A1490A579B37} + {837ED9F5-91A3-4AEB-9A29-22881643F6C2} = {1FC87EDC-78E7-465E-9721-D7B481D64ED7} {9F64CDFF-6347-4F62-B74F-C6CA2743E667} = {DF545B54-78FB-42D0-A842-A1490A579B37} {8A3B91E5-02A3-42F0-B2E7-E419B5086FFA} = {DF545B54-78FB-42D0-A842-A1490A579B37} {C2806393-D1EB-4DDE-8A4B-AA65014B8D08} = {8A3B91E5-02A3-42F0-B2E7-E419B5086FFA} @@ -1420,6 +1536,16 @@ Global {DC84EE86-A50E-4183-B092-899499E748A9} = {5D0BA166-1FCB-4D8B-97D6-655A5E16FF74} {389ABB64-4F0B-4DD8-8B40-9FC01E1D3C50} = {5D0BA166-1FCB-4D8B-97D6-655A5E16FF74} {D2B86706-5A7D-4414-8AE4-E579B4DEA462} = {5D0BA166-1FCB-4D8B-97D6-655A5E16FF74} + {1FC87EDC-78E7-465E-9721-D7B481D64ED7} = {DF545B54-78FB-42D0-A842-A1490A579B37} + {5CDF0690-B16C-4A75-80C2-F1EDC7C26514} = {DE50F426-4409-4573-8502-93364ED12E0C} + {90BF8847-F021-470D-A2ED-37C0B0A6E388} = {1FC87EDC-78E7-465E-9721-D7B481D64ED7} + {4ED9CAC7-0113-4B25-A580-6B1D5DA05173} = {5CDF0690-B16C-4A75-80C2-F1EDC7C26514} + {916046EF-085E-44F7-9DC4-4D7C41768C5C} = {1FC87EDC-78E7-465E-9721-D7B481D64ED7} + {717AC678-2735-4959-84B2-BD399FB59C23} = {5CDF0690-B16C-4A75-80C2-F1EDC7C26514} + {6024B537-CE0D-496E-9ECE-4D9F7C8B5DA3} = {1FC87EDC-78E7-465E-9721-D7B481D64ED7} + {34F746F3-152C-426F-B91F-02DE10F15518} = {5CDF0690-B16C-4A75-80C2-F1EDC7C26514} + {C04EAFE6-0E01-4792-A113-B98AE76466D1} = {1FC87EDC-78E7-465E-9721-D7B481D64ED7} + {18F49B30-17EC-4365-9F34-4D963D23FFE1} = {5CDF0690-B16C-4A75-80C2-F1EDC7C26514} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5283BE0B-F6F2-4458-B12F-64C78CFF8CBA} diff --git a/appveyor.yml b/appveyor.yml index e50afbfb5..21f60f27c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -8,21 +8,21 @@ branches: - /^preview\/\d+.\d+.\d+$/ - /^release\/\d+.\d+.\d+$/ image: -- Visual Studio 2019 +- Visual Studio 2022 services: - mongodb environment: GITHUB_TOKEN: secure: /o9VAhx5ewGmdLR9qcgFJMzBaCuzOmGlsXAHu7khUJLdQzsv4gJzLUfYDghcRPHd - donetsdk: 7.0.402 - JAVA_HOME: C:\Program Files\Java\jdk14 + donetsdk: 8.0.100 + JAVA_HOME: C:\Program Files\Java\jdk19 PATH: $(JAVA_HOME)\bin;$(PATH) init: - cmd: git config --global core.autocrlf true install: - ps: Install-Product node 18 x64 - - ps: .\dotnet-install.ps1 -Version 6.0.11 -Runtime dotnet -Architecture x64 -InstallDir "C:\Program Files\dotnet" - - ps: .\dotnet-install.ps1 -Version 6.0.11 -Runtime aspnetcore -Architecture x64 -InstallDir "C:\Program Files\dotnet" + - ps: .\dotnet-install.ps1 -Version 7.0.11 -Runtime dotnet -Architecture x64 -InstallDir "C:\Program Files\dotnet" + - ps: .\dotnet-install.ps1 -Version 7.0.11 -Runtime aspnetcore -Architecture x64 -InstallDir "C:\Program Files\dotnet" - ps: .\dotnet-install.ps1 -Version $env:donetsdk -InstallDir "C:\Program Files\dotnet" - ps: dotnet tool install --global GitVersion.Tool - ps: dotnet gitversion /l console /output buildserver diff --git a/build.ps1 b/build.ps1 index 4f8196668..59e6a3765 100644 --- a/build.ps1 +++ b/build.ps1 @@ -18,8 +18,8 @@ if ($prNumber) { elseif ($env:APPVEYOR_REPO_BRANCH) { $prArgs = "-d:sonar.branch.name=$env:APPVEYOR_REPO_BRANCH" } -Write-Host "dotnet sonarscanner begin /k:aguacongas_TheIdServer -o:aguacongas -d:sonar.host.url=https://sonarcloud.io -d:sonar.login=****** -d:sonar.coverageReportPaths=coverage\SonarQube.xml $prArgs -v:$env:Version" -dotnet sonarscanner begin /k:aguacongas_TheIdServer -o:aguacongas -d:sonar.host.url=https://sonarcloud.io -d:sonar.login=$env:sonarqube -d:sonar.coverageReportPaths=coverage\SonarQube.xml $prArgs -v:$env:Version +Write-Host "dotnet sonarscanner begin /k:aguacongas_TheIdServer -o:aguacongas -d:sonar.host.url=https://sonarcloud.io -d:sonar.token=****** -d:sonar.coverageReportPaths=coverage\SonarQube.xml $prArgs -v:$env:Version" +dotnet sonarscanner begin /k:aguacongas_TheIdServer -o:aguacongas -d:sonar.host.url=https://sonarcloud.io -d:sonar.token=$env:sonarqube -d:sonar.coverageReportPaths=coverage\SonarQube.xml $prArgs -v:$env:Version Write-Host "dotnet test -c Release --settings coverletArgs.runsettings" @@ -37,8 +37,8 @@ Get-ChildItem -rec ` $merge = "$path;$merge" } Write-Host $merge -ReportGenerator\tools\net5.0\ReportGenerator.exe "-reports:$merge" "-targetdir:coverage" "-reporttypes:SonarQube" +ReportGenerator\tools\net7.0\ReportGenerator.exe "-reports:$merge" "-targetdir:coverage" "-reporttypes:SonarQube" -dotnet sonarscanner end -d:sonar.login=$env:sonarqube +dotnet sonarscanner end -d:sonar.token=$env:sonarqube exit $result \ No newline at end of file diff --git a/bump-ms-dependencies.ps1 b/bump-ms-dependencies.ps1 index cbb3b1330..06d931485 100644 --- a/bump-ms-dependencies.ps1 +++ b/bump-ms-dependencies.ps1 @@ -4,10 +4,16 @@ function UpdatePackages { $project ) + $currentDirectoy = Get-Location $return = $false + $dir = Split-Path $project + Write-Host 'Set-Location' $dir + + Set-Location $dir + # Get outdated packages - $packageLineList = dotnet list $project package --outdated --include-prerelease + $packageLineList = dotnet list package --outdated --include-prerelease foreach($line in $packageLineList) { Write-Host $line @@ -19,8 +25,8 @@ function UpdatePackages { } # update an outdated package - $added = dotnet add $project package $Matches.1 --version $Matches.2 - + $added = dotnet add package $Matches.1 --version $Matches.2 + if ($LASTEXITCODE -ne 0) { # error while updating the package Write-Error "dotnet add $project package $Matches.1 --version $Matches.2 exit with code $LASTEXITCODE" @@ -28,9 +34,11 @@ function UpdatePackages { break } - $return = $true + Write-Host 'package' $Matches.1 'version' $Matches.2 'updated' + $return = $true } + Set-Location $currentDirectoy return $return } diff --git a/bump-system-dependencies.ps1 b/bump-system-dependencies.ps1 index adc2d25e1..948461eb0 100644 --- a/bump-system-dependencies.ps1 +++ b/bump-system-dependencies.ps1 @@ -4,10 +4,16 @@ function UpdatePackages { $project ) + $currentDirectoy = Get-Location $return = $false + $dir = Split-Path $project + Write-Host 'Set-Location' $dir + + Set-Location $dir + # Get outdated packages - $packageLineList = dotnet list $project package --outdated --include-prerelease + $packageLineList = dotnet list package --outdated --include-prerelease foreach($line in $packageLineList) { Write-Host $line @@ -19,8 +25,10 @@ function UpdatePackages { } # update an outdated package - $added = dotnet add $project package $Matches.1 --version $Matches.2 - + $added = dotnet add package $Matches.1 --version $Matches.2 + + Write-Host $Matches.1 'version' $Matches.2 $added + if ($LASTEXITCODE -ne 0) { # error while updating the package Write-Error "dotnet add $project package $Matches.1 --version $Matches.2 exit with code $LASTEXITCODE" @@ -31,6 +39,7 @@ function UpdatePackages { $return = $true } + Set-Location $currentDirectoy return $return } diff --git a/doc/ADMINAPP.md b/doc/ADMINAPP.md new file mode 100644 index 000000000..d40d5c77c --- /dev/null +++ b/doc/ADMINAPP.md @@ -0,0 +1,179 @@ +# TheIdServer Admin Application + +This project is the [Blazor Web Assembly](https//blazor.net) application to manage a TheIdServer instance. + +## Installation +### From Docker + +The application is embedded in the [server's Linux image](SERVER.md#from-docker). +If you prefer, you can install the [standalone application'sLinux image](https://hub.docker.com/r/aguacongas/theidserverapp). +This image uses an [nginx](http://nginx.org/) server to host the application. + +### From Github Release + +The application is embedded in the [server's Github release](SERVER.md#from-github-release). +You can choose to install the standalone application by selecting *Aguacongas.TheIdServer.BlazorApp{version}.zip* in the [list of releases](https://github.com/Aguafrommars/TheIdServer/releases). +Unzip in the destination of your choice, and use the server of your choice. + +Read [Host and deploy ASP.NET Core Blazor WebAssembly](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/blazor/webassembly?view=aspnetcore-3.1) for more information. + +### From NuGet packages + +NuGet packages composing the application are available on [nuget.org](https://www.nuget.org/): + +* **Aguacongas.TheIdServer.BlazorApp.Infrastructure** contains application models, services, validators and extensions +* **Aguacongas.TheIdServer.BlazorApp.Components** contains application components +* **Aguacongas.TheIdServer.BlazorApp.Pages.*** contains application pages + +## Configuration + +The application obtains its configuration from appsettings.json and the environment-specific settings from appsettings.{environment}.json. + +**appsettings.json** + +```json +{ + "administratorEmail": "aguacongas@gmail.com", + "apiBaseUrl": "https://localhost:5443/api", + "authenticationPaths": { + "remoteRegisterPath": "/identity/account/register", + "remoteProfilePath": "/identity/account/manage" + }, + "loggingOptions": { + "minimum": "Debug", + "filters": [ + { + "category": "System", + "level": "Warning" + }, + { + "category": "Microsoft", + "level": "Information" + } + ] + }, + "userOptions": { + "roleClaim": "role" + }, + "providerOptions": { + "authority": "https://localhost:5443/", + "clientId": "theidserveradmin", + "defaultScopes": [ + "openid", + "profile", + "theidserveradminapi" + ], + "postLogoutRedirectUri": "https://localhost:5443/authentication/logout-callback", + "redirectUri": "https://localhost:5443/authentication/login-callback", + "responseType": "code" + }, + "settingsOptions": { + "typeName": "Aguacongas.TheIdServer.BlazorApp.Models.ServerConfig, Aguacongas.TheIdServer.BlazorApp.Infrastructure", + "apiUrl": "https://localhost:5443/api/configuration" + }, + "menuOptions": { + "showSettings": true + }, + "welcomeContenUrl": "https://localhost:5443/welcome-fragment.html", + "serverSideSessionEnabled": false, + "cibaEnabled": false +} +``` + +For more details, read [ASP.NET Core Blazor hosting model configuration / Blazor WebAssembly / Configuration](https://docs.microsoft.com/en-us/aspnet/core/blazor/hosting-model-configuration?view=aspnetcore-3.1#configuration). + +### apiBaseUrl + +Defines the URL to the API. + +### administratorEmail + +Defines the administrator eMail address. + +### authenticationPaths + +The section **authenticationPaths** is binded to the class `Microsoft.AspNetCore.Components.WebAssembly.Authentication.RemoteAuthenticationApplicationPathsOptions`. +The application doesn't contain pages to register a new user or manage the current user, so we set the **authenticationPaths:remoteRegisterPath** and **authenticationPaths:remoteProfilePath** with their corresponding URL on the identity server. + + For more information, read [ASP.NET Core Blazor WebAssembly additional security scenarios / Customize app routes](https://docs.microsoft.com/en-us/aspnet/core/security/blazor/webassembly/additional-scenarios?view=aspnetcore-3.1#customize-app-routes). + +### loggingOptions + +Defines logging options. + +#### minimum + +Defines the [log minimum level](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel?view=dotnet-plat-ext-3.1). + +#### filters + +Each item in this array adds a log filter by category and [LogLevel](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel?view=dotnet-plat-ext-3.1). + +### userOptions + +The section **userOptions** is bound to the class `Microsoft.AspNetCore.Components.WebAssembly.Authentication.RemoteAuthenticationUserOptions`. +This configuration defines how users are authorized. The application and the API share the same authorization policy. + +* **Is4-Writer** authorizes users in this role to write data. +* **Is4-Reader** permits users in this role to read data. + +**userOptions:roleClaim** define the role claims type. + +### providerOptions + +The section **providerOptions** is binded to the class `Microsoft.AspNetCore.Components.WebAssembly.Authentication.OidcProviderOptions`. +This configuration section defines the application authentication. + +For more details, read [Secure an ASP.NET Core Blazor WebAssembly standalone app with the Authentication library / Authentication service support](https://docs.microsoft.com/en-us/aspnet/core/security/blazor/webassembly/standalone-with-authentication-library?view=aspnetcore-3.1#authentication-service-support). + +### welcomeContenUrl + +Defines the URL to the welcome page content. + +## Welcome page customization + +Except for its title, the home page displays contents read from `welcomeContenUrl` endpoint. + +This endpoint should return an HTML fragment. + +[**sample**](../src/Aguacongas.TheIdServer.Duende/wwwroot/welcome-fragment.html) + +```html +

+ This application manage your TheIdServer. +

+

+ Visit the github site for doc, source code and issue tracking. +

+

+ If you have trouble with login, disable Chromium cookies-without-same-site-must-be-secure flag.
+ + chrome://flags/#cookies-without-same-site-must-be-secure +
+ This site is running under a free heroku dyno without end-to-end https. +

+

+ You can sign-in with alice to have reader/writer access, or bob for a read-only access.
+ The password is Pass123$. +

+``` + +## UI Options +### Hide settings menu + +To hide the settings menu, unset **menuOptions:showSettings**. + +### Hide CIBA grant type + +If CIBA is not enabled you can hide the CIBA grant type by unsetting cibaEnabled options. + +### Hide coordinate lifetime with user session checkbox + +If server side sessions are not enable you can hide the coordinate lifetime with user session checkbox in client tokens section by unsetting serverSideSessionEnabled options. + +## Additional resources + +* [ASP.NET Core Blazor hosting model configuration](https://docs.microsoft.com/en-us/aspnet/core/blazor/hosting-model-configuration?view=aspnetcore-3.1#configuration) +* [ASP.NET Core Blazor WebAssembly additional security scenarios](https://docs.microsoft.com/en-us/aspnet/core/security/blazor/webassembly/additional-scenarios?view=aspnetcore-3.1#customize-app-routes) +* [Secure an ASP.NET Core Blazor WebAssembly standalone app with the Authentication library](https://docs.microsoft.com/en-us/aspnet/core/security/blazor/webassembly/standalone-with-authentication-library?view=aspnetcore-3.1#authentication-service-support) +* [LogLevel Enum](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel?view=dotnet-plat-ext-3.1) diff --git a/doc/INTRODUCTION.md b/doc/INTRODUCTION.md new file mode 100644 index 000000000..ef659b01c --- /dev/null +++ b/doc/INTRODUCTION.md @@ -0,0 +1,11 @@ +# Introduction + +Read [TheIdServer](../README.md). + +## How to contribute + +Read [How to contribute](../CONTRIBUTING.md) and [Contributor Covenant Code of Conduct](../CODE_OF_CONDUCT.md). + +## Security Policy + +Read [Security Policy](../SECURITY.md). diff --git a/doc/SERVER.md b/doc/SERVER.md new file mode 100644 index 000000000..cb3e50d02 --- /dev/null +++ b/doc/SERVER.md @@ -0,0 +1,1093 @@ +# TheIdServer Duende Web Server + +> TheIdServer use [Duende IdentityServer](https://duendesoftware.com/products/identityserver), for a commercial use you need to [acquire a license](https://duendesoftware.com/products/identityserver#pricing). + +The server obtains configuration from *appsettings.json*, *appsettings.{Environment}.json*, command-line arguments, or environment variables. + +Read [Configuration in ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/) for more information. + +## Installation + +### From Terraform + +The [Terraform](https://terraform.io) [Helm](https://helm.sh) module [theidserver](https://registry.terraform.io/modules/Aguafrommars/theidserver/helm/latest) make the deployement of TheIdServer easy. +To deploy the Duende version choose the [aguacongas/theidserver.duende image](https://hub.docker.com/r/aguacongas/theidserver.duende). + +``` hcl +provider "helm" { + kubernetes { + config_path = var.kubeconfig_path + } +} + +module "theidserver" { + source = "Aguafrommars/theidserver/helm" + + host = "theidserver.com" + tls_issuer_name = "letsencrypt" + tls_issuer_kind = "ClusterIssuer" + + image = { + repository = "aguacongas/theidserver.duende" + pullPolicy = "Always" + tag = "next" + } +} +``` + +### From Helm + +The [theidserver](https://hub.helm.sh/packages/helm/aguafrommars/theidserver) [Helm](https://helm.sh) chart is available in [hub.helm.sh](https://hub.helm.sh). + +#### Install + +``` bash +helm repo add aguafrommars https://aguafrommars.github.io/helm +helm install aguafrommars theidserver --set theidserver.mysql.db.password=my-P@ssword --set image.repository=aguacongas/theidserver.duende +``` + +> By default the helm char install the IS4 version, to install the Duende version your need to set `image.repository=aguacongas/theidserver.duende`. + +#### Upgrade + +Follow upgrades intstructions in the [chart readme](https://github.com/Aguafrommars/helm/blob/main/charts/theidserver/README.md#upgrade). + +### From Docker + +A [server's Linux image](https://hub.docker.com/r/aguacongas/theidserver.duende) is available on Docker Hub. + +[*sample/MultiTiers/Aguacongas.TheIdServer.Private/Dockerfile.Duende-private*](../../sample/MultiTiers/Aguacongas.TheIdServer.Private/Dockerfile.Duende-private) demonstrates how to create an image from the [server image](https://hub.docker.com/r/aguacongas/theidserver) to run a private Linux server container. + +[*sample/MultiTiers/Aguacongas.TheIdServer.Public/Dockerfile.Duende-public*](../../sample/MultiTiers/Aguacongas.TheIdServer.Public/Dockerfile.Duende-public) illustrates how to create an image from the [server image](https://hub.docker.com/r/aguacongas/theidserver) to run a public Linux server container. + +Read [Hosting ASP.NET Core images with Docker over HTTPS](https://docs.microsoft.com/en-us/aspnet/core/security/docker-https) to set up the HTTPS certificate. + +#### Kubernetes sample + +[/sample/Kubernetes/README.md](/sample/Kubernetes/README.md) contains a sample to set up a solution with Kubernetes. + +> The sample use the IS4 version but you just need to use `aguacongas/theidserver.duende` as docker image in the deployement file. + +### From dotnet new template + +The template [TheIdServer.Duende.Template](https://github.com/Aguafrommars/Templates) can be use to setup a TheIdServer solution. + +#### Install + +```bash +dotnet new -i TheIdServer.Duende.Template +``` + +#### Use + +```bash +> dotnet new tisduende -o TheIdServer +The template "TheIdServer.Duende" was created successfully. + +Processing post-creation actions... +Running 'dotnet restore' on TheIdServer\TheIdServer.sln... + Determining projects to restore... + Restored C:\Projects\Perso\Templates\artifacts\TheIdServer\test\WebAssembly.Net.Http\WebAssembly.Net.Http.csproj (in 114 ms). + Restored C:\Projects\Perso\Templates\artifacts\TheIdServer\src\TheIdServer.BlazorApp\TheIdServer.BlazorApp.csproj (in 916 ms). + Restored C:\Projects\Perso\Templates\artifacts\TheIdServer\test\Microsoft.AspNetCore.Components.Testing\Microsoft.AspNetCore.Components.Testing.csproj (in 1.08 sec). + Restored C:\Projects\Perso\Templates\artifacts\TheIdServer\src\TheIdServer\TheIdServer.csproj (in 2.03 sec). + Restored C:\Projects\Perso\Templates\artifacts\TheIdServer\test\TheIdServer.Test\TheIdServer.Test.csproj (in 2.04 sec). + Restored C:\Projects\Perso\Templates\artifacts\TheIdServer\test\TheIdServer.IntegrationTest\TheIdServer.IntegrationTest.csproj (in 2.04 sec). +Restore succeeded. +``` + +### From NuGet Packages + +If you need more customization, you can use published NuGet packages. +[sample/MultiTiers](sample/MultiTiers) contains a sample to build server and API from NuGet packages. + +> The sample use IS4 version but you just need to remplace IS4 by Duende in package reference to use the Duende version. + +### From Github Release + +Choose your release in the [list of releases](https://github.com/Aguafrommars/TheIdServer/releases) and download the server zip. +Unzip in the destination of your choice. Unzip in the destination of your choice. As with any ASP.NET Core web site, it can run in IIS or as a stand-alone server using your chosen platform. + +Read [Host and deploy ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/) for more information. + +## Configure data protection + +[Data protection](DATA_PROTECTION.md) provides details on data protection configuration. + +## Configure site + +The site name is defined by *SiteOptions:TheIdServer*. + +```json +"SiteOptions": { + "Name": "TheIdServer" +} +``` + +The site stylecheets are *wwwroot/lib/bootstrap/css/bootstrap.css* and *wwwroot/css/site.min.css*. +The site logo is *wwwroot/logo.png*. +And the favicon is *wwwroot/favicon.ico*. + +By replacing those files you can redefined the site style by yours. + +### Configure account options + +The section *AccountOptions* is bound to [`AccountOptions`](../src/Aguacongas.TheIdServer.Duende/Quickstart/Account/AccountOptions.cs). + +```json +"AccountOptions": { + "AllowLocalLogin": true, + "AllowRememberLogin": true, + "RememberMeLoginDuration": "30.00:00:00", + "ShowLogoutPrompt": true, + "AutomaticRedirectAfterSignOut": false, + "InvalidCredentialsErrorMessage": "Invalid username or password", + "ShowForgotPassworLink": true, + "ShowRegisterLink": true, + "ShowResendEmailConfirmationLink": true +} +``` + +## Configure ASP.Net Core Identity options + +The section **IdentityOptions** is bound to the class [`Microsoft.AspNetCore.Identity.IdentityOptions`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.identity.identityoptions). +So you can set any ASP.Net Core Identity options you want from configuration + +```json +"IdentityOptions": { + "User": { + "AllowedUserNameCharacters": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+ " + }, + "SignIn": { + "RequireConfirmedAccount": true + } +} +``` + +## Configure password hashers options + +Read [OWASP Password Storage Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html) to choose and configure your password hasher. + +### PBKDF2 Password hasher + +`Microsoft.AspNetCore.Identity.PasswordHasher` is the default hasher used by ASP.Net Core Identity. +You can hash password using PBKDF2 if the [upgrade password hasher](#upgrade-password-hasher) is configured to use `Microsoft.AspNetCore.Identity.PasswordHasher`. + +The section **PasswordHasherOptions** is bound to the class [`Microsoft.AspNetCore.Identity.PasswordHasherOptions`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.identity.passwordhasheroptions). +So you can set any [`Microsoft.AspNetCore.Identity.PasswordHasherOptions`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.identity.passwordhasheroptions) properties you want from configuration. + +```json +"PasswordHasherOptions": { + "IterationCount": 600000 +} +``` + +### Argon2id password hasher + +You can hash password using Argon2id if the [upgrade password hasher](#upgrade-password-hasher) is configured to use `Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Argon2PasswordHasher`. + +The section **Argon2PasswordHasherOptions** is bound to the class [`Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Argon2PasswordHasherOptions`](https://github.com/Aguafrommars/TheIdServer/blob/master/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/Argon2PasswordHasherOptions.cs). +So you can set any [`Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Argon2PasswordHasherOptions`](https://github.com/Aguafrommars/TheIdServer/blob/master/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/Argon2PasswordHasherOptions.cs) properties you want from configuration. + +```json +"Argon2PasswordHasherOptions": { + "Interations": 2, + "Memory": 67108864 +} +``` + +### scrypt password hasher + +You can hash password using scrypt if the [upgrade password hasher](#upgrade-password-hasher) is configured to use `Aguacongas.TheIdServer.Identity.ScryptPasswordHasher.ScryptPasswordHasher`. + +The section **ScryptPasswordHasherOptions** is bound to the class [`Aguacongas.TheIdServer.Identity.ScryptPasswordHasher.ScryptPasswordHasherOptions`](https://github.com/Aguafrommars/TheIdServer/blob/master/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/Argon2PasswordHasherOptions.cs). +So you can set any [`Aguacongas.TheIdServer.Identity.ScryptPasswordHasher.ScryptPasswordHasherOptions`](https://github.com/Aguafrommars/TheIdServer/blob/master/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/Argon2PasswordHasherOptions.cs) properties you want from configuration. + +```json +"ScryptPasswordHasherOptions": { + "IterationCount": 131072, + "BlockSize": 8, + "ThreadCount": 1 +} +``` + +### bcrypt password hasher + +You can hash password using bcrypt if the [upgrade password hasher](#upgrade-password-hasher) is configured to use `Aguacongas.TheIdServer.Identity.BcryptPasswordHasher.BcryptPasswordHasher`. + +The section **BcryptPasswordHasherOptions** is bound to the class [`Aguacongas.TheIdServer.Identity.BcryptPasswordHasher.BcryptPasswordHasherOptions`](https://github.com/Aguafrommars/TheIdServer/blob/master/src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/BcryptPasswordHasherOptions.cs). +So you can set any [`Aguacongas.TheIdServer.Identity.BcryptPasswordHasher.BcryptPasswordHasherOptions`](https://github.com/Aguafrommars/TheIdServer/blob/master/src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/BcryptPasswordHasherOptions.cs) properties you want from configuration. + +```json +"BcryptPasswordHasherOptions": { + "WorkFactor": 11 +} +``` + +### Upgrade password hasher + +Upgrade password hasher is used to manage hash migration between old password hashing algorithm to the new one to use. +In previous version of TheIdServer password was hashed with PBKDF2 by default ASP.Net Core Identity password hasher with its default configuration. +Now you can choose between Argon2id, scrypt, bcrypt and PBKDF2 by settings the hasher to use. + +Read [Password Hasher to rehash password to a new algorithm for ASP.NET Core Identity.](https://github.com/Aguafrommars/TheIdServer/blob/master/src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/README.md#password-hasher-to-rehash-password-to-a-new-algorithm-for-aspnet-core-identity) for more information. + +The section **UpgradePasswordHasherOptions** is bound to the class [`Aguacongas.TheIdServer.Identity.UpgradePasswordHasher.UpgradePasswordHasherOptions`](https://github.com/Aguafrommars/TheIdServer/blob/master/src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/UpgradePasswordHasherOptions.cs). +So you can set any [`Aguacongas.TheIdServer.Identity.UpgradePasswordHasher.UpgradePasswordHasherOptions`](https://github.com/Aguafrommars/TheIdServer/blob/master/src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/UpgradePasswordHasherOptions.cs) properties you want from configuration. + +```json +"UpgradePasswordHasherOptions": { + "HashPrefixMaps": { + "0": "Microsoft.AspNetCore.Identity.PasswordHasher", + "1": "Microsoft.AspNetCore.Identity.PasswordHasher", + "162": "Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Argon2PasswordHasher", + "12": "Aguacongas.TheIdServer.Identity.ScryptPasswordHasher.ScryptPasswordHasher", + "188": "Aguacongas.TheIdServer.Identity.BcryptPasswordHasher.BcryptPasswordHasher" + }, + "UsePasswordHasherTypeName": "Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Argon2PasswordHasher" +} +``` + +## Configure Duende IdentityServer + +The section **IdentityServerOptions** is bound to the class [`Duende.IdentityServer.Configuration.IdentityServerOptions`](https://docs.duendesoftware.com/identityserver/v5/reference/options/). +So you can set any Duende IdentityServer options you want from configuration (but key management options). + +```json +"IdentityServerOptions": { + "Events": { + "RaiseErrorEvents": true, + "RaiseInformationEvents": true, + "RaiseFailureEvents": true, + "RaiseSuccessEvents": true + }, + "Endpoints": { + "EnableJwtRequestUri": true + } +} +``` + +### Discovery document customs entries + +You can add customs entries to the genererated discovery document with *IdentityServerOptions* sub section *CustomEntriesOfStringArray*, *CustomEntriesOfString* and *CustomEntriesOfBool* + +```json +"IdentityServerOptions": { + "CustomEntriesOfStringArray": { + "token_endpoint_auth_signing_alg_values_supported": [ + "RS256", + "ES256", + "ES384", + "ES512", + "PS256", + "PS384", + "PS512", + "RS384", + "RS512" + ] + } +} +``` + +The sample above will add `"token_endpoint_auth_signing_alg_values_supported"` node to the generated document. + +### Mutual TLS client certificate options + +When Muutal TLS is enabled, you can configure the client certificate authentication options with `CertificateAuthenticationOptions` section. + +```json +"IdentityServerOptions": { + "MutualTls": { + "Enabled": true + } +}, +"CertificateAuthenticationOptions": { + "AllowedCertificateTypes": "All", + "ValidateCertificateUse": false, + "ValidateValidityPeriod": false, + "RevocationMode": "NoCheck" +} +``` + +### Retrieves client certificates fron HTTP request header + +When Mutual TLS is enabled the client certificate can be read in PEM format from request header. For exemple if you use a kubernetes NGINX ingress you can configure it to send the client certificate to the backend in the *ssl-client-cert* header. +See [Client Certificate Authentication](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#client-certificate-authentication). + +To retrieve the client certificate from the request header confiure the `MutualTls` sub section like : + +```json +"IdentityServerOptions": { + "MutualTls": { + "Enabled": true, + "PEMHeader": "ssl-client-cert" + } +} +``` + +### Configure Server-side sessions + +Read [Server-side sessions](SERVER_SIDE_SESSIONS.md) + +## Configure stores + +### Using Entity Framework Core + +The server supports *SqlServer*, *Sqlite*, *MySql*, *PostgreSQL*, *Oracle*, and *InMemory* databases. +Use **DbType** to the define the database engine. + +```json +"DbType": "SqlServer" +``` + +And **ConnectionStrings:DefaultConnection** to define the connection string. + +```json +"ConnectionStrings": { + "DefaultConnection": "Data Source=(LocalDb)\\MSSQLLocalDB;database=TheIdServer;trusted_connection=yes;" +} +``` + +> A [devart dotConnect for Oracle](https://www.devart.com/dotconnect/oracle/) license is a requirement for Oracle. + +### Using RavenDb + +Use **DbType** to the define the RavenDb database engine. + +```json +"DbType": "RavenDb" +``` + +And **RavenDbOptions** to define the RavenDb options. + +```json +"RavenDbOptions": { + "Urls": [ + "https://a.ravendb.local", + "https://b.ravendb.local", + "https://c.ravendb.local" + ], + "Database": "TheIdServer", + "CertificatePath": "cluster.admin.client.certificate.pfx", + "CertificatePassword": "p@$$w0rd" +} +``` + +> As no `DbContext` will be registered, you cannot store signing keys in EF but you can choose the `RavenDb` storage kind (see [Configure signin keys](#configure-signing-key)): +```json +"IdentityServer": { + "Key": { + "StorageKind": "RavenDb" + } +}, +"DataProtectionOptions": { + "StorageKind": "RavenDb" +} +``` +> The server support RavenDb 4.1 and above. + +### Using MongoDb + +Use **DbType** to the define the RavenDb database engine. + +```json +"DbType": "MongoDb" +``` + +And **ConnectionStrings:DefaultConnection** to define the connection string. + +```json +"ConnectionStrings": { + "DefaultConnection": "mongodb+srv://theidserver:theidserverpwd@cluster0.fvkfz.mongodb.net/TheIdServer?retryWrites=true&w=majority" +} +``` + +> We cannot used another database than the default database defined in the connection string. + +> As no `DbContext` will be registered, you cannot store signing keys in EF but you can choose the `MongoDb` storage kind (see [Configure signin keys](#configure-signing-key)): +```json +"IdentityServer": { + "Key": { + "StorageKind": "MongoDb" + } +}, +"DataProtectionOptions": { + "StorageKind": "MongoDb" +} +``` + +### Using the API + +![public-private.svg](assets/public-pribate.png) + +If you don't want to expose a database with your server, you can set up a second server on a private network accessing the database and use this private server API to access data. + +```json +"Proxy": true, +"PrivateServerAuthentication": { + "Authority": "https://theidserverprivate", + "ApiUrl": "https://theidserverprivate/api", + "ClientId": "public-server", + "ClientSecret": "84137599-13d6-469c-9376-9e372dd2c1bd", + "Scope": "theidserveradminapi", + "HttpClientName": "is4" +}, +"SignalR": { + "HubUrl": "https://theidserverprivate/providerhub" + "HubOptions": { + "EnableDetailedErrors": true + }, + "UseMessagePack": true +} +``` + +#### Proxy + +Start the server with proxy mode enabled. + +#### PrivateServerAuthentication + +Defines how to authenticate the public server on private server API. + +#### SignalR + +Defines the [SignalR client](https://docs.microsoft.com/en-us/aspnet/core/signalr/dotnet-client&tabs=visual-studio) configuration. +This client is used to update the external provider configuration of a running instance. When an external provider configuration changes, the API sends a SignalR notification to inform other running instances. + +For more information, read [Load balancing scenario](https://github.com/Aguafrommars/DymamicAuthProviders/wiki/Load-balancing-scenario). + +The SignalR hub accepts requests at */providerhub* and supports the [MessagePack](https://msgpack.org/index.html) protocol. + +For more information, read [Use MessagePack Hub Protocol in SignalR for ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/signalr/messagepackhubprotocol). + +### Database migration and data seeding + +Starting the server with the **/seed** command-line argument creates the database with initial data. Alternatively, configure the server with the following to create a database with initial users, protected resources, identity resources, and clients. + +```json +"Migrate": true, +"Seed": true +``` + +#### Roles + +* **Is4-Writer** authorizes users in this role to write data. +* **Is4-Reader** permits users in this role to read data. + +#### Identity resources + +* **profile** default profile resource with **role** claim +* **openid** default OpenID resource +* **address** default address resource +* **email** default email resource +* **phone** default phone resource + +#### Users + +Users defined in `InitialData:Users` configuration section are loaded and stored to the DB. + +Default configuration: + +```json +"InitialData": { +... + "Users": [ + { + "UserName": "alice", + "Email": "alice@theidserver.com", + "EmailConfirmed": true, + "PhoneNumber": "+41766403736", + "PhoneNumberConfirmed": true, + "Password": "Pass123$", + "Roles": [ + "Is4-Writer", + "Is4-Reader" + ], + "Claims": [ + { + "ClaimType": "name", + "ClaimValue": "Alice Smith" + }, + { + "ClaimType": "given_name", + "ClaimValue": "Alice" + }, + { + "ClaimType": "family_name", + "ClaimValue": "Smith" + }, + { + "ClaimType": "middle_name", + "ClaimValue": "Alice Smith" + }, + { + "ClaimType": "nickname", + "ClaimValue": "alice" + }, + { + "ClaimType": "website", + "ClaimValue": "http://alice.com" + }, + { + "ClaimType": "address", + "ClaimValue": "{ \"street_address\": \"One Hacker Way\", \"locality\": \"Heidelberg\", \"postal_code\": \"69118\", \"country\": \"Germany\" }", + }, + { + "ClaimType": "birthdate", + "ClaimValue": "1970-01-01" + }, + { + "ClaimType": "zoneinfo", + "ClaimValue": "ch" + }, + { + "ClaimType": "gender", + "ClaimValue": "female" + }, + { + "ClaimType": "profile", + "ClaimValue": "http://alice.com/profile" + }, + { + "ClaimType": "locale", + "ClaimValue": "fr" + }, + { + "ClaimType": "picture", + "ClaimValue": "http://alice.com/picture" + } + ] + } + ] +} +``` + +> A user with *Is4-Writer* and *Is4-Reader* roles is required to use the admin app. + +#### Protected resources (API) + +Apis defined in `InitialData:Apis` configuration section are loaded and stored to the DB. + +Default configuration: + +```json +"InitialData": { +... + "Apis": [ + { + "Name": "theidserveradminapi", + "DisplayName": "TheIdServer admin API", + "UserClaims": [ + "name", + "role" + ], + "ApiSecrets": [ + { + "Type": "SharedSecret", + "Value": "5b556f7c-b3bc-4b5b-85ab-45eed0cb962d" + } + ], + "Scopes": [ + "theidserveradminapi" + ] + } + ], +} +``` + +> The api **theidserveradminapi** is required for the admin app. + +#### ApiScopes + +ApiScopes defined in `InitialData:ApiScopes` configuration section are loaded and stored to the DB. + +Default configuration: + +```json +"InitialData": { +... + "ApiScopes": [ + { + "Name": "theidserveradminapi", + "DisplayName": "TheIdServer admin API", + "UserClaims": [ + "name", + "role" + ] + } + ], +} +``` + +> The scope **theidserveradminapi** is required for the admin app. + +#### Clients + +Clients defined in `InitialData:Clients` configuration section are loaded and stored to the DB. + +Default configuration: + +```json +"InitialData": { +... + "Clients": [ + { + "ClientId": "theidserveradmin", + "ClientName": "TheIdServer admin SPA Client", + "ClientUri": "https://localhost:5443/", + "ClientClaimsPrefix": null, + "AllowedGrantTypes": [ "authorization_code" ], + "RequirePkce": true, + "RequireClientSecret": false, + "BackChannelLogoutSessionRequired": false, + "FrontChannelLogoutSessionRequired": false, + "RedirectUris": [ + "http://localhost:5001/authentication/login-callback", + "https://localhost:5443/authentication/login-callback" + ], + "PostLogoutRedirectUris": [ + "http://localhost:5001/authentication/logout-callback", + "https://localhost:5443/authentication/logout-callback" + ], + "AllowedCorsOrigins": [ + "http://localhost:5001", + "https://localhost:5443" + ], + "AllowedScopes": [ + "openid", + "profile", + "theidserveradminapi" + ], + "AccessTokenType": "Reference" + }, + { + "ClientId": "public-server", + "ClientName": "Public server Credentials Client", + "ClientClaimsPrefix": null, + "AllowedGrantTypes": [ "client_credentials" ], + "ClientSecrets": [ + { + "Type": "SharedSecret", + "Value": "84137599-13d6-469c-9376-9e372dd2c1bd" + } + ], + "Claims": [ + { + "Type": "role", + "Value": "Is4-Writer" + }, + { + "Type": "role", + "Value": "Is4-Reader" + } + ], + "BackChannelLogoutSessionRequired": false, + "FrontChannelLogoutSessionRequired": false, + "AllowedScopes": [ + "openid", + "profile", + "theidserveradminapi" + ], + "AccessTokenType": "Reference" + } + ], +} +``` + +> The client **theidserveradmin** is required by the admin app. +> The client **public-server** is required to call web apis and server side prerendering of the admin app. + +## Configure Signing Key + +### Keys rotatation (recommanded) + +TheIdServer can be configured with a keys rotation mechanism instead of a single key. +Read [Keys rotation](KEYS_ROTATION.md) to know how to configure it. + +```json +"IdentityServer": { + "Key": { + "Type": "KeysRotation", + "StorageKind": "EntityFramework" + } +} +``` + +### From file + +```json +"IdentityServer": { + "Key": { + "Type": "File", + "FilePath": "{path to the .pfx}", + "Password": "{.pfx password}" + } +} +``` + +### From store + +Read [Example: Deploy to Azure Websites](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity-api-authorization#example-deploy-to-azure-websites) + +## Configure the Email service + +By default, the server uses [SendGrid](https://sendgrid.com/) to send Emails by calling the API at */api/email* + +```json +"SendGridUser": "your user", +"SendGridKey": "your SendGrid key" +``` + +### Use your API + +If you prefer to use your Email sender, implement a Web API receiving a POST request with the json: + +```json +{ + "subject": "Email subject", + "message": "Email message", + "addresses": [ + "an-address@aguacongas.con" + ] +} +``` + +And update the *EmailApiAuthentication* configuration section: + +```json +"EmailApiAuthentication": { + "Authority": "https://localhost:5443", + "ApiUrl": "https://localhost:5443/api/email", + "ClientId": "public-server", + "ClientSecret": "84137599-13d6-469c-9376-9e372dd2c1bd", + "Scope": "theidserveradminapi", + "HttpClientName": "email" +} +``` + +> If you want to use the same authentication configuration and token for both *EmailApi* and *PrivateServer*, you can simplify it by sharing the same **HttpClientName**. + +```json +"EmailApiAuthentication": { + "ApiUrl": "https://localhost:5443/api/email", + "HttpClientName": "is4" +} +``` + +## Configure the 2fa authenticator issuer + +By default, the issuer for the 2fa authenticator is **Aguacongas.TheIdServer**. +To update this value, set **AuthenticatorIssuer** with your issuer. + +```json +"AuthenticatorIssuer": "TheIdServer" +``` + +## Configure the API + +### Authentication + +The *ApiAuthentication* section defines the authentication configuration for the API. + +```json +"ApiAuthentication": { + "Authority": "https://localhost", + "RequireHttpsMetadata": false, + "SupportedTokens": "Both", + "ApiName": "theidserveradminapi", + "ApiSecret": "5b556f7c-b3bc-4b5b-85ab-45eed0cb962d", + "EnableCaching": true, + "CacheDuration": "0:10:0", + "LegacyAudienceValidation": true +} +``` + +### Documentation endpoint + +To enable the API documentation, set **EnableOpenApiDoc** to `true`. + +```json +"EnableOpenApiDoc": true +``` + +Use the section *SwaggerUiSettings* to configure the swagger client authentication. + +```json +"SwaggerUiSettings": { + "Path": "/api/swagger", + "OAuth2Client": { + "ClientId": "theidserver-swagger", + "AppName": "TheIdServer Swagger UI", + "UsePkceWithAuthorizationCodeGrant": true + }, + "WithCredentials": true +} +``` + +### CORS + +The section *CorsAllowedOrigin* defines allowed CORS origins. + +```json +"CorsAllowedOrigin": [ + "http://localhost:5001" +] +``` + +## Configure HTTPS + +To disable HTTPS, set **DisableHttps** to `false`. + +```json +"DisableHttps": true +``` + +If you use a self-signed certificate, you can disable strict-SSL by settings **DisableStrictSsl** to `true`. + +```json +"DisableStrictSsl": true +``` + +### Configure Forwarded Headers + +The section **ForwardedHeadersOptions** is bound to the class [`Microsoft.AspNetCore.Builder.ForwardedHeadersOptions`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.forwardedheadersoptions). + +```json +"ForwardedHeadersOptions": { + "ForwardedHeaders": "All" +} +``` + +### Force HTTPS scheme + +Some reverses proxies don't' forward headers. You can force HTTP requests schemes to https by settings ForceHttpsScheme. + +```json +"ForceHttpsScheme": true +``` + +## Configure the provider hub + +The [Aguacongas.AspNetCore.Authentication library](https://github.com/Aguafrommars/DymamicAuthProviders) dynamically configures external providers. +In a [load-balanced](https://github.com/Aguafrommars/DymamicAuthProviders/wiki/Load-balancing-scenario) configuration, the provider hub informs other running instances that an external provider configuration changes. +The **SignalR** section defines the configuration for both the SignalR hub and the client. + +```json +"SignalR": { + "HubUrl": "https://theidserverprivate/providerhub", + "HubOptions": { + "EnableDetailedErrors": true + }, + "UseMessagePack": true, + "RedisConnectionString": "redis:6379", + "RedisOptions": { + "Configuration": { + "ChannelPrefix": "TheIdServer" + } + } +} +``` + +If needed, the hub can use a [Redis backplane](https://docs.microsoft.com/en-us/aspnet/core/signalr/redis-backplane). **SignalR:RedisConnectionString** and **SignalR:RedisOptions** configures the backplane. +**SignalR:RedisOptions** is bound to an instance of [`Microsoft.AspNetCore.SignalR.StackExchangeRedis.RedisOptions`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.signalr.stackexchangeredis.redisoptions?view=aspnetcore-3.0) at startup. + +## Configure logs + +The **Serilog** section defines the [Serilog](https://serilog.net/) configuration. + +```json +"Serilog": { + "LevelSwitches": { + "$controlSwitch": "Information" + }, + "MinimumLevel": { + "ControlledBy": "$controlSwitch" + }, + "WriteTo": [ + { + "Name": "Seq", + "Args": { + "serverUrl": "http://localhost:5341", + "controlLevelSwitch": "$controlSwitch", + "apiKey": "DVYuookX2vOq078fuOyJ" + } + }, + { + "Name": "Console", + "Args": { + "outputTemplate": "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", + "theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Literate, Serilog.Sinks.Console" + } + }, + { + "Name": "Debug", + "Args": { + "outputTemplate": "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}" + } + } + ], + "Enrich": [ + "FromLogContext", + "WithMachineName", + "WithThreadId" + ] +} +``` +For more details, read [Serilog.Settings.Configuration](https://github.com/serilog/serilog-settings-configuration/blob/dev/README.md). + +## Configure claims providers + +[Claims provider](CLAIMS_PROVIDER.md) provides details on claims proivder configuration. + +## Configure token cleaner + +The token cleaner task removes expired tokens periodically. To configure the interval, use **TokenCleanupInterval**. + +```json +"TokenCleanupInterval": "00:05:00" +``` + +To disable the task, use **DisableTokenCleanup**. + +```json +"DisableTokenCleanup": true +``` + +> The task is not enabled on proxy server. + +## Configure Dynamic client registration allowed contacts a host + +The server supports [OpenID Connect Dynamic Client Registration](https://openid.net/specs/openid-connect-registration-1_0.html). + +New client registration is allowed to users with the **Is4-Writer** role by sending the user access token or to contacts defined in *DynamicClientRegistrationOptions* section. + +```json +"DynamicClientRegistrationOptions": { + "AllowedContacts": [ + { + "Contact": "certification@oidf.org", + "AllowedHosts": [ + "www.certification.openid.net" + ] + } + ] +} +``` + +It this case, the client registration request must contain the *contacts* array. + +**request sample** + +```json +{ + "client_name": "oidc_cert_client gUPPBlHIEAqNOYR", + "grant_types": [ + "authorization_code" + ], + "response_types": [ + "code" + ], + "redirect_uris": [ + "https://www.certification.openid.net/test/a/theidserver/callback" + ], + "contacts": [ + "certification@oidf.org" + ] +} +``` + +## Configure Jwt request validator + +Tokens returned by request_uri parameter are validated using the rules defined in *TokenValidationParameters* section. By default, the following rules are defined. + +```json +"TokenValidationParameters": { + "ValidateIssuer": false, + "ValidateAudience": false, + "ValidateIssuerSigningKey": false, + "ValidateLifetime": false, + "RequireAudience": false, + "RequireExpirationTime": false, + "RequireSignedTokens": false +} +``` + +> To enable JWT request uri, set *EnableJwtRequestUri* to true in *IdentityServerOptions:Endpoints* +> ```json +> "IdentityServerOptions": { +> "Endpoints": { +> "EnableJwtRequestUri": true +> } +> }, +> ``` + +## Configure WS-Federation endpoint + +Read [Aguacongas.IdentityServer.WsFederation.Duende](../IdentityServer/Duende/Aguacongas.IdentityServer.WsFederation.Duende/README.md) + +## Configure CIBA notification service + +Read [DUENDE CIBA INTEGRATION/Notification service](CIBA.md#Notification-service) + +## Use the client to override the default configuration + +The server and the blazor app integrate [Aguafrommars/DynamicConfiguration](https://github.com/Aguafrommars/DynamicConfiguration). Most of the configuration can be ovveriden using the blazor app. + +Use **DynamicConfigurationOptions** to define the dynamic configuration provider. + +```json +"DynamicConfigurationOptions": { + "ProviderType": "Aguacongas.DynamicConfiguration.Redis.RedisConfigurationProvider, Aguacongas.DynamicConfiguration.Redis" +} +``` +Use **RedisConfigurationOptions** section to configure the Redis db. + +```json +"RedisConfigurationOptions": { + "ConnectionString": "localhost", + "HashKey": "Aguacongas.TheIdServer.Duende", + "Channel": "Aguacongas.TheIdServer.Duende.Channel" +} +``` + +## Health checks + +The server expose an health checks enpoint you can use for docker on kubernetes at **/healthz**. + +The endpoit return a json reponse depending on the store kind used and redis dependencies : + +```json +{ + "status": "Healthy", + "results": { + "ConfigurationDbContext": { + "status": "Healthy" + }, + "OperationalDbContext": { + "status": "Healthy" + }, + "ApplicationDbContext": { + "status": "Healthy" + }, + "DynamicConfigurationRedis": { + "status": "Healthy" + } + } +} +``` + +## Configure OpenTelemetry + +[Configure OpenTelemetry doc](OPEN_TELEMETRY.md) provides details on [OpenTelemetry](https://opentelemetry.io/) configuration. + +## Additional resources + +* [Host and deploy ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/) +* [DymamicAuthProviders](https://github.com/Aguafrommars/DymamicAuthProviders) +* [Set up a Redis backplane for ASP.NET Core SignalR scale-out](https://docs.microsoft.com/en-us/aspnet/core/signalr/redis-backplane) +* [Microsoft.AspNetCore.SignalR.StackExchangeRedis.RedisOptions](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.signalr.stackexchangeredis.redisoptions?view=aspnetcore-3.0) +* [Serilog.Settings.Configuration](https://github.com/serilog/serilog-settings-configuration/blob/dev/README.md) +* [Hosting ASP.NET Core images with Docker over HTTPS](https://docs.microsoft.com/en-us/aspnet/core/security/docker-https) +* [OpenID Connect Dynamic Client Registration](https://openid.net/specs/openid-connect-registration-1_0.html) +* [Aguafrommars/DynamicConfiguration](https://github.com/Aguafrommars/DynamicConfiguration) +* [OpenTelemetry](https://opentelemetry.io/) diff --git a/doc/SETUP.md b/doc/SETUP.md new file mode 100644 index 000000000..68d28dda4 --- /dev/null +++ b/doc/SETUP.md @@ -0,0 +1,9 @@ +# SETUP + +## Server + +Setup instructions are discribed in [TheIdServer Duende Web Server project](../src/Aguacongas.TheIdServer.Duende/README.md). + +## Standalone admin app + +Setup instructions are discribed in [TheIdServer Admin Application](../src/Aguacongas.TheIdServer.BlazorApp/README.md) \ No newline at end of file diff --git a/sample/Aguacongas.TheIdServer.ApiSample/Aguacongas.TheIdServer.ApiSample.csproj b/sample/Aguacongas.TheIdServer.ApiSample/Aguacongas.TheIdServer.ApiSample.csproj index f236d879f..dc260d379 100644 --- a/sample/Aguacongas.TheIdServer.ApiSample/Aguacongas.TheIdServer.ApiSample.csproj +++ b/sample/Aguacongas.TheIdServer.ApiSample/Aguacongas.TheIdServer.ApiSample.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 @@ -13,9 +13,9 @@ - - - + + + diff --git a/sample/Aguacongas.TheIdServer.CibaSample/Aguacongas.TheIdServer.CibaSample.csproj b/sample/Aguacongas.TheIdServer.CibaSample/Aguacongas.TheIdServer.CibaSample.csproj index 198fb6692..2588ecca6 100644 --- a/sample/Aguacongas.TheIdServer.CibaSample/Aguacongas.TheIdServer.CibaSample.csproj +++ b/sample/Aguacongas.TheIdServer.CibaSample/Aguacongas.TheIdServer.CibaSample.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 enable enable false diff --git a/sample/Aguacongas.TheIdServer.ClientCredentialSample/Aguacongas.TheIdServer.ClientCredentialSample.csproj b/sample/Aguacongas.TheIdServer.ClientCredentialSample/Aguacongas.TheIdServer.ClientCredentialSample.csproj index df940b4f9..92efdb381 100644 --- a/sample/Aguacongas.TheIdServer.ClientCredentialSample/Aguacongas.TheIdServer.ClientCredentialSample.csproj +++ b/sample/Aguacongas.TheIdServer.ClientCredentialSample/Aguacongas.TheIdServer.ClientCredentialSample.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 false diff --git a/sample/Aguacongas.TheIdServer.CustomClaimsProvider/Aguacongas.TheIdServer.CustomClaimsProviders.csproj b/sample/Aguacongas.TheIdServer.CustomClaimsProvider/Aguacongas.TheIdServer.CustomClaimsProviders.csproj index 1354352b2..7d4f7dc1b 100644 --- a/sample/Aguacongas.TheIdServer.CustomClaimsProvider/Aguacongas.TheIdServer.CustomClaimsProviders.csproj +++ b/sample/Aguacongas.TheIdServer.CustomClaimsProvider/Aguacongas.TheIdServer.CustomClaimsProviders.csproj @@ -1,14 +1,14 @@ - net7.0 + net8.0 false True - - + + diff --git a/sample/Aguacongas.TheIdServer.DeviceFlowSample/Aguacongas.TheIdServer.DeviceFlowSample.csproj b/sample/Aguacongas.TheIdServer.DeviceFlowSample/Aguacongas.TheIdServer.DeviceFlowSample.csproj index a75e07773..71a021f51 100644 --- a/sample/Aguacongas.TheIdServer.DeviceFlowSample/Aguacongas.TheIdServer.DeviceFlowSample.csproj +++ b/sample/Aguacongas.TheIdServer.DeviceFlowSample/Aguacongas.TheIdServer.DeviceFlowSample.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 false diff --git a/sample/Aguacongas.TheIdServer.MvcClient/Aguacongas.TheIdServer.MvcClient.csproj b/sample/Aguacongas.TheIdServer.MvcClient/Aguacongas.TheIdServer.MvcClient.csproj index e59bcb8a2..4c8189d58 100644 --- a/sample/Aguacongas.TheIdServer.MvcClient/Aguacongas.TheIdServer.MvcClient.csproj +++ b/sample/Aguacongas.TheIdServer.MvcClient/Aguacongas.TheIdServer.MvcClient.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 false @@ -15,9 +15,9 @@ - - - + + + diff --git a/sample/Aguacongas.TheIdServer.WsFederationSample/Aguacongas.TheIdServer.WsFederationSample.csproj b/sample/Aguacongas.TheIdServer.WsFederationSample/Aguacongas.TheIdServer.WsFederationSample.csproj index 180a35961..e53310e45 100644 --- a/sample/Aguacongas.TheIdServer.WsFederationSample/Aguacongas.TheIdServer.WsFederationSample.csproj +++ b/sample/Aguacongas.TheIdServer.WsFederationSample/Aguacongas.TheIdServer.WsFederationSample.csproj @@ -1,14 +1,14 @@ - net7.0 + net8.0 false - - - + + + diff --git a/sample/Aguacongas.TheIdentityServer.SpaSample/Aguacongas.TheIdentityServer.SpaSample.csproj b/sample/Aguacongas.TheIdentityServer.SpaSample/Aguacongas.TheIdentityServer.SpaSample.csproj index a5a81bf08..563fcc24c 100644 --- a/sample/Aguacongas.TheIdentityServer.SpaSample/Aguacongas.TheIdentityServer.SpaSample.csproj +++ b/sample/Aguacongas.TheIdentityServer.SpaSample/Aguacongas.TheIdentityServer.SpaSample.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 @@ -13,11 +13,11 @@ - - - - - + + + + + diff --git a/sample/DPoP/Api/ApiHost.csproj b/sample/DPoP/Api/ApiHost.csproj index bc690473c..30e1eee03 100644 --- a/sample/DPoP/Api/ApiHost.csproj +++ b/sample/DPoP/Api/ApiHost.csproj @@ -1,13 +1,13 @@  - net7.0 + net8.0 - - + + \ No newline at end of file diff --git a/sample/DPoP/Api/DPoP/DPoPJwtBearerEvents.cs b/sample/DPoP/Api/DPoP/DPoPJwtBearerEvents.cs index 64bb8369e..a48b7a0d4 100644 --- a/sample/DPoP/Api/DPoP/DPoPJwtBearerEvents.cs +++ b/sample/DPoP/Api/DPoP/DPoPJwtBearerEvents.cs @@ -1,5 +1,6 @@ using IdentityModel; using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; using System.Text; @@ -130,8 +131,7 @@ public override Task Challenge(JwtBearerChallengeContext context) } } - context.Response.Headers.Add(HeaderNames.WWWAuthenticate, sb.ToString()); - + context.Response.Headers.Append(HeaderNames.WWWAuthenticate, sb.ToString()); if (context.HttpContext.Items.ContainsKey("DPoP-Nonce")) { diff --git a/sample/DPoP/Api/DPoP/DPoPProofValidator.cs b/sample/DPoP/Api/DPoP/DPoPProofValidator.cs index 606a0eaef..7a5c473f3 100644 --- a/sample/DPoP/Api/DPoP/DPoPProofValidator.cs +++ b/sample/DPoP/Api/DPoP/DPoPProofValidator.cs @@ -169,7 +169,7 @@ protected virtual Task ValidateHeaderAsync(DPoPProofValidatonContext context, DP /// /// Validates the signature. /// - protected virtual Task ValidateSignatureAsync(DPoPProofValidatonContext context, DPoPProofValidatonResult result) + protected virtual async Task ValidateSignatureAsync(DPoPProofValidatonContext context, DPoPProofValidatonResult result) { TokenValidationResult tokenValidationResult; @@ -185,14 +185,14 @@ protected virtual Task ValidateSignatureAsync(DPoPProofValidatonContext context, }; var handler = new JsonWebTokenHandler(); - tokenValidationResult = handler.ValidateToken(context.ProofToken, tvp); + tokenValidationResult = await handler.ValidateTokenAsync(context.ProofToken, tvp).ConfigureAwait(false); } catch (Exception ex) { Logger.LogDebug("Error parsing DPoP token: {error}", ex.Message); result.IsError = true; result.ErrorDescription = "Invalid signature on DPoP token."; - return Task.CompletedTask; + return; } if (tokenValidationResult.Exception != null) @@ -200,12 +200,10 @@ protected virtual Task ValidateSignatureAsync(DPoPProofValidatonContext context, Logger.LogDebug("Error parsing DPoP token: {error}", tokenValidationResult.Exception.Message); result.IsError = true; result.ErrorDescription = "Invalid signature on DPoP token."; - return Task.CompletedTask; + return; } result.Payload = tokenValidationResult.Claims; - - return Task.CompletedTask; } /// diff --git a/sample/DPoP/ClientCredentials/ClientCredentials.csproj b/sample/DPoP/ClientCredentials/ClientCredentials.csproj index 7dac37251..9a2fdd13c 100644 --- a/sample/DPoP/ClientCredentials/ClientCredentials.csproj +++ b/sample/DPoP/ClientCredentials/ClientCredentials.csproj @@ -1,13 +1,13 @@  - net7.0 + net8.0 enable - + diff --git a/sample/DPoP/WebClient/WebClient.csproj b/sample/DPoP/WebClient/WebClient.csproj index f6fe79667..a53912fb5 100644 --- a/sample/DPoP/WebClient/WebClient.csproj +++ b/sample/DPoP/WebClient/WebClient.csproj @@ -1,13 +1,13 @@  - net7.0 + net8.0 - - + + \ No newline at end of file diff --git a/sample/MultiTiers/Aguacongas.TheIdServer.Api/Aguacongas.TheIdServer.Api.csproj b/sample/MultiTiers/Aguacongas.TheIdServer.Api/Aguacongas.TheIdServer.Api.csproj index 2cd5fb3f0..a6b70a396 100644 --- a/sample/MultiTiers/Aguacongas.TheIdServer.Api/Aguacongas.TheIdServer.Api.csproj +++ b/sample/MultiTiers/Aguacongas.TheIdServer.Api/Aguacongas.TheIdServer.Api.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 false 3d7ce8dc-f8b3-4d0a-967c-7d1aeead003e Linux @@ -35,24 +35,24 @@ 1701;1702;NU1603 - - - - - - - + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + - - - - + + + + diff --git a/sample/MultiTiers/Aguacongas.TheIdServer.Private/Aguacongas.TheIdServer.Private.csproj b/sample/MultiTiers/Aguacongas.TheIdServer.Private/Aguacongas.TheIdServer.Private.csproj index 6496ddd39..2f45af37a 100644 --- a/sample/MultiTiers/Aguacongas.TheIdServer.Private/Aguacongas.TheIdServer.Private.csproj +++ b/sample/MultiTiers/Aguacongas.TheIdServer.Private/Aguacongas.TheIdServer.Private.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 false Linux ..\.. @@ -23,36 +23,36 @@ 1701;1702;NU1603 - - - + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + - + - - - + + + \ No newline at end of file diff --git a/sample/MultiTiers/Aguacongas.TheIdServer.Private/Quickstart/Account/InvalidReturnUrlException.cs b/sample/MultiTiers/Aguacongas.TheIdServer.Private/Quickstart/Account/InvalidReturnUrlException.cs index d4aa6f918..9a3efc905 100644 --- a/sample/MultiTiers/Aguacongas.TheIdServer.Private/Quickstart/Account/InvalidReturnUrlException.cs +++ b/sample/MultiTiers/Aguacongas.TheIdServer.Private/Quickstart/Account/InvalidReturnUrlException.cs @@ -26,10 +26,5 @@ public InvalidReturnUrlException(string message) : this(message, null) public InvalidReturnUrlException(string message, Exception innerException) : base(message, innerException) { } - - protected InvalidReturnUrlException(SerializationInfo serializationInfo, StreamingContext streamingContext) - : base(serializationInfo, streamingContext) - { - } } } diff --git a/sample/MultiTiers/Aguacongas.TheIdServer.Public/Aguacongas.TheIdServer.Public.csproj b/sample/MultiTiers/Aguacongas.TheIdServer.Public/Aguacongas.TheIdServer.Public.csproj index 79f30b9f8..de6d902fc 100644 --- a/sample/MultiTiers/Aguacongas.TheIdServer.Public/Aguacongas.TheIdServer.Public.csproj +++ b/sample/MultiTiers/Aguacongas.TheIdServer.Public/Aguacongas.TheIdServer.Public.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 false Linux ..\.. @@ -16,32 +16,32 @@ Aguacongas.TheIdServer.Public.ruleset - - - - + + + + - - - - - - - - + + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + - - - - + + + + diff --git a/sample/MultiTiers/Aguacongas.TheIdServer.Public/Quickstart/Account/InvalidReturnUrlException.cs b/sample/MultiTiers/Aguacongas.TheIdServer.Public/Quickstart/Account/InvalidReturnUrlException.cs index d4aa6f918..1d4dd7acb 100644 --- a/sample/MultiTiers/Aguacongas.TheIdServer.Public/Quickstart/Account/InvalidReturnUrlException.cs +++ b/sample/MultiTiers/Aguacongas.TheIdServer.Public/Quickstart/Account/InvalidReturnUrlException.cs @@ -1,6 +1,7 @@ // Project: Aguafrommars/TheIdServer // Copyright (c) 2023 @Olivier Lefebvre using System; +using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; namespace Duende.IdentityServer.Quickstart.UI @@ -10,6 +11,7 @@ namespace Duende.IdentityServer.Quickstart.UI /// /// [Serializable] + [SuppressMessage("Major Code Smell", "S3925:\"ISerializable\" should be implemented correctly", Justification = "Obsolete")] public class InvalidReturnUrlException : Exception { /// @@ -26,10 +28,5 @@ public InvalidReturnUrlException(string message) : this(message, null) public InvalidReturnUrlException(string message, Exception innerException) : base(message, innerException) { } - - protected InvalidReturnUrlException(SerializationInfo serializationInfo, StreamingContext streamingContext) - : base(serializationInfo, streamingContext) - { - } } } diff --git a/src/Aguacongas.TheIdServer.Authentication/Aguacongas.TheIdServer.Authentication.csproj b/src/Aguacongas.TheIdServer.Authentication/Aguacongas.TheIdServer.Authentication.csproj index f54c20c51..28a9c7e13 100644 --- a/src/Aguacongas.TheIdServer.Authentication/Aguacongas.TheIdServer.Authentication.csproj +++ b/src/Aguacongas.TheIdServer.Authentication/Aguacongas.TheIdServer.Authentication.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre Apache-2.0 @@ -15,7 +15,7 @@ - + diff --git a/src/Aguacongas.TheIdServer.BlazorApp/Aguacongas.TheIdServer.BlazorApp.csproj b/src/Aguacongas.TheIdServer.BlazorApp/Aguacongas.TheIdServer.BlazorApp.csproj index 0a475a8c8..c2a012668 100644 --- a/src/Aguacongas.TheIdServer.BlazorApp/Aguacongas.TheIdServer.BlazorApp.csproj +++ b/src/Aguacongas.TheIdServer.BlazorApp/Aguacongas.TheIdServer.BlazorApp.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre Apache-2.0 @@ -15,8 +15,6 @@ true enable enable - - true @@ -35,8 +33,9 @@ - - + + + @@ -63,31 +62,35 @@ - + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Aguacongas.TheIdServer.BlazorApp/App.razor.cs b/src/Aguacongas.TheIdServer.BlazorApp/App.razor.cs index aaea11976..1e3db11f7 100644 --- a/src/Aguacongas.TheIdServer.BlazorApp/App.razor.cs +++ b/src/Aguacongas.TheIdServer.BlazorApp/App.razor.cs @@ -1,6 +1,5 @@ // Project: Aguafrommars/TheIdServer // Copyright (c) 2023 @Olivier Lefebvre -using Aguacongas.TheIdServer.BlazorApp.Services; using Microsoft.AspNetCore.Components.Routing; using System.Reflection; @@ -8,13 +7,13 @@ namespace Aguacongas.TheIdServer.BlazorApp { public partial class App { - private readonly List _lazyLoadedAssemblies = new List + private readonly List _lazyLoadedAssemblies = new() { typeof(Pages.Index).Assembly }; - private readonly string[] _pageKindList = new [] - { + private readonly string[] _pageKindList = + [ "Api", "ApiScope", "Client", @@ -29,7 +28,7 @@ public partial class App "RelyingParties", "RelyingParty", "Settings" - }; + ]; protected override async Task OnInitializedAsync() { @@ -37,27 +36,27 @@ protected override async Task OnInitializedAsync() await _themeService.InitAsync().ConfigureAwait(false); } - private Task OnNavigateAsync(NavigationContext args) + private Task OnNavigateAsync(NavigationContext args) { var path = args.Path.Split("/")[0]; if (path == "protectresource") { - return LoadAssemblyAsync("Aguacongas.TheIdServer.BlazorApp.Pages.Api.dll"); + return LoadAssemblyAsync("Aguacongas.TheIdServer.BlazorApp.Pages.Api.wasm"); } if (path == "identityresource") { - return LoadAssemblyAsync("Aguacongas.TheIdServer.BlazorApp.Pages.Identity.dll"); + return LoadAssemblyAsync("Aguacongas.TheIdServer.BlazorApp.Pages.Identity.wasm"); } var pageKind = _pageKindList.FirstOrDefault(k => path == $"{k.ToLower()}s"); if (pageKind != null) { - return LoadAssemblyAsync($"Aguacongas.TheIdServer.BlazorApp.Pages.{pageKind}s.dll"); + return LoadAssemblyAsync($"Aguacongas.TheIdServer.BlazorApp.Pages.{pageKind}s.wasm"); } - pageKind = _pageKindList.FirstOrDefault(k => path == k.ToLower()); - return pageKind != null ? LoadAssemblyAsync($"Aguacongas.TheIdServer.BlazorApp.Pages.{pageKind}.dll") : Task.CompletedTask; + pageKind = _pageKindList.FirstOrDefault(k => path.Equals(k, StringComparison.InvariantCultureIgnoreCase)); + return pageKind != null ? LoadAssemblyAsync($"Aguacongas.TheIdServer.BlazorApp.Pages.{pageKind}.wasm") : Task.CompletedTask; } private async Task LoadAssemblyAsync(string assemblyName) diff --git a/src/Aguacongas.TheIdServer.BlazorApp/Dockerfile b/src/Aguacongas.TheIdServer.BlazorApp/Dockerfile index 4981a5127..89ba6886a 100644 --- a/src/Aguacongas.TheIdServer.BlazorApp/Dockerfile +++ b/src/Aguacongas.TheIdServer.BlazorApp/Dockerfile @@ -1,7 +1,8 @@ ARG GITHIB_FEED_TOKEN #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. -FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release WORKDIR /src COPY ["nuget.config", "."] COPY ["src/Aguacongas.TheIdServer.BlazorApp/Aguacongas.TheIdServer.BlazorApp.csproj", "src/Aguacongas.TheIdServer.BlazorApp/"] @@ -29,16 +30,16 @@ COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Users/Aguacongas.The COPY ["src/IdentityServer/Aguacongas.IdentityServer.Admin.Http.Store/Aguacongas.IdentityServer.Admin.Http.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.Admin.Http.Store/"] COPY ["src/IdentityServer/Aguacongas.IdentityServer.Store/Aguacongas.IdentityServer.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.Store/"] COPY ["src/Aguacongas.TheIdServer.BlazorApp/nginx.conf", "src/Aguacongas.TheIdServer.BlazorApp/"] -RUN apt-get update -y -RUN apt-get install -y python3 +RUN apt-get update -y && apt-get install -y python3 gcc && apt-get clean RUN dotnet workload install wasm-tools -RUN dotnet restore "src/Aguacongas.TheIdServer.BlazorApp/Aguacongas.TheIdServer.BlazorApp.csproj" +RUN dotnet restore "./src/Aguacongas.TheIdServer.BlazorApp/Aguacongas.TheIdServer.BlazorApp.csproj" COPY . . WORKDIR "/src/src/Aguacongas.TheIdServer.BlazorApp" -RUN dotnet build "Aguacongas.TheIdServer.BlazorApp.csproj" -c Release -o /app/build -p:FileVersion=$FILE_VERSION -p:SourceRevisionId=$SOURCE_VERSION +RUN dotnet build "./Aguacongas.TheIdServer.BlazorApp.csproj" -c "$BUILD_CONFIGURATION" -o /app/build -p:FileVersion="$FILE_VERSION" -p:SourceRevisionId="$SOURCE_VERSION" FROM build AS publish -RUN dotnet publish "Aguacongas.TheIdServer.BlazorApp.csproj" -c Release -o /app/publish -p:FileVersion=$FILE_VERSION -p:SourceRevisionId=$SOURCE_VERSION +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./Aguacongas.TheIdServer.BlazorApp.csproj" -c "$BUILD_CONFIGURATION" -o /app/publish /p:UseAppHost=false -p:FileVersion="$FILE_VERSION" -p:SourceRevisionId="$SOURCE_VERSION" FROM nginx:1.25.3 AS final WORKDIR /usr/share/nginx/html diff --git a/src/Aguacongas.TheIdServer.BlazorApp/README.md b/src/Aguacongas.TheIdServer.BlazorApp/README.md index ab0f5abe5..6f8481959 100644 --- a/src/Aguacongas.TheIdServer.BlazorApp/README.md +++ b/src/Aguacongas.TheIdServer.BlazorApp/README.md @@ -2,178 +2,4 @@ This project is the [Blazor Web Assembly](https//blazor.net) application to manage a TheIdServer instance. -## Installation -### From Docker - -The application is embedded in the [server's Linux image](../Aguacongas.TheIdServer/README.md#from-docker). -If you prefer, you can install the [standalone application'sLinux image](https://hub.docker.com/r/aguacongas/theidserverapp). -This image uses an [nginx](http://nginx.org/) server to host the application. - -### From Github Release - -The application is embedded in the [server's Github release](../Aguacongas.TheIdServer/README.md#from-github-release). -You can choose to install the standalone application by selecting *Aguacongas.TheIdServer.BlazorApp{version}.zip* in the [list of releases](https://github.com/Aguafrommars/TheIdServer/releases). -Unzip in the destination of your choice, and use the server of your choice. - -Read [Host and deploy ASP.NET Core Blazor WebAssembly](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/blazor/webassembly?view=aspnetcore-3.1) for more information. - -### From NuGet packages - -NuGet packages composing the application are available on [nuget.org](https://www.nuget.org/): - -* **Aguacongas.TheIdServer.BlazorApp.Infrastructure** contains application models, services, validators and extensions -* **Aguacongas.TheIdServer.BlazorApp.Components** contains application components -* **Aguacongas.TheIdServer.BlazorApp.Pages.*** contains application pages - -## Configuration - -The application obtains its configuration from appsettings.json and the environment-specific settings from appsettings.{environment}.json. - -**appsettings.json** - -```json -{ - "administratorEmail": "aguacongas@gmail.com", - "apiBaseUrl": "https://localhost:5443/api", - "authenticationPaths": { - "remoteRegisterPath": "/identity/account/register", - "remoteProfilePath": "/identity/account/manage" - }, - "loggingOptions": { - "minimum": "Debug", - "filters": [ - { - "category": "System", - "level": "Warning" - }, - { - "category": "Microsoft", - "level": "Information" - } - ] - }, - "userOptions": { - "roleClaim": "role" - }, - "providerOptions": { - "authority": "https://localhost:5443/", - "clientId": "theidserveradmin", - "defaultScopes": [ - "openid", - "profile", - "theidserveradminapi" - ], - "postLogoutRedirectUri": "https://localhost:5443/authentication/logout-callback", - "redirectUri": "https://localhost:5443/authentication/login-callback", - "responseType": "code" - }, - "settingsOptions": { - "typeName": "Aguacongas.TheIdServer.BlazorApp.Models.ServerConfig, Aguacongas.TheIdServer.BlazorApp.Infrastructure", - "apiUrl": "https://localhost:5443/api/api/configuration" - }, - "menuOptions": { - "showSettings": true - }, - "welcomeContenUrl": "https://localhost:5443/welcome-fragment.html", - "serverSideSessionEnabled": false, - "cibaEnabled": false -} -``` - -For more details, read [ASP.NET Core Blazor hosting model configuration / Blazor WebAssembly / Configuration](https://docs.microsoft.com/en-us/aspnet/core/blazor/hosting-model-configuration?view=aspnetcore-3.1#configuration). - -### apiBaseUrl - -Defines the URL to the API. - -### administratorEmail - -Defines the administrator eMail address. - -### authenticationPaths - -The section **authenticationPaths** is binded to the class `Microsoft.AspNetCore.Components.WebAssembly.Authentication.RemoteAuthenticationApplicationPathsOptions`. -The application doesn't contain pages to register a new user or manage the current user, so we set the **authenticationPaths:remoteRegisterPath** and **authenticationPaths:remoteProfilePath** with their corresponding URL on the identity server. - - For more information, read [ASP.NET Core Blazor WebAssembly additional security scenarios / Customize app routes](https://docs.microsoft.com/en-us/aspnet/core/security/blazor/webassembly/additional-scenarios?view=aspnetcore-3.1#customize-app-routes). - -### loggingOptions - -Defines logging options. - -#### minimum - -Defines the [log minimum level](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel?view=dotnet-plat-ext-3.1). - -#### filters - -Each item in this array adds a log filter by category and [LogLevel](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel?view=dotnet-plat-ext-3.1). - -### userOptions - -The section **userOptions** is bound to the class `Microsoft.AspNetCore.Components.WebAssembly.Authentication.RemoteAuthenticationUserOptions`. -This configuration defines how users are authorized. The application and the API share the same authorization policy. - -* **Is4-Writer** authorizes users in this role to write data. -* **Is4-Reader** permits users in this role to read data. - -**userOptions:roleClaim** define the role claims type. - -### providerOptions - -The section **providerOptions** is binded to the class `Microsoft.AspNetCore.Components.WebAssembly.Authentication.OidcProviderOptions`. -This configuration section defines the application authentication. - -For more details, read [Secure an ASP.NET Core Blazor WebAssembly standalone app with the Authentication library / Authentication service support](https://docs.microsoft.com/en-us/aspnet/core/security/blazor/webassembly/standalone-with-authentication-library?view=aspnetcore-3.1#authentication-service-support). - -### welcomeContenUrl - -Defines the URL to the welcome page content. - -## Welcome page customization - -Except for its title, the home page displays contents read from `welcomeContenUrl` endpoint. - -This endpoint should return an HTML fragment. - -[**sample**](../Aguacongas.TheIdServer/wwwroot/welcome-fragment.html) - -```html -

- This application manage your TheIdServer. -

-

- Visit the github site for doc, source code and issue tracking. -

-

- If you have trouble with login, disable Chromium cookies-without-same-site-must-be-secure flag.
- - chrome://flags/#cookies-without-same-site-must-be-secure -
- This site is running under a free heroku dyno without end-to-end https. -

-

- You can sign-in with alice to have reader/writer access, or bob for a read-only access.
- The password is Pass123$. -

-``` - -## UI Options -### Hide settings menu - -To hide the settings menu, unset **menuOptions:showSettings**. - -### Hide CIBA grant type - -If CIBA is not enabled you can hide the CIBA grant type by unsetting cibaEnabled options. - -### Hide coordinate lifetime with user session checkbox - -If server side sessions are not enable you can hide the coordinate lifetime with user session checkbox in client tokens section by unsetting serverSideSessionEnabled options. - -## Additional resources - -* [ASP.NET Core Blazor hosting model configuration](https://docs.microsoft.com/en-us/aspnet/core/blazor/hosting-model-configuration?view=aspnetcore-3.1#configuration) -* [ASP.NET Core Blazor WebAssembly additional security scenarios](https://docs.microsoft.com/en-us/aspnet/core/security/blazor/webassembly/additional-scenarios?view=aspnetcore-3.1#customize-app-routes) -* [Secure an ASP.NET Core Blazor WebAssembly standalone app with the Authentication library](https://docs.microsoft.com/en-us/aspnet/core/security/blazor/webassembly/standalone-with-authentication-library?view=aspnetcore-3.1#authentication-service-support) -* [LogLevel Enum](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel?view=dotnet-plat-ext-3.1) +[Doc](../../doc/ADMINAPP.md) \ No newline at end of file diff --git a/src/Aguacongas.TheIdServer.BlazorApp/Roots.xml b/src/Aguacongas.TheIdServer.BlazorApp/Roots.xml new file mode 100644 index 000000000..fd65d88e6 --- /dev/null +++ b/src/Aguacongas.TheIdServer.BlazorApp/Roots.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/Aguacongas.TheIdServer.BlazorApp/wwwroot/appsettings.heroku-duende.json b/src/Aguacongas.TheIdServer.BlazorApp/wwwroot/appsettings.heroku-duende.json index 11c72fca8..2d8b9a53f 100644 --- a/src/Aguacongas.TheIdServer.BlazorApp/wwwroot/appsettings.heroku-duende.json +++ b/src/Aguacongas.TheIdServer.BlazorApp/wwwroot/appsettings.heroku-duende.json @@ -14,7 +14,7 @@ ] }, "settingsOptions": { - "apiUrl": "https://theidserver-duende.herokuapp.com/api/api/configuration" + "apiUrl": "https://theidserver-duende.herokuapp.com/api/configuration" }, "providerOptions": { "authority": "https://theidserver-duende.herokuapp.com/", diff --git a/src/Aguacongas.TheIdServer.BlazorApp/wwwroot/appsettings.heroku.json b/src/Aguacongas.TheIdServer.BlazorApp/wwwroot/appsettings.heroku.json index 9b0b8afe1..6efa69dc2 100644 --- a/src/Aguacongas.TheIdServer.BlazorApp/wwwroot/appsettings.heroku.json +++ b/src/Aguacongas.TheIdServer.BlazorApp/wwwroot/appsettings.heroku.json @@ -14,7 +14,7 @@ ] }, "settingsOptions": { - "apiUrl": "https://theidserver.herokuapp.com/api/api/configuration" + "apiUrl": "https://theidserver.herokuapp.com/api/configuration" }, "providerOptions": { "authority": "https://theidserver.herokuapp.com/", diff --git a/src/Aguacongas.TheIdServer.BlazorApp/wwwroot/appsettings.json b/src/Aguacongas.TheIdServer.BlazorApp/wwwroot/appsettings.json index 6287195a2..5141a3f76 100644 --- a/src/Aguacongas.TheIdServer.BlazorApp/wwwroot/appsettings.json +++ b/src/Aguacongas.TheIdServer.BlazorApp/wwwroot/appsettings.json @@ -27,7 +27,7 @@ "welcomeContenUrl": "https://localhost:5443/api/welcomefragment", "settingsOptions": { "typeName": "Aguacongas.TheIdServer.BlazorApp.Models.ServerConfig, Aguacongas.TheIdServer.BlazorApp.Infrastructure", - "apiUrl": "https://localhost:5443/api/api/configuration" + "apiUrl": "https://localhost:5443/api/configuration" }, "menuOptions": { "showSettings": true diff --git a/src/Aguacongas.TheIdServer.Duende/Aguacongas.TheIdServer.Duende.csproj b/src/Aguacongas.TheIdServer.Duende/Aguacongas.TheIdServer.Duende.csproj index ceaa651e1..c68cc5f67 100644 --- a/src/Aguacongas.TheIdServer.Duende/Aguacongas.TheIdServer.Duende.csproj +++ b/src/Aguacongas.TheIdServer.Duende/Aguacongas.TheIdServer.Duende.csproj @@ -1,13 +1,15 @@ - net7.0 + net8.0 51a7f0be-96e4-42d5-ad09-37e9adabfff6 Linux ..\.. True - + enable + enable + $(DefineConstants)TRACE @@ -32,6 +34,7 @@ + @@ -39,10 +42,15 @@ + + + + - - + + + diff --git a/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/LoginWith2fa.cshtml b/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/LoginWith2fa.cshtml index e331ec798..0a412a59a 100644 --- a/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/LoginWith2fa.cshtml +++ b/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/LoginWith2fa.cshtml @@ -31,15 +31,15 @@ else
- - - + + +
-
@@ -50,7 +50,7 @@ else

- @Localizer["Don't have access to your authenticator device? You can log in with a recovery code.", UrlHelper.Page("./LoginWithRecoveryCode", new { returnUrl = Model.ReturnUrl })] + @Localizer["Don't have access to your authenticator device? You can log in with a recovery code.", UrlHelper.Page("./LoginWithRecoveryCode", new { returnUrl = Model.ReturnUrl })!]

@section Scripts { diff --git a/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/LoginWith2fa.cshtml.cs b/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/LoginWith2fa.cshtml.cs index 7e69b37e3..d55341583 100644 --- a/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/LoginWith2fa.cshtml.cs +++ b/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/LoginWith2fa.cshtml.cs @@ -41,11 +41,11 @@ public LoginWith2faModel(SignInManager signInManager, } [BindProperty] - public InputModel Input { get; set; } + public InputModel? Input { get; set; } public bool RememberMe { get; set; } - public string ReturnUrl { get; set; } + public string? ReturnUrl { get; set; } public bool RedirectToReturnUrl { get; set; } @@ -55,13 +55,13 @@ public class InputModel [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] [DataType(DataType.Text)] [Display(Name = "Authenticator code")] - public string TwoFactorCode { get; set; } + public string? TwoFactorCode { get; set; } [Display(Name = "Remember this machine")] public bool RememberMachine { get; set; } } - public async Task OnGetAsync(bool rememberMe, string returnUrl = null) + public async Task OnGetAsync(bool rememberMe, string? returnUrl = null) { // Ensure the user has gone through the username & password screen first var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); @@ -77,7 +77,7 @@ public async Task OnGetAsync(bool rememberMe, string returnUrl = return Page(); } - public async Task OnPostAsync(string userName, bool rememberMe, string returnUrl = null) + public async Task OnPostAsync(string userName, bool rememberMe, string? returnUrl = null) { if (!ModelState.IsValid) { @@ -92,7 +92,7 @@ public async Task OnPostAsync(string userName, bool rememberMe, s throw new InvalidOperationException($"Unable to load two-factor authentication user."); } - var authenticatorCode = Input.TwoFactorCode.Replace(" ", string.Empty).Replace("-", string.Empty); + var authenticatorCode = Input!.TwoFactorCode!.Replace(" ", string.Empty).Replace("-", string.Empty); var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, rememberMe, Input.RememberMachine); diff --git a/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/Manage/Ciba.cshtml b/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/Manage/Ciba.cshtml index 1379d9749..a7c1c783f 100644 --- a/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/Manage/Ciba.cshtml +++ b/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/Manage/Ciba.cshtml @@ -14,7 +14,7 @@ Copyright (c) 2023 @Olivier Lefebvre

@ViewData["Title"]

@Localizer["Below is the list of backchannel login requests awaiting your approbation."]
-@if (Model.Logins.Any() == true) +@if (Model.Logins?.Any() == true) { diff --git a/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/Manage/Ciba.cshtml.cs b/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/Manage/Ciba.cshtml.cs index 049fbad23..7129a607a 100644 --- a/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/Manage/Ciba.cshtml.cs +++ b/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/Manage/Ciba.cshtml.cs @@ -10,12 +10,12 @@ namespace Aguacongas.TheIdServer.Areas.Identity.Pages.Account.Manage { public class CibaModel : PageModel { - public IEnumerable Logins { get; set; } + public IEnumerable? Logins { get; set; } [BindProperty, Required] - public string Id { get; set; } + public string? Id { get; set; } [BindProperty, Required] - public string Button { get; set; } + public string? Button { get; set; } private readonly IBackchannelAuthenticationInteractionService _backchannelAuthenticationInteraction; diff --git a/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/Manage/Grants.cshtml b/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/Manage/Grants.cshtml index 8a8b30908..331d344aa 100644 --- a/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/Manage/Grants.cshtml +++ b/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/Manage/Grants.cshtml @@ -21,7 +21,7 @@ Copyright (c) 2023 @Olivier Lefebvre
- @if (Model.Grants.Any() == false) + @if (Model.Grants?.Any() == false) {
@@ -33,7 +33,7 @@ Copyright (c) 2023 @Olivier Lefebvre } else { - foreach (var grant in Model.Grants) + foreach (var grant in Model.Grants!) {
@@ -57,7 +57,7 @@ Copyright (c) 2023 @Olivier Lefebvre @Localizer["Expires:"] @grant.Expires.Value.ToString("yyyy-MM-dd")
} - @if (grant.IdentityGrantNames.Any()) + @if (grant.IdentityGrantNames?.Any() == true) {
@Localizer["Identity Grants"]
@@ -69,7 +69,7 @@ Copyright (c) 2023 @Olivier Lefebvre
} - @if (grant.ApiGrantNames.Any()) + @if (grant.ApiGrantNames?.Any() == true) {
@Localizer["API Grants"]
diff --git a/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/Manage/Grants.cshtml.cs b/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/Manage/Grants.cshtml.cs index 448680b9a..2f03c2d31 100644 --- a/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/Manage/Grants.cshtml.cs +++ b/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/Manage/Grants.cshtml.cs @@ -20,7 +20,7 @@ public class GrantsModel : PageModel private readonly IResourceStore _resources; private readonly IEventService _events; - public IEnumerable Grants { get; set; } + public IEnumerable? Grants { get; set; } public GrantsModel(IIdentityServerInteractionService interaction, IClientStore clients, @@ -80,14 +80,14 @@ private async Task BuildViewModelAsync() 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? ClientId { get; set; } + public string? ClientName { get; set; } + public string? ClientUrl { get; set; } + public string? ClientLogoUrl { get; set; } public DateTime Created { get; set; } public DateTime? Expires { get; set; } - public IEnumerable IdentityGrantNames { get; set; } - public IEnumerable ApiGrantNames { get; set; } + public IEnumerable? IdentityGrantNames { get; set; } + public IEnumerable? ApiGrantNames { get; set; } } } } diff --git a/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/Manage/Sessions.cshtml.cs b/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/Manage/Sessions.cshtml.cs index 8b0883870..eec06a766 100644 --- a/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/Manage/Sessions.cshtml.cs +++ b/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/Manage/Sessions.cshtml.cs @@ -11,12 +11,12 @@ namespace Aguacongas.TheIdServer.Areas.Identity.Pages.Account.Manage { public class SessionModel : PageModel { - public QueryResult Sessions { get; set; } + public QueryResult? Sessions { get; set; } [BindProperty, Required] - public string Id { get; set; } + public string? Id { get; set; } [BindProperty, Required] - public string Button { get; set; } + public string? Button { get; set; } private readonly ISessionManagementService _sessionManagementService; private readonly IClientStore _clients; @@ -31,7 +31,7 @@ public async Task OnGetAsync() { Sessions = await _sessionManagementService.QuerySessionsAsync(new SessionQuery { - SubjectId = User.FindFirst("sub").Value + SubjectId = User.FindFirst("sub")!.Value }).ConfigureAwait(false); return Page(); @@ -50,12 +50,12 @@ await _sessionManagementService.RemoveSessionsAsync(new RemoveSessionsContext } public string GetActivePageClass(int index) - => index == Sessions.CurrentPage ? "active" : string.Empty; + => index == Sessions!.CurrentPage ? "active" : string.Empty; public string GetPreviousPageClass() - => Sessions.HasPrevResults ? string.Empty : "disabled"; + => Sessions!.HasPrevResults ? string.Empty : "disabled"; public string GetNextPageClass() - => Sessions.HasNextResults ? string.Empty : "disabled"; + => Sessions!.HasNextResults ? string.Empty : "disabled"; } } diff --git a/src/Aguacongas.TheIdServer.Duende/Config.cs b/src/Aguacongas.TheIdServer.Duende/Config.cs index 58c70cdba..febaadde7 100644 --- a/src/Aguacongas.TheIdServer.Duende/Config.cs +++ b/src/Aguacongas.TheIdServer.Duende/Config.cs @@ -2,16 +2,14 @@ // Copyright (c) 2023 @Olivier Lefebvre using Duende.IdentityServer; using Duende.IdentityServer.Models; -using Microsoft.Extensions.Configuration; -using System; -using System.Collections.Generic; -using System.Linq; using Entity = Aguacongas.IdentityServer.Store.Entity; namespace Aguacongas.TheIdServer { public static class Config { + internal static string[] SeedPage { get; } = ["/seed"]; + public static IEnumerable GetIdentityResources() { var profile = new IdentityResources.Profile(); diff --git a/src/Aguacongas.TheIdServer.Duende/Dockerfile b/src/Aguacongas.TheIdServer.Duende/Dockerfile index a13a97436..92e166eea 100644 --- a/src/Aguacongas.TheIdServer.Duende/Dockerfile +++ b/src/Aguacongas.TheIdServer.Duende/Dockerfile @@ -1,74 +1,79 @@ -#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. -FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base +#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. + +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +USER app WORKDIR /app EXPOSE 80 EXPOSE 443 -FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release WORKDIR /src COPY ["nuget.config", "."] COPY ["src/Aguacongas.TheIdServer.Duende/Aguacongas.TheIdServer.Duende.csproj", "src/Aguacongas.TheIdServer.Duende/"] -COPY ["src/IdentityServer/Duende/Aguacongas.IdentityServer.Admin.Duende/Aguacongas.IdentityServer.Admin.Duende.csproj", "src/IdentityServer/Duende/Aguacongas.IdentityServer.Admin.Duende/"] -COPY ["src/IdentityServer/Duende/Aguacongas.IdentityServer.KeysRotation.Duende/Aguacongas.IdentityServer.KeysRotation.Duende.csproj", "src/IdentityServer/Duende/Aguacongas.IdentityServer.KeysRotation.Duende/"] -COPY ["src/IdentityServer/Aguacongas.IdentityServer.KeysRotation/Aguacongas.IdentityServer.KeysRotation.csproj", "src/IdentityServer/Aguacongas.IdentityServer.KeysRotation/"] -COPY ["src/IdentityServer/Aguacongas.IdentityServer.Admin/Aguacongas.IdentityServer.Admin.csproj", "src/IdentityServer/Aguacongas.IdentityServer.Admin/"] -COPY ["src/IdentityServer/Aguacongas.IdentityServer/Aguacongas.IdentityServer.csproj", "src/IdentityServer/Aguacongas.IdentityServer/"] -COPY ["src/Aguacongas.TheIdServer.Authentication/Aguacongas.TheIdServer.Authentication.csproj", "src/Aguacongas.TheIdServer.Authentication/"] -COPY ["src/IdentityServer/Aguacongas.IdentityServer.Store/Aguacongas.IdentityServer.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.Store/"] -COPY ["src/Aguacongas.TheIdServer.Identity/Aguacongas.TheIdServer.Identity.csproj", "src/Aguacongas.TheIdServer.Identity/"] -COPY ["src/IdentityServer/Duende/Aguacongas.IdentityServer.Duende/Aguacongas.IdentityServer.Duende.csproj", "src/IdentityServer/Duende/Aguacongas.IdentityServer.Duende/"] -COPY ["src/IdentityServer/Duende/Aguacongas.IdentityServer.EntityFramework.Store.Duende/Aguacongas.IdentityServer.EntityFramework.Store.Duende.csproj", "src/IdentityServer/Duende/Aguacongas.IdentityServer.EntityFramework.Store.Duende/"] -COPY ["src/IdentityServer/Aguacongas.IdentityServer.EntityFramework.Store/Aguacongas.IdentityServer.EntityFramework.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.EntityFramework.Store/"] -COPY ["src/Aguacongas.TheIdServer/Aguacongas.TheIdServer.csproj", "src/Aguacongas.TheIdServer/"] -COPY ["src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Aguacongas.TheIdServer.Migrations.Sqlite.csproj", "src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/"] -COPY ["src/IdentityServer/Aguacongas.IdentityServer.Http.Store/Aguacongas.IdentityServer.Http.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.Http.Store/"] -COPY ["src/IdentityServer/Aguacongas.IdentityServer.Admin.Http.Store/Aguacongas.IdentityServer.Admin.Http.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.Admin.Http.Store/"] -COPY ["src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Aguacongas.TheIdServer.Migrations.SqlServer.csproj", "src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/"] -COPY ["src/IdentityServer/Aguacongas.IdentityServer.MongoDb.Store/Aguacongas.IdentityServer.MongoDb.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.MongoDb.Store/"] -COPY ["src/IdentityServer/Aguacongas.IdentityServer.WsFederation/Aguacongas.IdentityServer.WsFederation.csproj", "src/IdentityServer/Aguacongas.IdentityServer.WsFederation/"] COPY ["src/Aguacongas.TheIdServer.BlazorApp/Aguacongas.TheIdServer.BlazorApp.csproj", "src/Aguacongas.TheIdServer.BlazorApp/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScope/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScope.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScope/"] +COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScopes/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScopes.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScopes/"] COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Components/Aguacongas.TheIdServer.BlazorApp.Components.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Components/"] COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Infrastructure/Aguacongas.TheIdServer.BlazorApp.Infrastructure.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Infrastructure/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParties/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParties.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParties/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Identity/Aguacongas.TheIdServer.BlazorApp.Pages.Identity.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Identity/"] +COPY ["src/IdentityServer/Aguacongas.IdentityServer.Admin.Http.Store/Aguacongas.IdentityServer.Admin.Http.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.Admin.Http.Store/"] +COPY ["src/IdentityServer/Aguacongas.IdentityServer.Store/Aguacongas.IdentityServer.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.Store/"] +COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScope/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScope.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScope/"] +COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Apis/Aguacongas.TheIdServer.BlazorApp.Pages.Apis.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Apis/"] COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Api/Aguacongas.TheIdServer.BlazorApp.Pages.Api.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Api/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Role/Aguacongas.TheIdServer.BlazorApp.Pages.Role.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Role/"] +COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Clients/Aguacongas.TheIdServer.BlazorApp.Pages.Clients.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Clients/"] +COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Client/Aguacongas.TheIdServer.BlazorApp.Pages.Client.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Client/"] +COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Cultures/Aguacongas.TheIdServer.BlazorApp.Pages.Cultures.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Cultures/"] +COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Culture/Aguacongas.TheIdServer.BlazorApp.Pages.Culture.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Culture/"] +COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProviders/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProviders.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProviders/"] +COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProvider/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProvider.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProvider/"] COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Identities/Aguacongas.TheIdServer.BlazorApp.Pages.Identities.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Identities/"] +COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Identity/Aguacongas.TheIdServer.BlazorApp.Pages.Identity.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Identity/"] COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Import/Aguacongas.TheIdServer.BlazorApp.Pages.Import.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Import/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProvider/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProvider.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProvider/"] +COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Keys/Aguacongas.TheIdServer.BlazorApp.Pages.Keys.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Keys/"] +COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParties/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParties.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParties/"] COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParty/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParty.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParty/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProviders/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProviders.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProviders/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages/Aguacongas.TheIdServer.BlazorApp.Pages.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScopes/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScopes.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScopes/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Culture/Aguacongas.TheIdServer.BlazorApp.Pages.Culture.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Culture/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Clients/Aguacongas.TheIdServer.BlazorApp.Pages.Clients.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Clients/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Cultures/Aguacongas.TheIdServer.BlazorApp.Pages.Cultures.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Cultures/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Apis/Aguacongas.TheIdServer.BlazorApp.Pages.Apis.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Apis/"] COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Roles/Aguacongas.TheIdServer.BlazorApp.Pages.Roles.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Roles/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Client/Aguacongas.TheIdServer.BlazorApp.Pages.Client.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Client/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Keys/Aguacongas.TheIdServer.BlazorApp.Pages.Keys.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Keys/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.User/Aguacongas.TheIdServer.BlazorApp.Pages.User.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.User/"] +COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Role/Aguacongas.TheIdServer.BlazorApp.Pages.Role.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Role/"] +COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Settings/Aguacongas.TheIdServer.BlazorApp.Pages.Settings.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Settings/"] COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Users/Aguacongas.TheIdServer.BlazorApp.Pages.Users.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Users/"] -COPY ["src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Oracle/Aguacongas.TheIdServer.Migrations.Oracle.csproj", "src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Oracle/"] +COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.User/Aguacongas.TheIdServer.BlazorApp.Pages.User.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.User/"] +COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages/Aguacongas.TheIdServer.BlazorApp.Pages.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages/"] +COPY ["src/Aguacongas.TheIdServer/Aguacongas.TheIdServer.csproj", "src/Aguacongas.TheIdServer/"] +COPY ["src/Aguacongas.TheIdServer.Authentication/Aguacongas.TheIdServer.Authentication.csproj", "src/Aguacongas.TheIdServer.Authentication/"] +COPY ["src/Aguacongas.TheIdServer.Identity/Aguacongas.TheIdServer.Identity.csproj", "src/Aguacongas.TheIdServer.Identity/"] +COPY ["src/IdentityServer/Aguacongas.IdentityServer.Admin/Aguacongas.IdentityServer.Admin.csproj", "src/IdentityServer/Aguacongas.IdentityServer.Admin/"] +COPY ["src/IdentityServer/Aguacongas.IdentityServer.KeysRotation/Aguacongas.IdentityServer.KeysRotation.csproj", "src/IdentityServer/Aguacongas.IdentityServer.KeysRotation/"] +COPY ["src/IdentityServer/Aguacongas.IdentityServer/Aguacongas.IdentityServer.csproj", "src/IdentityServer/Aguacongas.IdentityServer/"] +COPY ["src/IdentityServer/Aguacongas.IdentityServer.EntityFramework.Store/Aguacongas.IdentityServer.EntityFramework.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.EntityFramework.Store/"] +COPY ["src/IdentityServer/Aguacongas.IdentityServer.Http.Store/Aguacongas.IdentityServer.Http.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.Http.Store/"] +COPY ["src/IdentityServer/Aguacongas.IdentityServer.MongoDb.Store/Aguacongas.IdentityServer.MongoDb.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.MongoDb.Store/"] +COPY ["src/IdentityServer/Aguacongas.IdentityServer.RavenDb.Store/Aguacongas.IdentityServer.RavenDb.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.RavenDb.Store/"] +COPY ["src/IdentityServer/Aguacongas.IdentityServer.WsFederation/Aguacongas.IdentityServer.WsFederation.csproj", "src/IdentityServer/Aguacongas.IdentityServer.WsFederation/"] COPY ["src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.MySql/Aguacongas.TheIdServer.Migrations.MySql.csproj", "src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.MySql/"] +COPY ["src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Oracle/Aguacongas.TheIdServer.Migrations.Oracle.csproj", "src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Oracle/"] COPY ["src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/Aguacongas.TheIdServer.Migrations.PostgreSQL.csproj", "src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/"] -COPY ["src/IdentityServer/Aguacongas.IdentityServer.RavenDb.Store/Aguacongas.IdentityServer.RavenDb.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.RavenDb.Store/"] -COPY ["src/IdentityServer/Duende/Aguacongas.IdentityServer.RavenDb.Store.Duende/Aguacongas.IdentityServer.RavenDb.Store.Duende.csproj", "src/IdentityServer/Duende/Aguacongas.IdentityServer.RavenDb.Store.Duende/"] +COPY ["src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Aguacongas.TheIdServer.Migrations.Sqlite.csproj", "src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/"] +COPY ["src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Aguacongas.TheIdServer.Migrations.SqlServer.csproj", "src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/"] +COPY ["src/IdentityServer/Duende/Aguacongas.IdentityServer.Admin.Duende/Aguacongas.IdentityServer.Admin.Duende.csproj", "src/IdentityServer/Duende/Aguacongas.IdentityServer.Admin.Duende/"] +COPY ["src/IdentityServer/Duende/Aguacongas.IdentityServer.Duende/Aguacongas.IdentityServer.Duende.csproj", "src/IdentityServer/Duende/Aguacongas.IdentityServer.Duende/"] +COPY ["src/IdentityServer/Duende/Aguacongas.IdentityServer.KeysRotation.Duende/Aguacongas.IdentityServer.KeysRotation.Duende.csproj", "src/IdentityServer/Duende/Aguacongas.IdentityServer.KeysRotation.Duende/"] +COPY ["src/IdentityServer/Duende/Aguacongas.IdentityServer.EntityFramework.Store.Duende/Aguacongas.IdentityServer.EntityFramework.Store.Duende.csproj", "src/IdentityServer/Duende/Aguacongas.IdentityServer.EntityFramework.Store.Duende/"] COPY ["src/IdentityServer/Duende/Aguacongas.IdentityServer.MongoDb.Store.Duende/Aguacongas.IdentityServer.MongoDb.Store.Duende.csproj", "src/IdentityServer/Duende/Aguacongas.IdentityServer.MongoDb.Store.Duende/"] +COPY ["src/IdentityServer/Duende/Aguacongas.IdentityServer.RavenDb.Store.Duende/Aguacongas.IdentityServer.RavenDb.Store.Duende.csproj", "src/IdentityServer/Duende/Aguacongas.IdentityServer.RavenDb.Store.Duende/"] +COPY ["src/IdentityServer/Duende/Aguacongas.IdentityServer.Saml2p.Duende/Aguacongas.IdentityServer.Saml2p.Duende.csproj", "src/IdentityServer/Duende/Aguacongas.IdentityServer.Saml2p.Duende/"] COPY ["src/IdentityServer/Duende/Aguacongas.IdentityServer.WsFederation.Duende/Aguacongas.IdentityServer.WsFederation.Duende.csproj", "src/IdentityServer/Duende/Aguacongas.IdentityServer.WsFederation.Duende/"] -RUN apt-get update -y -RUN apt-get install -y python3 +RUN apt-get update -y && apt-get install -y python3 gcc && apt-get clean RUN dotnet workload install wasm-tools -RUN dotnet restore "src/Aguacongas.TheIdServer.Duende/Aguacongas.TheIdServer.Duende.csproj" +RUN dotnet restore "./src/Aguacongas.TheIdServer.Duende/./Aguacongas.TheIdServer.Duende.csproj" COPY . . WORKDIR "/src/src/Aguacongas.TheIdServer.Duende" -RUN dotnet build "Aguacongas.TheIdServer.Duende.csproj" -c Release -o /app/build -p:FileVersion=$FILE_VERSION -p:SourceRevisionId=$SOURCE_VERSION +RUN dotnet build "./Aguacongas.TheIdServer.Duende.csproj" -c "$BUILD_CONFIGURATION" -o /app/build -p:FileVersion="$FILE_VERSION" -p:SourceRevisionId="$SOURCE_VERSION" FROM build AS publish -RUN dotnet publish "Aguacongas.TheIdServer.Duende.csproj" -c Release -o /app/publish -p:FileVersion=$FILE_VERSION -p:SourceRevisionId=$SOURCE_VERSION +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./Aguacongas.TheIdServer.Duende.csproj" -c "$BUILD_CONFIGURATION" -o /app/publish /p:UseAppHost=false -p:FileVersion="$FILE_VERSION" -p:SourceRevisionId="$SOURCE_VERSION" FROM base AS final WORKDIR /app COPY --from=publish /app/publish . -ENTRYPOINT ["dotnet", "Aguacongas.TheIdServer.Duende.dll"] +ENTRYPOINT ["dotnet", "Aguacongas.TheIdServer.Duende.dll"] \ No newline at end of file diff --git a/src/Aguacongas.TheIdServer.Duende/Extensions/ApplicationBuilderExtensions.cs b/src/Aguacongas.TheIdServer.Duende/Extensions/ApplicationBuilderExtensions.cs index cefd9fac7..2c6b1677e 100644 --- a/src/Aguacongas.TheIdServer.Duende/Extensions/ApplicationBuilderExtensions.cs +++ b/src/Aguacongas.TheIdServer.Duende/Extensions/ApplicationBuilderExtensions.cs @@ -7,45 +7,40 @@ using Aguacongas.TheIdServer; using Aguacongas.TheIdServer.Admin.Hubs; using Aguacongas.TheIdServer.Authentication; +using Aguacongas.TheIdServer.BlazorApp; using Aguacongas.TheIdServer.BlazorApp.Models; using Aguacongas.TheIdServer.Data; using Aguacongas.TheIdServer.Models; using Aguacongas.TheIdServer.Options.OpenTelemetry; -using Duende.IdentityServer.Hosting; using Duende.IdentityServer.Configuration; -using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Components.WebAssembly.Authentication; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.AspNetCore.Diagnostics.HealthChecks; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Localization; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; using Serilog; -using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; -using System.IO; -using System.Linq; using System.Net; +using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; -using System.Threading.Tasks; -using System; -using Microsoft.Extensions.Logging; -using System.Security.Cryptography; namespace Microsoft.AspNetCore.Builder { public static class ApplicationBuilderExtensions { + private static JsonSerializerOptions _serializerOptions = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + WriteIndented = true + }; + public static IApplicationBuilder UseTheIdServer(this IApplicationBuilder app, IWebHostEnvironment environment, IConfiguration configuration) { var isProxy = configuration.GetValue("Proxy"); @@ -74,7 +69,8 @@ public static IApplicationBuilder UseTheIdServer(this IApplicationBuilder app, I if (environment.IsDevelopment()) { app.UseDeveloperExceptionPage() - .UseMigrationsEndPoint(); + .UseMigrationsEndPoint() + .UseWebAssemblyDebugging(); } else { @@ -109,17 +105,19 @@ public static IApplicationBuilder UseTheIdServer(this IApplicationBuilder app, I .UseIdentityServerAdminApi("/api", child => { ConfigureAdminApiHandler(configuration, child); - }) - .UseRouting(); + }); - app.UseIdentityServer(); + app.UseIdentityServer() + .UseRouting() + .UseAuthentication(); if (!isProxy) { - app.UseIdentityServerAdminAuthentication("/providerhub", JwtBearerDefaults.AuthenticationScheme); + app.UseIdentityServerAdminAuthentication("/providerhub"); } - app.UseAuthorization() + app.UseIdentityServerAdminAuthentication() + .UseAuthorization() .Use((context, next) => { var service = context.RequestServices; @@ -134,6 +132,7 @@ public static IApplicationBuilder UseTheIdServer(this IApplicationBuilder app, I .UsePrometheus(configuration) .UseEndpoints(endpoints => { + endpoints.MapAdminApiControllers(); endpoints.MapHealthChecks("healthz", new HealthCheckOptions { ResponseWriter = WriteHealtResponse @@ -146,34 +145,60 @@ public static IApplicationBuilder UseTheIdServer(this IApplicationBuilder app, I endpoints.MapHub("/providerhub"); } endpoints.MapFallbackToPage("/_Host"); - }) - .LoadDynamicAuthenticationConfiguration(); + endpoints.MapRazorComponents() + .AddInteractiveWebAssemblyRenderMode(); + }); + - return app; + return app.LoadDynamicAuthenticationConfiguration(); } private static void ConfigureAdminApiHandler(IConfiguration configuration, IApplicationBuilder child) { if (configuration.GetValue("EnableOpenApiDoc")) { - child.UseOpenApi() - .UseSwaggerUi3(options => - { - var settings = configuration.GetSection("SwaggerUiSettings").Get(); - options.OAuth2Client = settings.OAuth2Client; - }); + ConfigureOpentApi(configuration, child); } var allowedOrigin = configuration.GetSection("CorsAllowedOrigin").Get>(); - if (allowedOrigin != null) + if (allowedOrigin is null) + { + return; + } + + child.UseCors(configure => { - child.UseCors(configure => + configure.SetIsOriginAllowed(origin => allowedOrigin.Any(o => o == origin)) + .AllowAnyMethod() + .AllowAnyHeader() + .AllowCredentials(); + }); + } + + private static void ConfigureOpentApi(IConfiguration configuration, IApplicationBuilder child) + { + child.UseOpenApi( + options => + { + var settings = configuration.GetSection("SwaggerUiSettings").Get(); + if (settings?.Path is not null) + { + var path = settings.Path; + path = path.EndsWith('/') ? path : $"{path}/"; + options.Path = $"{settings.Path}{{documentName}}/swagger.json"; + } + }) + .UseSwaggerUi3(options => { - configure.SetIsOriginAllowed(origin => allowedOrigin.Any(o => o == origin)) - .AllowAnyMethod() - .AllowAnyHeader() - .AllowCredentials(); + var settings = configuration.GetSection("SwaggerUiSettings").Get(); + options.OAuth2Client = settings?.OAuth2Client; + options.Path = settings?.Path; + if (settings?.Path is not null) + { + var path = settings.Path; + path = path.EndsWith('/') ? path : $"{path}/"; + options.DocumentPath = $"{settings.Path}{{documentName}}/swagger.json"; + } }); - } } private static IApplicationBuilder UseClientCerificate(this IApplicationBuilder app, string certificateHeader) @@ -194,7 +219,7 @@ private static IApplicationBuilder UseClientCerificate(this IApplicationBuilder requestLogger.LogInformation("Get certificate from header {ClientCertificateHeader}", certificateHeader); try { - context.Connection.ClientCertificate = X509Certificate2.CreateFromPem(Uri.UnescapeDataString(values[0])); + context.Connection.ClientCertificate = X509Certificate2.CreateFromPem(Uri.UnescapeDataString(values[0]!)); } catch (CryptographicException e) { @@ -257,8 +282,8 @@ private static void ConfigureInitialData(IApplicationBuilder app, IConfiguration var opContext = scope.ServiceProvider.GetRequiredService(); opContext.Database.Migrate(); - var appcontext = scope.ServiceProvider.GetService(); - appcontext.Database.Migrate(); + var appContext = scope.ServiceProvider.GetRequiredService(); + appContext.Database.Migrate(); } if (configuration.GetValue("Seed")) @@ -297,12 +322,6 @@ private static Task WriteHealtResponse(HttpContext context, HealthReport healthR if (value.Data.Any()) { - var serializerOptions = new JsonSerializerOptions - { - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - WriteIndented = true - }; - jsonWriter.WriteStartObject("data"); foreach (var item in value.Data) @@ -310,7 +329,7 @@ private static Task WriteHealtResponse(HttpContext context, HealthReport healthR jsonWriter.WritePropertyName(item.Key); JsonSerializer.Serialize(jsonWriter, item.Value, - item.Value?.GetType() ?? typeof(object), serializerOptions); + item.Value?.GetType() ?? typeof(object), _serializerOptions); } jsonWriter.WriteEndObject(); diff --git a/src/Aguacongas.TheIdServer.Duende/Extensions/IdentityServerBuilderExtensions.cs b/src/Aguacongas.TheIdServer.Duende/Extensions/IdentityServerBuilderExtensions.cs index d86ca68dd..42635cbce 100644 --- a/src/Aguacongas.TheIdServer.Duende/Extensions/IdentityServerBuilderExtensions.cs +++ b/src/Aguacongas.TheIdServer.Duende/Extensions/IdentityServerBuilderExtensions.cs @@ -5,13 +5,9 @@ using Aguacongas.IdentityServer.KeysRotation; using Aguacongas.IdentityServer.KeysRotation.Extensions; using Aguacongas.TheIdServer.Models; -using Microsoft.Extensions.Configuration; +using Duende.IdentityServer.Configuration; using StackExchange.Redis; -using System; -using System.Collections.Generic; -using System.IO; using System.Security.Cryptography.X509Certificates; -using Duende.IdentityServer.Configuration; using static Duende.IdentityServer.IdentityServerConstants; namespace Microsoft.Extensions.DependencyInjection @@ -63,7 +59,7 @@ public static IIdentityServerBuilder ConfigureKey(this IIdentityServerBuilder id additionalSigningKeyTypeSection.Bind(additionalKeyType); foreach (var key in additionalKeyType.Keys) { - if (key.StartsWith("E")) + if (key.StartsWith('E')) { var ecdsa = Enum.Parse(key); builder.AddECDsaKeysRotation(ecdsa, options => keyRotationSection?.Bind(options)) @@ -79,7 +75,7 @@ public static IIdentityServerBuilder ConfigureKey(this IIdentityServerBuilder id } var dataProtectionsOptions = configuration.Get(); - switch (dataProtectionsOptions.StorageKind) + switch (dataProtectionsOptions?.StorageKind) { case StorageKind.AzureStorage: builder.PersistKeysToAzureBlobStorage(new Uri(dataProtectionsOptions.StorageConnectionString)); @@ -106,7 +102,7 @@ public static IIdentityServerBuilder ConfigureKey(this IIdentityServerBuilder id builder.PersistKeysToStackExchangeRedis(redis, dataProtectionsOptions.RedisKey); break; } - var protectOptions = dataProtectionsOptions.KeyProtectionOptions; + var protectOptions = dataProtectionsOptions?.KeyProtectionOptions; if (protectOptions != null) { switch (protectOptions.KeyProtectionKind) diff --git a/src/Aguacongas.TheIdServer.Duende/Extensions/ServiceCollectionExtensions.cs b/src/Aguacongas.TheIdServer.Duende/Extensions/ServiceCollectionExtensions.cs index eaceef6e3..5cc885880 100644 --- a/src/Aguacongas.TheIdServer.Duende/Extensions/ServiceCollectionExtensions.cs +++ b/src/Aguacongas.TheIdServer.Duende/Extensions/ServiceCollectionExtensions.cs @@ -11,39 +11,32 @@ using Aguacongas.TheIdServer.BlazorApp.Models; using Aguacongas.TheIdServer.BlazorApp.Services; using Aguacongas.TheIdServer.Data; +using Aguacongas.TheIdServer.Identity.Argon2PasswordHasher; +using Aguacongas.TheIdServer.Identity.BcryptPasswordHasher; +using Aguacongas.TheIdServer.Identity.ScryptPasswordHasher; +using Aguacongas.TheIdServer.Identity.UpgradePasswordHasher; using Aguacongas.TheIdServer.Models; using Aguacongas.TheIdServer.Services; using Aguacongas.TheIdServer.UI; using Duende.IdentityServer.Configuration; -using Duende.IdentityServer.Configuration.Configuration; using Duende.IdentityServer.Services; using IdentityModel.AspNetCore.OAuth2Introspection; using Microsoft.AspNetCore.Authentication.Certificate; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.WebAssembly.Authentication; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.AspNetCore.Components.WebAssembly.Services; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using Newtonsoft.Json; using Raven.Client.Documents; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; using System.Security.Cryptography.X509Certificates; -using System.Threading.Tasks; using ConfigurationModel = Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; namespace Microsoft.Extensions.DependencyInjection @@ -60,13 +53,18 @@ public static IServiceCollection AddTheIdServer(this IServiceCollection services .AddConfigurationStores() .AddOperationalStores() .AddTokenExchange() + .Configure(configurationManager.GetSection(nameof(PasswordHasherOptions))) .AddIdentity( options => { configurationManager.Bind(nameof(AspNetCore.Identity.IdentityOptions), options); }) .AddTheIdServerStores() - .AddDefaultTokenProviders(); + .AddDefaultTokenProviders() + .AddArgon2PasswordHasher(configurationManager.GetSection(nameof(Argon2PasswordHasherOptions))) + .AddBcryptPasswordHasher(configurationManager.GetSection(nameof(BcryptPasswordHasherOptions))) + .AddScryptPasswordHasher(configurationManager.GetSection(nameof(ScryptPasswordHasherOptions))) + .AddUpgradePasswordHasher(configurationManager.GetSection(nameof(UpgradePasswordHasherOptions))); if (isProxy) { @@ -154,7 +152,7 @@ public static IServiceCollection AddTheIdServer(this IServiceCollection services var authenticationBuilder = services.Configure(configurationManager.GetSection("Google")) .AddAuthorization(options => - options.AddIdentityServerPolicies(true)) + options.AddIdentityServerPolicies()) .AddAuthentication() .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => ConfigureIdentityServerJwtBearerOptions(options, configurationManager)) // reference tokens @@ -164,7 +162,7 @@ public static IServiceCollection AddTheIdServer(this IServiceCollection services if (mutulaTlsOptions?.Enabled == true) { // MutualTLS - authenticationBuilder.AddCertificate(mutulaTlsOptions?.ClientCertificateAuthenticationScheme, + authenticationBuilder.AddCertificate(mutulaTlsOptions!.ClientCertificateAuthenticationScheme, options => configurationManager.Bind(nameof(CertificateAuthenticationOptions), options)); } @@ -207,7 +205,9 @@ public static IServiceCollection AddTheIdServer(this IServiceCollection services .AddRazorPages(options => options.Conventions.AuthorizeAreaFolder("Identity", "/Account")); ConfigureHealthChecks(services, dbType, isProxy, configurationManager); - + + services.AddRazorComponents().AddInteractiveWebAssemblyComponents(); + return services; } @@ -394,12 +394,13 @@ private static void AddDefaultServices(IServiceCollection services, IConfigurati } } - private static void ConfigureDataProtection(IServiceCollection services, IConfiguration configuration) + private static void ConfigureDataProtection(IServiceCollection services, ConfigurationManager configurationManager) { - var dataprotectionSection = configuration.GetSection(nameof(DataProtectionOptions)); - if (dataprotectionSection != null) + var dataProtectionSection = configurationManager.GetSection(nameof(DataProtectionOptions)); + if (dataProtectionSection != null) { - services.AddDataProtection(options => dataprotectionSection.Bind(options)).ConfigureDataProtection(dataprotectionSection); + services.AddDataProtection(options => dataProtectionSection.Bind(options)) + .ConfigureDataProtection(dataProtectionSection); } } @@ -430,7 +431,7 @@ private static void ConfigureDbHealthChecks(DbTypes dbTypes, bool isProxy, IConf switch (dbTypes) { case DbTypes.MongoDb: - builder.AddMongoDb(configuration.GetConnectionString("DefaultConnection"), tags: tags); + builder.AddMongoDb(configuration.GetConnectionString("DefaultConnection")!, tags: tags); break; case DbTypes.RavenDb: builder.AddRavenDB(options => diff --git a/src/Aguacongas.TheIdServer.Duende/Program.cs b/src/Aguacongas.TheIdServer.Duende/Program.cs index 045e61bd9..8b273d639 100644 --- a/src/Aguacongas.TheIdServer.Duende/Program.cs +++ b/src/Aguacongas.TheIdServer.Duende/Program.cs @@ -1,17 +1,11 @@ // Project: Aguafrommars/TheIdServer // Copyright (c) 2023 @Olivier Lefebvre using Aguacongas.TheIdServer; -using Aguacongas.TheIdServer.BlazorApp.Models; using Aguacongas.TheIdServer.Options.OpenTelemetry; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Server.Kestrel.Https; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; using Serilog; using System.Diagnostics; -using System.Linq; using MutualTlsOptions = Aguacongas.TheIdServer.BlazorApp.Models.MutualTlsOptions; var builder = WebApplication.CreateBuilder(args); @@ -28,7 +22,7 @@ var seed = args.Any(x => x == "/seed"); if (seed) { - args = args.Except(new[] { "/seed" }).ToArray(); + args = args.Except(Config.SeedPage).ToArray(); } services.AddOpenTelemetry(configuration.GetSection(nameof(OpenTelemetryOptions))); @@ -61,4 +55,4 @@ }); app.UseTheIdServer(app.Environment, configuration); -app.Run(); +await app.RunAsync().ConfigureAwait(false); diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/AccountController.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/AccountController.cs index 333bbf314..18c6f0684 100644 --- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/AccountController.cs +++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/AccountController.cs @@ -1,25 +1,21 @@ // Project: Aguafrommars/TheIdServer // Copyright (c) 2023 @Olivier Lefebvre using Aguacongas.TheIdServer.Models; -using IdentityModel; using Duende.IdentityServer; using Duende.IdentityServer.Events; using Duende.IdentityServer.Extensions; using Duende.IdentityServer.Models; using Duende.IdentityServer.Services; using Duende.IdentityServer.Stores; +using IdentityModel; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Options; -using System; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Text.Encodings.Web; -using System.Threading.Tasks; namespace Aguacongas.TheIdServer.UI { @@ -91,20 +87,20 @@ public async Task Login(LoginInputModel model, string button) // the user clicked the "cancel" button if (button != "login") { - return await OnCancel(model, context).ConfigureAwait(false); + return await OnCancel(model, context!).ConfigureAwait(false); } if (ModelState.IsValid) { - var result = await _signInManager.PasswordSignInAsync(model.Username, model.Password, model.RememberLogin, lockoutOnFailure: true).ConfigureAwait(false); + var result = await _signInManager.PasswordSignInAsync(model.Username!, model.Password!, model.RememberLogin, lockoutOnFailure: true).ConfigureAwait(false); if (result.Succeeded) { - return await OnSiginSuccesss(model, context).ConfigureAwait(false); + return await OnSiginSuccesss(model, context!).ConfigureAwait(false); } if (result.RequiresTwoFactor) { - return Redirect($"/Identity/Account/LoginWith2fa?rememberMe={model.RememberLogin}&returnUrl={_urlEncoder.Encode(model.ReturnUrl)}"); + return Redirect($"/Identity/Account/LoginWith2fa?rememberMe={model.RememberLogin}&returnUrl={_urlEncoder.Encode(model.ReturnUrl!)}"); } await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials", clientId:context?.Client.ClientId)).ConfigureAwait(false); @@ -118,8 +114,8 @@ public async Task Login(LoginInputModel model, string button) private async Task OnSiginSuccesss(LoginInputModel model, AuthorizationRequest context) { - var user = await _userManager.FindByNameAsync(model.Username).ConfigureAwait(false); - await _events.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id, user.UserName, clientId: context?.Client.ClientId)).ConfigureAwait(false); + var user = await _userManager.FindByNameAsync(model.Username!).ConfigureAwait(false); + await _events.RaiseAsync(new UserLoginSuccessEvent(user!.UserName, user.Id, user.UserName, clientId: context?.Client.ClientId)).ConfigureAwait(false); if (context != null) { @@ -131,7 +127,7 @@ private async Task OnSiginSuccesss(LoginInputModel model, Authori } // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null - return Redirect(model.ReturnUrl); + return Redirect(model.ReturnUrl!); } // request for a local page @@ -162,10 +158,10 @@ private async Task OnCancel(LoginInputModel model, AuthorizationR { // The client is native, so this change in how to // return the response is for better UX for the end user. - return this.LoadingPage("Redirect", model.ReturnUrl); + return this.LoadingPage("Redirect", model.ReturnUrl!); } - return Redirect(model.ReturnUrl); + return Redirect(model.ReturnUrl!); } else { @@ -202,9 +198,9 @@ public async Task Logout(string logoutId) public async Task Logout(LogoutInputModel model) { // build a model so the logged out page knows what to display - var vm = await BuildLoggedOutViewModelAsync(model.LogoutId).ConfigureAwait(false); + var vm = await BuildLoggedOutViewModelAsync(model.LogoutId!).ConfigureAwait(false); - if (User?.Identity.IsAuthenticated == true) + if (User?.Identity?.IsAuthenticated == true) { // delete local authentication cookie await _signInManager.SignOutAsync().ConfigureAwait(false); @@ -219,10 +215,10 @@ public async Task Logout(LogoutInputModel model) // 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.Action("Logout", new { logoutId = vm.LogoutId }); + var url = Url.Action("Logout", new { logoutId = vm.LogoutId }); // this triggers a redirect to the external provider for sign-out - return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme); + return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme!); } return View("LoggedOut", vm); @@ -251,7 +247,7 @@ private async Task BuildLoginViewModelAsync(string returnUrl) { EnableLocalLogin = local, ReturnUrl = returnUrl, - Username = context?.LoginHint, + Username = context.LoginHint, }; if (!local) @@ -283,9 +279,9 @@ private async Task BuildLoginViewModelAsync(string returnUrl) { allowLocal = client.EnableLocalLogin; - if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Any()) + if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Count > 0) { - providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList(); + providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme!)).ToList(); } } } @@ -305,7 +301,7 @@ private async Task BuildLoginViewModelAsync(string returnUrl) private async Task BuildLoginViewModelAsync(LoginInputModel model) { - var vm = await BuildLoginViewModelAsync(model.ReturnUrl).ConfigureAwait(false); + var vm = await BuildLoginViewModelAsync(model.ReturnUrl!).ConfigureAwait(false); vm.Username = model.Username; vm.RememberLogin = model.RememberLogin; return vm; @@ -315,7 +311,7 @@ private async Task BuildLogoutViewModelAsync(string logoutId) { var vm = new LogoutViewModel { LogoutId = logoutId, ShowLogoutPrompt = _options.Value.ShowLogoutPrompt }; - if (User?.Identity.IsAuthenticated != true) + if (User?.Identity?.IsAuthenticated != true) { // if the user is not authenticated, then just show logged out page vm.ShowLogoutPrompt = false; @@ -349,7 +345,7 @@ private async Task BuildLoggedOutViewModelAsync(string logou LogoutId = logoutId }; - if (User?.Identity.IsAuthenticated == true) + if (User?.Identity?.IsAuthenticated == true) { var idp = User.FindFirst(JwtClaimTypes.IdentityProvider)?.Value; if (idp != null && idp != IdentityServerConstants.LocalIdentityProvider) diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/ExternalController.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/ExternalController.cs index 0dcc742c7..27c0bacaf 100644 --- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/ExternalController.cs +++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/ExternalController.cs @@ -1,23 +1,16 @@ // Project: Aguafrommars/TheIdServer // Copyright (c) 2023 @Olivier Lefebvre using Aguacongas.TheIdServer.Models; -using IdentityModel; using Duende.IdentityServer; using Duende.IdentityServer.Events; using Duende.IdentityServer.Services; -using Duende.IdentityServer.Stores; +using IdentityModel; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; -using System.Linq; using System.Security.Claims; -using System.Threading.Tasks; namespace Aguacongas.TheIdServer.UI { @@ -101,7 +94,7 @@ public async Task Callback() if (_logger.IsEnabled(LogLevel.Debug)) { - var externalClaims = result.Principal.Claims.Select(c => $"{c.Type}: {c.Value}"); + var externalClaims = result.Principal!.Claims.Select(c => $"{c.Type}: {c.Value}"); _logger.LogDebug("External claims: {@claims}", externalClaims); } @@ -112,7 +105,7 @@ public async Task Callback() // 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 - user = await AutoProvisionUserAsync(provider, providerUserId, claims).ConfigureAwait(false); + user = await AutoProvisionUserAsync(provider!, providerUserId, claims).ConfigureAwait(false); } // this allows us to collect any additonal claims or properties @@ -141,7 +134,7 @@ public async Task Callback() await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); // retrieve return URL - var returnUrl = result.Properties.Items["returnUrl"] ?? "~/"; + var returnUrl = result.Properties!.Items["returnUrl"] ?? "~/"; // check if external login is in the context of an OIDC request var context = await _interaction.GetAuthorizationContextAsync(returnUrl); @@ -172,28 +165,28 @@ private async Task ProcessWindowsLoginAsync(string provider, Clai }; var id = new ClaimsIdentity(provider); - var name = user.Identity.Name ?? + var name = user.Identity!.Name ?? user.FindFirst(JwtClaimTypes.Name)?.Value ?? user.FindFirst(ClaimTypes.Name)?.Value ?? user.FindFirst(JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap[ClaimTypes.Name])?.Value ?? user.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? user.FindFirst(JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap[ClaimTypes.NameIdentifier])?.Value; - id.AddClaim(new Claim(JwtClaimTypes.Subject, name)); - id.AddClaim(new Claim(JwtClaimTypes.Name, name)); + id.AddClaim(new Claim(JwtClaimTypes.Subject, name!)); + id.AddClaim(new Claim(JwtClaimTypes.Name, name!)); await HttpContext.SignInAsync( IdentityConstants.ExternalScheme, new ClaimsPrincipal(id), props).ConfigureAwait(false); - return Redirect(props.RedirectUri); + return Redirect(props.RedirectUri!); } - private async Task<(ApplicationUser user, string provider, string providerUserId, IEnumerable claims)> + private async Task<(ApplicationUser? user, string? provider, string providerUserId, IEnumerable claims)> FindUserFromExternalProviderAsync(AuthenticateResult result) { - var externalUser = result.Principal; + var externalUser = result.Principal!; // 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 @@ -207,11 +200,11 @@ await HttpContext.SignInAsync( var claims = externalUser.Claims.ToList(); claims.Remove(userIdClaim); - var provider = result.Properties.Items["scheme"]; + var provider = result.Properties?.Items["scheme"]; var providerUserId = userIdClaim.Value; // find external user - var user = await _userManager.FindByLoginAsync(provider, providerUserId); + var user = await _userManager.FindByLoginAsync(provider!, providerUserId); return (user, provider, providerUserId, claims); } @@ -263,7 +256,7 @@ private async Task AutoProvisionUserAsync(string provider, stri var identityResult = await _userManager.CreateAsync(user); if (!identityResult.Succeeded) throw new InvalidOperationException(identityResult.Errors.First().Description); - if (filtered.Any()) + if (filtered.Count > 0) { identityResult = await _userManager.AddClaimsAsync(user, filtered); if (!identityResult.Succeeded) throw new InvalidOperationException(identityResult.Errors.First().Description); @@ -280,14 +273,14 @@ private static void ProcessLoginCallbackForOidc(AuthenticateResult externalResul { // 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); + 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 id_token = externalResult.Properties.GetTokenValue("id_token"); + var id_token = externalResult.Properties!.GetTokenValue("id_token"); if (id_token != null) { localSignInProps.StoreTokens(new[] { new AuthenticationToken { Name = "id_token", Value = id_token } }); diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/ExternalProvider.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/ExternalProvider.cs index bbb94f9b4..df5df603b 100644 --- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/ExternalProvider.cs +++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/ExternalProvider.cs @@ -4,7 +4,7 @@ namespace Aguacongas.TheIdServer.UI { public class ExternalProvider { - public string DisplayName { get; set; } - public string AuthenticationScheme { get; set; } + public string? DisplayName { get; set; } + public string? AuthenticationScheme { get; set; } } } \ No newline at end of file diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/InvalidReturnUrlException.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/InvalidReturnUrlException.cs index ac19934f1..c88b720cf 100644 --- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/InvalidReturnUrlException.cs +++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/InvalidReturnUrlException.cs @@ -1,6 +1,7 @@ // Project: Aguafrommars/TheIdServer // Copyright (c) 2023 @Olivier Lefebvre using System; +using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; namespace Aguacongas.TheIdServer.UI @@ -10,6 +11,7 @@ namespace Aguacongas.TheIdServer.UI /// /// [Serializable] + [SuppressMessage("Major Code Smell", "S3925:\"ISerializable\" should be implemented correctly", Justification = "Obsolete")] public class InvalidReturnUrlException : Exception { /// @@ -23,12 +25,7 @@ public InvalidReturnUrlException(string message) : this(message, null) { } - public InvalidReturnUrlException(string message, Exception innerException) : base(message, innerException) - { - } - - protected InvalidReturnUrlException(SerializationInfo serializationInfo, StreamingContext streamingContext) - : base(serializationInfo, streamingContext) + public InvalidReturnUrlException(string message, Exception? innerException) : base(message, innerException) { } } diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/LoggedOutViewModel.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/LoggedOutViewModel.cs index 5513cf399..fe1552c2e 100644 --- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/LoggedOutViewModel.cs +++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/LoggedOutViewModel.cs @@ -4,14 +4,14 @@ namespace Aguacongas.TheIdServer.UI { public class LoggedOutViewModel { - public string PostLogoutRedirectUri { get; set; } - public string ClientName { get; set; } - public string SignOutIframeUrl { get; set; } + public string? PostLogoutRedirectUri { get; set; } + public string? ClientName { get; set; } + public string? SignOutIframeUrl { get; set; } public bool AutomaticRedirectAfterSignOut { get; set; } - public string LogoutId { get; set; } + public string? LogoutId { get; set; } public bool TriggerExternalSignout => ExternalAuthenticationScheme != null; - public string ExternalAuthenticationScheme { get; set; } + public string? ExternalAuthenticationScheme { get; set; } } } \ No newline at end of file diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/LoginInputModel.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/LoginInputModel.cs index 7b9374be8..018eb6b44 100644 --- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/LoginInputModel.cs +++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/LoginInputModel.cs @@ -7,10 +7,10 @@ namespace Aguacongas.TheIdServer.UI public class LoginInputModel { [Required] - public string Username { get; set; } + public string? Username { get; set; } [Required] - public string Password { get; set; } + public string? Password { get; set; } public bool RememberLogin { get; set; } - public string ReturnUrl { get; set; } + public string? ReturnUrl { get; set; } } } \ No newline at end of file diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/LoginViewModel.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/LoginViewModel.cs index 619547808..e5f7eb034 100644 --- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/LoginViewModel.cs +++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/LoginViewModel.cs @@ -11,11 +11,11 @@ public class LoginViewModel : LoginInputModel 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 IEnumerable? ExternalProviders { get; set; } = Enumerable.Empty(); + public IEnumerable? VisibleExternalProviders => ExternalProviders?.Where(x => !string.IsNullOrWhiteSpace(x.DisplayName)); public bool IsExternalLoginOnly => !EnableLocalLogin && ExternalProviders?.Count() == 1; - public string ExternalLoginScheme => IsExternalLoginOnly ? ExternalProviders?.SingleOrDefault()?.AuthenticationScheme : null; + public string? ExternalLoginScheme => IsExternalLoginOnly ? ExternalProviders?.SingleOrDefault()?.AuthenticationScheme : null; public bool ShowForgotPassworLink { get; set; } = true; diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/LogoutInputModel.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/LogoutInputModel.cs index a01d51cef..5ae004d48 100644 --- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/LogoutInputModel.cs +++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/LogoutInputModel.cs @@ -4,6 +4,6 @@ namespace Aguacongas.TheIdServer.UI { public class LogoutInputModel { - public string LogoutId { get; set; } + public string? LogoutId { get; set; } } } diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/RedirectViewModel.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/RedirectViewModel.cs index ded169e58..a6d180e2e 100644 --- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/RedirectViewModel.cs +++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/RedirectViewModel.cs @@ -4,6 +4,6 @@ namespace Aguacongas.TheIdServer.UI { public class RedirectViewModel { - public string RedirectUrl { get; set; } + public string? RedirectUrl { get; set; } } } \ No newline at end of file diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/CibaController.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/CibaController.cs index bf455cc69..3b0618797 100644 --- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/CibaController.cs +++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/CibaController.cs @@ -35,7 +35,7 @@ public CibaController(IBackchannelAuthenticationInteractionService interaction, public async Task Consent(string id) { var request = await _interaction.GetLoginRequestByInternalIdAsync(id); - var model = BuildViewModelAsync(request, id); + var model = BuildViewModelAsync(request!, id); return View(model); } @@ -44,18 +44,18 @@ public async Task Consent(string id) [ValidateAntiForgeryToken] public async Task Consent([FromForm] InputModel input) { - var request = await _interaction.GetLoginRequestByInternalIdAsync(input.Id); - var viewModel = BuildViewModelAsync(request, input.Id, input); + var request = await _interaction.GetLoginRequestByInternalIdAsync(input.Id!); + var viewModel = BuildViewModelAsync(request!, input.Id!, input); - CompleteBackchannelLoginRequest result = null; + CompleteBackchannelLoginRequest? result = null; // user clicked 'no' - send back the standard 'access_denied' response if (input.Button == "no") { - result = new CompleteBackchannelLoginRequest(input.Id); + result = new CompleteBackchannelLoginRequest(input.Id!); // emit event - await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)) + await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request!.Client.ClientId, request.ValidatedResources.RawScopeValues)) .ConfigureAwait(false); } // user clicked 'yes' - validate the data @@ -70,14 +70,14 @@ await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Cli scopes = scopes.Where(x => x != StandardScopes.OfflineAccess); } - result = new CompleteBackchannelLoginRequest(input.Id) + 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)) + await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request!.Client.ClientId, request.ValidatedResources.RawScopeValues, result.ScopesValuesConsented, false)) .ConfigureAwait(false); } else @@ -100,7 +100,7 @@ await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Cl return View(viewModel); } - private ViewModel BuildViewModelAsync(BackchannelUserLoginRequest request, string id, InputModel model = null) + private ViewModel BuildViewModelAsync(BackchannelUserLoginRequest request, string id, InputModel? model = null) { if (request is null) { @@ -113,7 +113,7 @@ private ViewModel BuildViewModelAsync(BackchannelUserLoginRequest request, strin throw new InvalidOperationException(_localizer["SubjectIds don't match."]); } - private ViewModel CreateConsentViewModel(InputModel model, + private ViewModel CreateConsentViewModel(InputModel? model, string id, BackchannelUserLoginRequest request) { diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/InputModel.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/InputModel.cs index 22acc02e5..d2b963e44 100644 --- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/InputModel.cs +++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/InputModel.cs @@ -4,10 +4,10 @@ namespace Aguacongas.TheIdServer.Duende.Quickstart.Ciba { public class InputModel { - public string Button { get; set; } - public IEnumerable ScopesConsented { get; set; } - public string Id { get; set; } - public string Description { get; set; } + public string? Button { get; set; } + public IEnumerable? ScopesConsented { get; set; } + public string? Id { get; set; } + public string? Description { get; set; } } } diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/ResourceViewModel.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/ResourceViewModel.cs index 75039a5df..236375ef0 100644 --- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/ResourceViewModel.cs +++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/ResourceViewModel.cs @@ -2,7 +2,7 @@ { public class ResourceViewModel { - public string Name { get; set; } - public string DisplayName { get; set; } + public string? Name { get; set; } + public string? DisplayName { get; set; } } } \ No newline at end of file diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/ScopeViewModel.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/ScopeViewModel.cs index 6022bf35c..765d63d48 100644 --- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/ScopeViewModel.cs +++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/ScopeViewModel.cs @@ -4,13 +4,13 @@ namespace Aguacongas.TheIdServer.Duende.Quickstart.Ciba { public class ScopeViewModel { - public string Name { get; set; } - public string Value { get; set; } - public string DisplayName { get; set; } - public string Description { get; set; } + 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 IEnumerable? Resources { get; set; } } } \ No newline at end of file diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/ViewModel.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/ViewModel.cs index c2a26af8b..9debc9c2e 100644 --- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/ViewModel.cs +++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/ViewModel.cs @@ -4,20 +4,20 @@ namespace Aguacongas.TheIdServer.Duende.Quickstart.Ciba { public class ViewModel { - public string Id { get; set; } + public string? Id { get; set; } - public string ClientName { get; set; } - public string ClientUrl { get; set; } - public string ClientLogoUrl { get; set; } + public string? ClientName { get; set; } + public string? ClientUrl { get; set; } + public string? ClientLogoUrl { get; set; } - public string BindingMessage { get; set; } + public string? BindingMessage { get; set; } - public string Description { get; set; } + public string? Description { get; set; } - public InputModel Input { get; set; } + public InputModel? Input { get; set; } - public IEnumerable IdentityScopes { get; set; } - public IEnumerable ApiScopes { get; set; } + public IEnumerable? IdentityScopes { get; set; } + public IEnumerable? ApiScopes { get; set; } } } diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ConsentController.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ConsentController.cs index 47561b7b2..1e91ee709 100644 --- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ConsentController.cs +++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ConsentController.cs @@ -73,12 +73,12 @@ public async Task Index(ConsentInputModel model) return this.LoadingPage("Redirect", result.RedirectUri); } - return Redirect(result.RedirectUri); + return Redirect(result.RedirectUri!); } if (result.HasValidationError) { - ModelState.AddModelError(string.Empty, result.ValidationError); + ModelState.AddModelError(string.Empty, result.ValidationError!); } if (result.ShowView) @@ -100,10 +100,10 @@ private async Task ProcessConsent(ConsentInputModel model) var request = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); if (request == null) return result; - ConsentResponse grantedConsent = null; + ConsentResponse? grantedConsent = null; // user clicked 'no' - send back the standard 'access_denied' response - if (model?.Button == "no") + if (model.Button == "no") { grantedConsent = new ConsentResponse { Error = AuthorizationError.AccessDenied }; @@ -111,7 +111,7 @@ private async Task ProcessConsent(ConsentInputModel model) await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); } // user clicked 'yes' - validate the data - else if (model?.Button == "yes") + else if (model.Button == "yes") { // if the user consented to some scope, build the response model if (model.ScopesConsented != null && model.ScopesConsented.Any()) @@ -160,7 +160,7 @@ private async Task ProcessConsent(ConsentInputModel model) return result; } - private async Task BuildViewModelAsync(string returnUrl, ConsentInputModel model = null) + private async Task BuildViewModelAsync(string? returnUrl, ConsentInputModel? model = null) { var request = await _interaction.GetAuthorizationContextAsync(returnUrl); if (request != null) @@ -176,7 +176,7 @@ private async Task BuildViewModelAsync(string returnUrl, Conse } private ConsentViewModel CreateConsentViewModel( - ConsentInputModel model, string returnUrl, + ConsentInputModel? model, string? returnUrl, AuthorizationRequest request) { var client = request.Client; @@ -193,11 +193,11 @@ private ConsentViewModel CreateConsentViewModel( ClientLogoUrl = client.LogoUri, AllowRememberConsent = client.AllowRememberConsent }; - if (client.Properties.TryGetValue("PolicyUrl", out string policyUrl)) + if (client.Properties.TryGetValue("PolicyUrl", out var policyUrl)) { vm.PolicyUrl = policyUrl; } - if (client.Properties.TryGetValue("TosUrl", out string tosUrl)) + if (client.Properties.TryGetValue("TosUrl", out var tosUrl)) { vm.TosUrl = tosUrl; } diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ConsentInputModel.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ConsentInputModel.cs index 4b59bd7c7..a5335df0f 100644 --- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ConsentInputModel.cs +++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ConsentInputModel.cs @@ -6,10 +6,10 @@ namespace Aguacongas.TheIdServer.UI { public class ConsentInputModel { - public string Button { get; set; } - public IEnumerable ScopesConsented { get; set; } + public string? Button { get; set; } + public IEnumerable? ScopesConsented { get; set; } public bool RememberConsent { get; set; } - public string ReturnUrl { get; set; } - public string Description { get; set; } + public string? ReturnUrl { get; set; } + public string? Description { get; set; } } } \ No newline at end of file diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ConsentViewModel.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ConsentViewModel.cs index 41c93cd37..2553187f6 100644 --- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ConsentViewModel.cs +++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ConsentViewModel.cs @@ -6,17 +6,17 @@ namespace Aguacongas.TheIdServer.UI { public class ConsentViewModel : ConsentInputModel { - public string ClientName { get; set; } - public string ClientUrl { get; set; } - public string ClientLogoUrl { get; set; } + public string? ClientName { get; set; } + public string? ClientUrl { get; set; } + public string? ClientLogoUrl { get; set; } - public string PolicyUrl { get; set; } + public string? PolicyUrl { get; set; } - public string TosUrl { get; set; } + public string? TosUrl { get; set; } public bool AllowRememberConsent { get; set; } - public IEnumerable IdentityScopes { get; set; } - public IEnumerable ApiScopes { get; set; } + public IEnumerable? IdentityScopes { get; set; } + public IEnumerable? ApiScopes { get; set; } } } diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ProcessConsentResult.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ProcessConsentResult.cs index a838390a4..90a7a7258 100644 --- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ProcessConsentResult.cs +++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ProcessConsentResult.cs @@ -7,13 +7,13 @@ namespace Aguacongas.TheIdServer.UI public class ProcessConsentResult { public bool IsRedirect => RedirectUri != null; - public string RedirectUri { get; set; } - public Client Client { get; set; } + public string? RedirectUri { get; set; } + public Client? Client { get; set; } public bool ShowView => ViewModel != null; - public ConsentViewModel ViewModel { get; set; } + public ConsentViewModel? ViewModel { get; set; } public bool HasValidationError => ValidationError != null; - public string ValidationError { get; set; } + public string? ValidationError { get; set; } } } diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ScopeViewModel.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ScopeViewModel.cs index b0959b3e7..69fb93ae3 100644 --- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ScopeViewModel.cs +++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ScopeViewModel.cs @@ -4,9 +4,9 @@ namespace Aguacongas.TheIdServer.UI { public class ScopeViewModel { - public string Value { get; set; } - public string DisplayName { get; set; } - public string Description { 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; } diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Device/DeviceAuthorizationInputModel.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Device/DeviceAuthorizationInputModel.cs index 91a56b546..46f766a57 100644 --- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Device/DeviceAuthorizationInputModel.cs +++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Device/DeviceAuthorizationInputModel.cs @@ -6,6 +6,6 @@ namespace Aguacongas.IdentityServer.UI.Device { public class DeviceAuthorizationInputModel : ConsentInputModel { - public string UserCode { get; set; } + public string? UserCode { get; set; } } } \ No newline at end of file diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Device/DeviceAuthorizationViewModel.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Device/DeviceAuthorizationViewModel.cs index 91bb36c30..fa734e2ac 100644 --- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Device/DeviceAuthorizationViewModel.cs +++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Device/DeviceAuthorizationViewModel.cs @@ -6,7 +6,7 @@ namespace Aguacongas.IdentityServer.UI.Device { public class DeviceAuthorizationViewModel : ConsentViewModel { - public string UserCode { get; set; } + public string? UserCode { get; set; } public bool ConfirmUserCode { get; set; } } } \ No newline at end of file diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Device/DeviceController.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Device/DeviceController.cs index e610082d2..b5f35630d 100644 --- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Device/DeviceController.cs +++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Device/DeviceController.cs @@ -1,9 +1,5 @@ // Project: Aguafrommars/TheIdServer // Copyright (c) 2023 @Olivier Lefebvre -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Aguacongas.TheIdServer.UI; using Duende.IdentityServer; using Duende.IdentityServer.Events; @@ -39,11 +35,14 @@ public DeviceController( [HttpGet] public async Task Index() { - string userCodeParamName = _options.Value.UserInteraction.DeviceVerificationUserCodeParameter; - string userCode = Request.Query[userCodeParamName]; - if (string.IsNullOrWhiteSpace(userCode)) return View("UserCodeCapture"); + var userCodeParamName = _options.Value.UserInteraction.DeviceVerificationUserCodeParameter; + var userCode = Request.Query[userCodeParamName]; + if (string.IsNullOrWhiteSpace(userCode)) + { + return View("UserCodeCapture"); + } - var vm = await BuildViewModelAsync(userCode); + var vm = await BuildViewModelAsync(userCode!); if (vm == null) return View("Error"); vm.ConfirmUserCode = true; @@ -67,10 +66,7 @@ public async Task UserCodeCapture(string userCode) [ValidateAntiForgeryToken] public Task Callback(DeviceAuthorizationInputModel model) { - if (model == null) - { - throw new ArgumentNullException(nameof(model)); - } + ArgumentNullException.ThrowIfNull(model); return CallbackInternal(model); } @@ -90,10 +86,10 @@ private async Task ProcessConsent(DeviceAuthorizationInput { var result = new ProcessConsentResult(); - var request = await _interaction.GetAuthorizationContextAsync(model.UserCode); + var request = await _interaction.GetAuthorizationContextAsync(model.UserCode!); if (request == null) return result; - ConsentResponse grantedConsent = null; + ConsentResponse? grantedConsent = null; // user clicked 'no' - send back the standard 'access_denied' response if (model.Button == "no") @@ -138,7 +134,7 @@ private async Task ProcessConsent(DeviceAuthorizationInput if (grantedConsent != null) { // communicate outcome of consent back to identityserver - await _interaction.HandleRequestAsync(model.UserCode, grantedConsent); + await _interaction.HandleRequestAsync(model.UserCode!, grantedConsent); // indicate that's it ok to redirect back to authorization endpoint result.RedirectUri = model.ReturnUrl; @@ -147,13 +143,13 @@ private async Task ProcessConsent(DeviceAuthorizationInput else { // we need to redisplay the consent UI - result.ViewModel = await BuildViewModelAsync(model.UserCode, model); + result.ViewModel = await BuildViewModelAsync(model.UserCode!, model); } return result; } - private async Task BuildViewModelAsync(string userCode, DeviceAuthorizationInputModel model = null) + private async Task BuildViewModelAsync(string userCode, DeviceAuthorizationInputModel? model = null) { var request = await _interaction.GetAuthorizationContextAsync(userCode); if (request != null) @@ -164,7 +160,7 @@ private async Task BuildViewModelAsync(string user return null; } - private DeviceAuthorizationViewModel CreateConsentViewModel(string userCode, DeviceAuthorizationInputModel model, DeviceFlowAuthorizationRequest request) + private DeviceAuthorizationViewModel CreateConsentViewModel(string userCode, DeviceAuthorizationInputModel? model, DeviceFlowAuthorizationRequest request) { var client = request.Client; var vm = new DeviceAuthorizationViewModel @@ -180,11 +176,11 @@ private DeviceAuthorizationViewModel CreateConsentViewModel(string userCode, Dev ClientLogoUrl = client.LogoUri, AllowRememberConsent = client.AllowRememberConsent }; - if (client.Properties.TryGetValue("PolicyUrl", out string policyUrl)) + if (client.Properties.TryGetValue("PolicyUrl", out var policyUrl)) { vm.PolicyUrl = policyUrl; } - if (client.Properties.TryGetValue("TosUrl", out string tosUrl)) + if (client.Properties.TryGetValue("TosUrl", out var tosUrl)) { vm.TosUrl = tosUrl; } diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Diagnostics/DiagnosticsController.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Diagnostics/DiagnosticsController.cs index 430317da3..fa6881015 100644 --- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Diagnostics/DiagnosticsController.cs +++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Diagnostics/DiagnosticsController.cs @@ -14,8 +14,8 @@ public class DiagnosticsController : Controller { public async Task Index() { - var localAddresses = new string[] { "127.0.0.1", "::1", HttpContext.Connection.LocalIpAddress.ToString() }; - if (!localAddresses.Contains(HttpContext.Connection.RemoteIpAddress.ToString())) + var localAddresses = new string[] { "127.0.0.1", "::1", HttpContext.Connection.LocalIpAddress!.ToString() }; + if (!localAddresses.Contains(HttpContext.Connection.RemoteIpAddress!.ToString())) { return NotFound(); } diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Diagnostics/DiagnosticsViewModel.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Diagnostics/DiagnosticsViewModel.cs index 94452eb61..f5fbbc750 100644 --- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Diagnostics/DiagnosticsViewModel.cs +++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Diagnostics/DiagnosticsViewModel.cs @@ -14,18 +14,18 @@ public DiagnosticsViewModel(AuthenticateResult result) { AuthenticateResult = result; - if (!result.Properties.Items.TryGetValue("client_list", out string encoded)) + if (result.Properties?.Items is null || !result.Properties.Items.TryGetValue("client_list", out var encoded)) { return; } - var bytes = Base64Url.Decode(encoded); + var bytes = Base64Url.Decode(encoded!); var value = Encoding.UTF8.GetString(bytes); Clients = JsonConvert.DeserializeObject(value); } - public AuthenticateResult AuthenticateResult { get; } - public IEnumerable Clients { get; } = new List(); + public AuthenticateResult? AuthenticateResult { get; } + public IEnumerable? Clients { get; } = new List(); } } \ No newline at end of file diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Extensions.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Extensions.cs index b542d2b0e..5d0d2f491 100644 --- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Extensions.cs +++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Extensions.cs @@ -2,7 +2,6 @@ // Copyright (c) 2023 @Olivier Lefebvre using Duende.IdentityServer.Models; using Microsoft.AspNetCore.Mvc; -using System; namespace Aguacongas.TheIdServer.UI { @@ -18,10 +17,10 @@ public static bool IsNativeClient(this AuthorizationRequest context) && !context.RedirectUri.StartsWith("http", StringComparison.Ordinal); } - public static IActionResult LoadingPage(this Controller controller, string viewName, string redirectUri) + public static IActionResult LoadingPage(this Controller controller, string? viewName, string? redirectUri) { controller.HttpContext.Response.StatusCode = 200; - controller.HttpContext.Response.Headers["Location"] = ""; + controller.HttpContext.Response.Headers.Location = ""; return controller.View(viewName, new RedirectViewModel { RedirectUrl = redirectUri }); } diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Home/ErrorViewModel.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Home/ErrorViewModel.cs index 413ac6876..aa50a0466 100644 --- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Home/ErrorViewModel.cs +++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Home/ErrorViewModel.cs @@ -15,6 +15,6 @@ public ErrorViewModel(string error) Error = new ErrorMessage { Error = error }; } - public ErrorMessage Error { get; set; } + public ErrorMessage? Error { get; set; } } } \ No newline at end of file diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/SecurityHeadersAttribute.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/SecurityHeadersAttribute.cs index 6421ff544..ce2f00182 100644 --- a/src/Aguacongas.TheIdServer.Duende/Quickstart/SecurityHeadersAttribute.cs +++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/SecurityHeadersAttribute.cs @@ -17,13 +17,13 @@ public override void OnResultExecuting(ResultExecutingContext context) // 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"); + context.HttpContext.Response.Headers.XContentTypeOptions = "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"); + context.HttpContext.Response.Headers.XFrameOptions = "SAMEORIGIN"; } // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy @@ -53,19 +53,19 @@ public override void OnResultExecuting(ResultExecutingContext context) // once for standards compliant browsers if (!context.HttpContext.Response.Headers.ContainsKey("Content-Security-Policy")) { - context.HttpContext.Response.Headers.Add("Content-Security-Policy", csp); + context.HttpContext.Response.Headers.ContentSecurityPolicy = 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); + context.HttpContext.Response.Headers["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); + context.HttpContext.Response.Headers["Referrer-Policy"] = referrer_policy; } } } diff --git a/src/Aguacongas.TheIdServer.Duende/README.md b/src/Aguacongas.TheIdServer.Duende/README.md index 011dcf945..bc44ee382 100644 --- a/src/Aguacongas.TheIdServer.Duende/README.md +++ b/src/Aguacongas.TheIdServer.Duende/README.md @@ -2,1007 +2,4 @@ > TheIdServer use [Duende IdentityServer](https://duendesoftware.com/products/identityserver), for a commercial use you need to [acquire a license](https://duendesoftware.com/products/identityserver#pricing). -The server obtains configuration from *appsettings.json*, *appsettings.{Environment}.json*, command-line arguments, or environment variables. - -Read [Configuration in ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/) for more information. - -## Installation - -### From Terraform - -The [Terraform](https://terraform.io) [Helm](https://helm.sh) module [theidserver](https://registry.terraform.io/modules/Aguafrommars/theidserver/helm/latest) make the deployement of TheIdServer easy. -To deploy the Duende version choose the [aguacongas/theidserver.duende image](https://hub.docker.com/r/aguacongas/theidserver.duende). - -``` hcl -provider "helm" { - kubernetes { - config_path = var.kubeconfig_path - } -} - -module "theidserver" { - source = "Aguafrommars/theidserver/helm" - - host = "theidserver.com" - tls_issuer_name = "letsencrypt" - tls_issuer_kind = "ClusterIssuer" - - image = { - repository = "aguacongas/theidserver.duende" - pullPolicy = "Always" - tag = "next" - } -} -``` - -### From Helm - -The [theidserver](https://hub.helm.sh/packages/helm/aguafrommars/theidserver) [Helm](https://helm.sh) chart is available in [hub.helm.sh](https://hub.helm.sh). - -#### Install - -``` bash -helm repo add aguafrommars https://aguafrommars.github.io/helm -helm install aguafrommars theidserver --set theidserver.mysql.db.password=my-P@ssword --set image.repository=aguacongas/theidserver.duende -``` - -> By default the helm char install the IS4 version, to install the Duende version your need to set `image.repository=aguacongas/theidserver.duende`. - -#### Upgrade - -Follow upgrades intstructions in the [chart readme](https://github.com/Aguafrommars/helm/blob/main/charts/theidserver/README.md#upgrade). - -### From Docker - -A [server's Linux image](https://hub.docker.com/r/aguacongas/theidserver.duende) is available on Docker Hub. - -[*sample/MultiTiers/Aguacongas.TheIdServer.Private/Dockerfile.Duende-private*](../../sample/MultiTiers/Aguacongas.TheIdServer.Private/Dockerfile.Duende-private) demonstrates how to create an image from the [server image](https://hub.docker.com/r/aguacongas/theidserver) to run a private Linux server container. - -[*sample/MultiTiers/Aguacongas.TheIdServer.Public/Dockerfile.Duende-public*](../../sample/MultiTiers/Aguacongas.TheIdServer.Public/Dockerfile.Duende-public) illustrates how to create an image from the [server image](https://hub.docker.com/r/aguacongas/theidserver) to run a public Linux server container. - -Read [Hosting ASP.NET Core images with Docker over HTTPS](https://docs.microsoft.com/en-us/aspnet/core/security/docker-https) to set up the HTTPS certificate. - -#### Kubernetes sample - -[/sample/Kubernetes/README.md](/sample/Kubernetes/README.md) contains a sample to set up a solution with Kubernetes. - -> The sample use the IS4 version but you just need to use `aguacongas/theidserver.duende` as docker image in the deployement file. - -### From dotnet new template - -The template [TheIdServer.Duende.Template](https://github.com/Aguafrommars/Templates) can be use to setup a TheIdServer solution. - -#### Install - -```bash -dotnet new -i TheIdServer.Duende.Template -``` - -#### Use - -```bash -> dotnet new tisduende -o TheIdServer -The template "TheIdServer.Duende" was created successfully. - -Processing post-creation actions... -Running 'dotnet restore' on TheIdServer\TheIdServer.sln... - Determining projects to restore... - Restored C:\Projects\Perso\Templates\artifacts\TheIdServer\test\WebAssembly.Net.Http\WebAssembly.Net.Http.csproj (in 114 ms). - Restored C:\Projects\Perso\Templates\artifacts\TheIdServer\src\TheIdServer.BlazorApp\TheIdServer.BlazorApp.csproj (in 916 ms). - Restored C:\Projects\Perso\Templates\artifacts\TheIdServer\test\Microsoft.AspNetCore.Components.Testing\Microsoft.AspNetCore.Components.Testing.csproj (in 1.08 sec). - Restored C:\Projects\Perso\Templates\artifacts\TheIdServer\src\TheIdServer\TheIdServer.csproj (in 2.03 sec). - Restored C:\Projects\Perso\Templates\artifacts\TheIdServer\test\TheIdServer.Test\TheIdServer.Test.csproj (in 2.04 sec). - Restored C:\Projects\Perso\Templates\artifacts\TheIdServer\test\TheIdServer.IntegrationTest\TheIdServer.IntegrationTest.csproj (in 2.04 sec). -Restore succeeded. -``` - -### From NuGet Packages - -If you need more customization, you can use published NuGet packages. -[sample/MultiTiers](sample/MultiTiers) contains a sample to build server and API from NuGet packages. - -> The sample use IS4 version but you just need to remplace IS4 by Duende in package reference to use the Duende version. - -### From Github Release - -Choose your release in the [list of releases](https://github.com/Aguafrommars/TheIdServer/releases) and download the server zip. -Unzip in the destination of your choice. Unzip in the destination of your choice. As with any ASP.NET Core web site, it can run in IIS or as a stand-alone server using your chosen platform. - -Read [Host and deploy ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/) for more information. - -## Configure data protection - -[Data protection](../../doc/DATA_PROTECTION.md) provides details on data protection configuration. - -## Configure site - -The site name is defined by *SiteOptions:TheIdServer*. - -```json -"SiteOptions": { - "Name": "TheIdServer" -} -``` - -The site stylecheets are *wwwroot/lib/bootstrap/css/bootstrap.css* and *wwwroot/css/site.min.css*. -The site logo is *wwwroot/logo.png*. -And the favicon is *wwwroot/favicon.ico*. - -By replacing those files you can redefined the site style by yours. - -### Configure account options - -The section *AccountOptions* is bound to [`AccountOptions`](../Aguacongas.TheIdServer.Shared/Quickstart/Account/AccountOptions.cs). - -```json -"AccountOptions": { - "AllowLocalLogin": true, - "AllowRememberLogin": true, - "RememberMeLoginDuration": "30.00:00:00", - "ShowLogoutPrompt": true, - "AutomaticRedirectAfterSignOut": false, - "InvalidCredentialsErrorMessage": "Invalid username or password", - "ShowForgotPassworLink": true, - "ShowRegisterLink": true, - "ShowResendEmailConfirmationLink": true -} -``` - -## Configure ASP.Net Core Identity options - -The section **IdentityOptions** is binded to the class [`Microsoft.AspNetCore.Identity.IdentityOptions`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.identity.identityoptions). -So you can set any ASP.Net Core Identity options you want from configuration - -```json -"IdentityOptions": { - "User": { - "AllowedUserNameCharacters": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+ " - }, - "SignIn": { - "RequireConfirmedAccount": true - } -} -``` - -## Configure Duende IdentityServer - -The section **IdentityServerOptions** is binded to the class [`Duende.IdentityServer.Configuration.IdentityServerOptions`](https://docs.duendesoftware.com/identityserver/v5/reference/options/). -So you can set any Duende IdentityServer options you want from configuration (but key management options). - -```json -"IdentityServerOptions": { - "Events": { - "RaiseErrorEvents": true, - "RaiseInformationEvents": true, - "RaiseFailureEvents": true, - "RaiseSuccessEvents": true - }, - "Endpoints": { - "EnableJwtRequestUri": true - } -} -``` - -### Discovery document customs entries - -You can add customs entries to the genererated discovery document with *IdentityServerOptions* sub section *CustomEntriesOfStringArray*, *CustomEntriesOfString* and *CustomEntriesOfBool* - -```json -"IdentityServerOptions": { - "CustomEntriesOfStringArray": { - "token_endpoint_auth_signing_alg_values_supported": [ - "RS256", - "ES256", - "ES384", - "ES512", - "PS256", - "PS384", - "PS512", - "RS384", - "RS512" - ] - } -} -``` - -The sample above will add `"token_endpoint_auth_signing_alg_values_supported"` node to the generated document. - -### Mutual TLS client certificate options - -When Muutal TLS is enabled, you can configure the client certificate authentication options with `CertificateAuthenticationOptions` section. - -```json -"IdentityServerOptions": { - "MutualTls": { - "Enabled": true - } -}, -"CertificateAuthenticationOptions": { - "AllowedCertificateTypes": "All", - "ValidateCertificateUse": false, - "ValidateValidityPeriod": false, - "RevocationMode": "NoCheck" -} -``` - -### Retrieves client certificates fron HTTP request header - -When Mutual TLS is enabled the client certificate can be read in PEM format from request header. For exemple if you use a kubernetes NGINX ingress you can configure it to send the client certificate to the backend in the *ssl-client-cert* header. -See [Client Certificate Authentication](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#client-certificate-authentication). - -To retrieve the client certificate from the request header confiure the `MutualTls` sub section like : - -```json -"IdentityServerOptions": { - "MutualTls": { - "Enabled": true, - "PEMHeader": "ssl-client-cert" - } -} -``` - -### Configure Server-side sessions - -Read [Server-side sessions](../../doc/SERVER_SIDE_SESSIONS.md) - -## Configure stores - -### Using Entity Framework Core - -The server supports *SqlServer*, *Sqlite*, *MySql*, *PostgreSQL*, *Oracle*, and *InMemory* databases. -Use **DbType** to the define the database engine. - -```json -"DbType": "SqlServer" -``` - -And **ConnectionStrings:DefaultConnection** to define the connection string. - -```json -"ConnectionStrings": { - "DefaultConnection": "Data Source=(LocalDb)\\MSSQLLocalDB;database=TheIdServer;trusted_connection=yes;" -} -``` - -> A [devart dotConnect for Oracle](https://www.devart.com/dotconnect/oracle/) license is a requirement for Oracle. - -### Using RavenDb - -Use **DbType** to the define the RavenDb database engine. - -```json -"DbType": "RavenDb" -``` - -And **RavenDbOptions** to define the RavenDb options. - -```json -"RavenDbOptions": { - "Urls": [ - "https://a.ravendb.local", - "https://b.ravendb.local", - "https://c.ravendb.local" - ], - "Database": "TheIdServer", - "CertificatePath": "cluster.admin.client.certificate.pfx", - "CertificatePassword": "p@$$w0rd" -} -``` - -> As no `DbContext` will be registered, you cannot store signing keys in EF but you can choose the `RavenDb` storage kind (see [Configure signin keys](#configure-signing-key)): -```json -"IdentityServer": { - "Key": { - "StorageKind": "RavenDb" - } -}, -"DataProtectionOptions": { - "StorageKind": "RavenDb" -} -``` -> The server support RavenDb 4.1 and above. - -### Using MongoDb - -Use **DbType** to the define the RavenDb database engine. - -```json -"DbType": "MongoDb" -``` - -And **ConnectionStrings:DefaultConnection** to define the connection string. - -```json -"ConnectionStrings": { - "DefaultConnection": "mongodb+srv://theidserver:theidserverpwd@cluster0.fvkfz.mongodb.net/TheIdServer?retryWrites=true&w=majority" -} -``` - -> We cannot used another database than the default database defined in the connection string. - -> As no `DbContext` will be registered, you cannot store signing keys in EF but you can choose the `MongoDb` storage kind (see [Configure signin keys](#configure-signing-key)): -```json -"IdentityServer": { - "Key": { - "StorageKind": "MongoDb" - } -}, -"DataProtectionOptions": { - "StorageKind": "MongoDb" -} -``` - -### Using the API - -![public-private.svg](../../doc/assets/public-pribate.png) - -If you don't want to expose a database with your server, you can set up a second server on a private network accessing the database and use this private server API to access data. - -```json -"Proxy": true, -"PrivateServerAuthentication": { - "Authority": "https://theidserverprivate", - "ApiUrl": "https://theidserverprivate/api", - "ClientId": "public-server", - "ClientSecret": "84137599-13d6-469c-9376-9e372dd2c1bd", - "Scope": "theidserveradminapi", - "HttpClientName": "is4" -}, -"SignalR": { - "HubUrl": "https://theidserverprivate/providerhub" - "HubOptions": { - "EnableDetailedErrors": true - }, - "UseMessagePack": true -} -``` - -#### Proxy - -Start the server with proxy mode enabled. - -#### PrivateServerAuthentication - -Defines how to authenticate the public server on private server API. - -#### SignalR - -Defines the [SignalR client](https://docs.microsoft.com/en-us/aspnet/core/signalr/dotnet-client&tabs=visual-studio) configuration. -This client is used to update the external provider configuration of a running instance. When an external provider configuration changes, the API sends a SignalR notification to inform other running instances. - -For more information, read [Load balancing scenario](https://github.com/Aguafrommars/DymamicAuthProviders/wiki/Load-balancing-scenario). - -The SignalR hub accepts requests at */providerhub* and supports the [MessagePack](https://msgpack.org/index.html) protocol. - -For more information, read [Use MessagePack Hub Protocol in SignalR for ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/signalr/messagepackhubprotocol). - -### Database migration and data seeding - -Starting the server with the **/seed** command-line argument creates the database with initial data. Alternatively, configure the server with the following to create a database with initial users, protected resources, identity resources, and clients. - -```json -"Migrate": true, -"Seed": true -``` - -#### Roles - -* **Is4-Writer** authorizes users in this role to write data. -* **Is4-Reader** permits users in this role to read data. - -#### Identity resources - -* **profile** default profile resource with **role** claim -* **openid** default OpenID resource -* **address** default address resource -* **email** default email resource -* **phone** default phone resource - -#### Users - -Users defined in `InitialData:Users` configuration section are loaded and stored to the DB. - -Default configuration: - -```json -"InitialData": { -... - "Users": [ - { - "UserName": "alice", - "Email": "alice@theidserver.com", - "EmailConfirmed": true, - "PhoneNumber": "+41766403736", - "PhoneNumberConfirmed": true, - "Password": "Pass123$", - "Roles": [ - "Is4-Writer", - "Is4-Reader" - ], - "Claims": [ - { - "ClaimType": "name", - "ClaimValue": "Alice Smith" - }, - { - "ClaimType": "given_name", - "ClaimValue": "Alice" - }, - { - "ClaimType": "family_name", - "ClaimValue": "Smith" - }, - { - "ClaimType": "middle_name", - "ClaimValue": "Alice Smith" - }, - { - "ClaimType": "nickname", - "ClaimValue": "alice" - }, - { - "ClaimType": "website", - "ClaimValue": "http://alice.com" - }, - { - "ClaimType": "address", - "ClaimValue": "{ \"street_address\": \"One Hacker Way\", \"locality\": \"Heidelberg\", \"postal_code\": \"69118\", \"country\": \"Germany\" }", - }, - { - "ClaimType": "birthdate", - "ClaimValue": "1970-01-01" - }, - { - "ClaimType": "zoneinfo", - "ClaimValue": "ch" - }, - { - "ClaimType": "gender", - "ClaimValue": "female" - }, - { - "ClaimType": "profile", - "ClaimValue": "http://alice.com/profile" - }, - { - "ClaimType": "locale", - "ClaimValue": "fr" - }, - { - "ClaimType": "picture", - "ClaimValue": "http://alice.com/picture" - } - ] - } - ] -} -``` - -> A user with *Is4-Writer* and *Is4-Reader* roles is required to use the admin app. - -#### Protected resources (API) - -Apis defined in `InitialData:Apis` configuration section are loaded and stored to the DB. - -Default configuration: - -```json -"InitialData": { -... - "Apis": [ - { - "Name": "theidserveradminapi", - "DisplayName": "TheIdServer admin API", - "UserClaims": [ - "name", - "role" - ], - "ApiSecrets": [ - { - "Type": "SharedSecret", - "Value": "5b556f7c-b3bc-4b5b-85ab-45eed0cb962d" - } - ], - "Scopes": [ - "theidserveradminapi" - ] - } - ], -} -``` - -> The api **theidserveradminapi** is required for the admin app. - -#### ApiScopes - -ApiScopes defined in `InitialData:ApiScopes` configuration section are loaded and stored to the DB. - -Default configuration: - -```json -"InitialData": { -... - "ApiScopes": [ - { - "Name": "theidserveradminapi", - "DisplayName": "TheIdServer admin API", - "UserClaims": [ - "name", - "role" - ] - } - ], -} -``` - -> The scope **theidserveradminapi** is required for the admin app. - -#### Clients - -Clients defined in `InitialData:Clients` configuration section are loaded and stored to the DB. - -Default configuration: - -```json -"InitialData": { -... - "Clients": [ - { - "ClientId": "theidserveradmin", - "ClientName": "TheIdServer admin SPA Client", - "ClientUri": "https://localhost:5443/", - "ClientClaimsPrefix": null, - "AllowedGrantTypes": [ "authorization_code" ], - "RequirePkce": true, - "RequireClientSecret": false, - "BackChannelLogoutSessionRequired": false, - "FrontChannelLogoutSessionRequired": false, - "RedirectUris": [ - "http://localhost:5001/authentication/login-callback", - "https://localhost:5443/authentication/login-callback" - ], - "PostLogoutRedirectUris": [ - "http://localhost:5001/authentication/logout-callback", - "https://localhost:5443/authentication/logout-callback" - ], - "AllowedCorsOrigins": [ - "http://localhost:5001", - "https://localhost:5443" - ], - "AllowedScopes": [ - "openid", - "profile", - "theidserveradminapi" - ], - "AccessTokenType": "Reference" - }, - { - "ClientId": "public-server", - "ClientName": "Public server Credentials Client", - "ClientClaimsPrefix": null, - "AllowedGrantTypes": [ "client_credentials" ], - "ClientSecrets": [ - { - "Type": "SharedSecret", - "Value": "84137599-13d6-469c-9376-9e372dd2c1bd" - } - ], - "Claims": [ - { - "Type": "role", - "Value": "Is4-Writer" - }, - { - "Type": "role", - "Value": "Is4-Reader" - } - ], - "BackChannelLogoutSessionRequired": false, - "FrontChannelLogoutSessionRequired": false, - "AllowedScopes": [ - "openid", - "profile", - "theidserveradminapi" - ], - "AccessTokenType": "Reference" - } - ], -} -``` - -> The client **theidserveradmin** is required by the admin app. -> The client **public-server** is required to call web apis and server side prerendering of the admin app. - -## Configure Signing Key - -### Keys rotatation (recommanded) - -TheIdServer can be configured with a keys rotation mechanism instead of a single key. -Read [Keys rotation](../../doc/KEYS_ROTATION.md) to know how to configure it. - -```json -"IdentityServer": { - "Key": { - "Type": "KeysRotation", - "StorageKind": "EntityFramework" - } -} -``` - -### From file - -```json -"IdentityServer": { - "Key": { - "Type": "File", - "FilePath": "{path to the .pfx}", - "Password": "{.pfx password}" - } -} -``` - -### From store - -Read [Example: Deploy to Azure Websites](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity-api-authorization#example-deploy-to-azure-websites) - -## Configure the Email service - -By default, the server uses [SendGrid](https://sendgrid.com/) to send Emails by calling the API at */api/email* - -```json -"SendGridUser": "your user", -"SendGridKey": "your SendGrid key" -``` - -### Use your API - -If you prefer to use your Email sender, implement a Web API receiving a POST request with the json: - -```json -{ - "subject": "Email subject", - "message": "Email message", - "addresses": [ - "an-address@aguacongas.con" - ] -} -``` - -And update the *EmailApiAuthentication* configuration section: - -```json -"EmailApiAuthentication": { - "Authority": "https://localhost:5443", - "ApiUrl": "https://localhost:5443/api/email", - "ClientId": "public-server", - "ClientSecret": "84137599-13d6-469c-9376-9e372dd2c1bd", - "Scope": "theidserveradminapi", - "HttpClientName": "email" -} -``` - -> If you want to use the same authentication configuration and token for both *EmailApi* and *PrivateServer*, you can simplify it by sharing the same **HttpClientName**. - -```json -"EmailApiAuthentication": { - "ApiUrl": "https://localhost:5443/api/email", - "HttpClientName": "is4" -} -``` - -## Configure the 2fa authenticator issuer - -By default, the issuer for the 2fa authenticator is **Aguacongas.TheIdServer**. -To update this value, set **AuthenticatorIssuer** with your issuer. - -```json -"AuthenticatorIssuer": "TheIdServer" -``` - -## Configure the API - -### Authentication - -The *ApiAuthentication* section defines the authentication configuration for the API. - -```json -"ApiAuthentication": { - "Authority": "https://localhost", - "RequireHttpsMetadata": false, - "SupportedTokens": "Both", - "ApiName": "theidserveradminapi", - "ApiSecret": "5b556f7c-b3bc-4b5b-85ab-45eed0cb962d", - "EnableCaching": true, - "CacheDuration": "0:10:0", - "LegacyAudienceValidation": true -} -``` - -### Documentation endpoint - -To enable the API documentation, set **EnableOpenApiDoc** to `true`. - -```json -"EnableOpenApiDoc": true -``` - -Use the section *SwaggerUiSettings* to configure the swagger client authentication. - -```json -"SwaggerUiSettings": { - "OAuth2Client": { - "ClientId": "theidserver-swagger", - "AppName": "TheIdServer Swagger UI", - "UsePkceWithAuthorizationCodeGrant": true - }, - "WithCredentials": true -} -``` - -### CORS - -The section *CorsAllowedOrigin* defines allowed CORS origins. - -```json -"CorsAllowedOrigin": [ - "http://localhost:5001" -] -``` - -## Configure HTTPS - -To disable HTTPS, set **DisableHttps** to `false`. - -```json -"DisableHttps": true -``` - -If you use a self-signed certificate, you can disable strict-SSL by settings **DisableStrictSsl** to `true`. - -```json -"DisableStrictSsl": true -``` - -### Configure Forwarded Headers - -The section **ForwardedHeadersOptions** is bound to the class [`Microsoft.AspNetCore.Builder.ForwardedHeadersOptions`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.forwardedheadersoptions). - -```json -"ForwardedHeadersOptions": { - "ForwardedHeaders": "All" -} -``` - -### Force HTTPS scheme - -Some reverses proxies don't' forward headers. You can force HTTP requests schemes to https by settings ForceHttpsScheme. - -```json -"ForceHttpsScheme": true -``` - -## Configure the provider hub - -The [Aguacongas.AspNetCore.Authentication library](https://github.com/Aguafrommars/DymamicAuthProviders) dynamically configures external providers. -In a [load-balanced](https://github.com/Aguafrommars/DymamicAuthProviders/wiki/Load-balancing-scenario) configuration, the provider hub informs other running instances that an external provider configuration changes. -The **SignalR** section defines the configuration for both the SignalR hub and the client. - -```json -"SignalR": { - "HubUrl": "https://theidserverprivate/providerhub", - "HubOptions": { - "EnableDetailedErrors": true - }, - "UseMessagePack": true, - "RedisConnectionString": "redis:6379", - "RedisOptions": { - "Configuration": { - "ChannelPrefix": "TheIdServer" - } - } -} -``` - -If needed, the hub can use a [Redis backplane](https://docs.microsoft.com/en-us/aspnet/core/signalr/redis-backplane). **SignalR:RedisConnectionString** and **SignalR:RedisOptions** configures the backplane. -**SignalR:RedisOptions** is bound to an instance of [`Microsoft.AspNetCore.SignalR.StackExchangeRedis.RedisOptions`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.signalr.stackexchangeredis.redisoptions?view=aspnetcore-3.0) at startup. - -## Configure logs - -The **Serilog** section defines the [Serilog](https://serilog.net/) configuration. - -```json -"Serilog": { - "LevelSwitches": { - "$controlSwitch": "Information" - }, - "MinimumLevel": { - "ControlledBy": "$controlSwitch" - }, - "WriteTo": [ - { - "Name": "Seq", - "Args": { - "serverUrl": "http://localhost:5341", - "controlLevelSwitch": "$controlSwitch", - "apiKey": "DVYuookX2vOq078fuOyJ" - } - }, - { - "Name": "Console", - "Args": { - "outputTemplate": "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", - "theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Literate, Serilog.Sinks.Console" - } - }, - { - "Name": "Debug", - "Args": { - "outputTemplate": "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}" - } - } - ], - "Enrich": [ - "FromLogContext", - "WithMachineName", - "WithThreadId" - ] -} -``` -For more details, read [Serilog.Settings.Configuration](https://github.com/serilog/serilog-settings-configuration/blob/dev/README.md). - -## Configure claims providers - -[Claims provider](../../doc/CLAIMS_PROVIDER.md) provides details on claims proivder configuration. - -## Configure token cleaner - -The token cleaner task removes expired tokens periodically. To configure the interval, use **TokenCleanupInterval**. - -```json -"TokenCleanupInterval": "00:05:00" -``` - -To disable the task, use **DisableTokenCleanup**. - -```json -"DisableTokenCleanup": true -``` - -> The task is not enabled on proxy server. - -## Configure Dynamic client registration allowed contacts a host - -The server supports [OpenID Connect Dynamic Client Registration](https://openid.net/specs/openid-connect-registration-1_0.html). - -New client registration is allowed to users with the **Is4-Writer** role by sending the user access token or to contacts defined in *DynamicClientRegistrationOptions* section. - -```json -"DynamicClientRegistrationOptions": { - "AllowedContacts": [ - { - "Contact": "certification@oidf.org", - "AllowedHosts": [ - "www.certification.openid.net" - ] - } - ] -} -``` - -It this case, the client registration request must contain the *contacts* array. - -**request sample** - -```json -{ - "client_name": "oidc_cert_client gUPPBlHIEAqNOYR", - "grant_types": [ - "authorization_code" - ], - "response_types": [ - "code" - ], - "redirect_uris": [ - "https://www.certification.openid.net/test/a/theidserver/callback" - ], - "contacts": [ - "certification@oidf.org" - ] -} -``` - -## Configure Jwt request validator - -Tokens returned by request_uri parameter are validated using the rules defined in *TokenValidationParameters* section. By default, the following rules are defined. - -```json -"TokenValidationParameters": { - "ValidateIssuer": false, - "ValidateAudience": false, - "ValidateIssuerSigningKey": false, - "ValidateLifetime": false, - "RequireAudience": false, - "RequireExpirationTime": false, - "RequireSignedTokens": false -} -``` - -> To enable JWT request uri, set *EnableJwtRequestUri* to true in *IdentityServerOptions:Endpoints* -> ```json -> "IdentityServerOptions": { -> "Endpoints": { -> "EnableJwtRequestUri": true -> } -> }, -> ``` - -## Configure WS-Federation endpoint - -Read [Aguacongas.IdentityServer.WsFederation.Duende](../IdentityServer/Duende/Aguacongas.IdentityServer.WsFederation.Duende/README.md) - -## Configure CIBA notification service - -Read [DUENDE CIBA INTEGRATION/Notification service](../../doc/CIBA.md#Notification-service) - -## Use the client to override the default configuration - -The server and the blazor app integrate [Aguafrommars/DynamicConfiguration](https://github.com/Aguafrommars/DynamicConfiguration). Most of the configuration can be ovveriden using the blazor app. - -Use **DynamicConfigurationOptions** to define the dynamic configuration provider. - -```json -"DynamicConfigurationOptions": { - "ProviderType": "Aguacongas.DynamicConfiguration.Redis.RedisConfigurationProvider, Aguacongas.DynamicConfiguration.Redis" -} -``` -Use **RedisConfigurationOptions** section to configure the Redis db. - -```json -"RedisConfigurationOptions": { - "ConnectionString": "localhost", - "HashKey": "Aguacongas.TheIdServer.Duende", - "Channel": "Aguacongas.TheIdServer.Duende.Channel" -} -``` - -## Health checks - -The server expose an health checks enpoint you can use for docker on kubernetes at **/healthz**. - -The endpoit return a json reponse depending on the store kind used and redis dependencies : - -```json -{ - "status": "Healthy", - "results": { - "ConfigurationDbContext": { - "status": "Healthy" - }, - "OperationalDbContext": { - "status": "Healthy" - }, - "ApplicationDbContext": { - "status": "Healthy" - }, - "DynamicConfigurationRedis": { - "status": "Healthy" - } - } -} -``` - -## Configure OpenTelemetry - -[Configure OpenTelemetry doc](../../doc/OPEN_TELEMETRY.md) provides details on [OpenTelemetry](https://opentelemetry.io/) configuration. - -## Additional resources - -* [Host and deploy ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/) -* [DymamicAuthProviders](https://github.com/Aguafrommars/DymamicAuthProviders) -* [Set up a Redis backplane for ASP.NET Core SignalR scale-out](https://docs.microsoft.com/en-us/aspnet/core/signalr/redis-backplane) -* [Microsoft.AspNetCore.SignalR.StackExchangeRedis.RedisOptions](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.signalr.stackexchangeredis.redisoptions?view=aspnetcore-3.0) -* [Serilog.Settings.Configuration](https://github.com/serilog/serilog-settings-configuration/blob/dev/README.md) -* [Hosting ASP.NET Core images with Docker over HTTPS](https://docs.microsoft.com/en-us/aspnet/core/security/docker-https) -* [OpenID Connect Dynamic Client Registration](https://openid.net/specs/openid-connect-registration-1_0.html) -* [Aguafrommars/DynamicConfiguration](https://github.com/Aguafrommars/DynamicConfiguration) -* [OpenTelemetry](https://opentelemetry.io/) +[Doc](../../doc/SERVER.md) \ No newline at end of file diff --git a/src/Aguacongas.TheIdServer.Duende/SeedData.cs b/src/Aguacongas.TheIdServer.Duende/SeedData.cs index 309456a9d..99b090881 100644 --- a/src/Aguacongas.TheIdServer.Duende/SeedData.cs +++ b/src/Aguacongas.TheIdServer.Duende/SeedData.cs @@ -4,28 +4,26 @@ using Aguacongas.IdentityServer.Store; using Aguacongas.TheIdServer.Data; using Aguacongas.TheIdServer.Models; +using Duende.IdentityServer; using IdentityModel; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; +using Serilog; using System.Security.Claims; using System.Text.Json; -using System.Threading.Tasks; using Entity = Aguacongas.IdentityServer.Store.Entity; -using Duende.IdentityServer; using ISModels = Duende.IdentityServer.Models; namespace Aguacongas.TheIdServer { public static class SeedData { - public static void EnsureSeedData(IConfiguration configuration, IServiceProvider services) + private static readonly JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + + public static void EnsureSeedData(IConfiguration configuration, IServiceProvider services) { using var scope = services.CreateScope(); @@ -38,7 +36,7 @@ public static void EnsureSeedData(IConfiguration configuration, IServiceProvider var opContext = scope.ServiceProvider.GetRequiredService(); opContext.Database.Migrate(); - var appcontext = scope.ServiceProvider.GetService(); + var appcontext = scope.ServiceProvider.GetRequiredService(); appcontext.Database.Migrate(); } @@ -74,17 +72,17 @@ public static void SeedUsers(IServiceScope scope, IConfiguration configuration) int index = 0; foreach (var user in userList) { - var existing = userMgr.FindByNameAsync(user.UserName).GetAwaiter().GetResult(); + var existing = userMgr.FindByNameAsync(user.UserName!).GetAwaiter().GetResult(); if (existing != null) { logger.LogInformation("{UserName} already exists", user.UserName); continue; } var pwd = configuration.GetValue($"InitialData:Users:{index}:Password"); - ExcuteAndCheckResult(() => userMgr.CreateAsync(user, pwd)) + ExcuteAndCheckResult(() => userMgr.CreateAsync(user, pwd!)) .GetAwaiter().GetResult(); - var claimList = configuration.GetSection($"InitialData:Users:{index}:Claims").Get>() + var claimList = configuration.GetSection($"InitialData:Users:{index}:Claims").Get>()! .Select(c => new Claim(c.ClaimType, c.ClaimValue, c.OriginalType, c.Issuer)) .ToList(); claimList.Add(new Claim(JwtClaimTypes.UpdatedAt, DateTime.Now.ToEpochTime().ToString(), ClaimValueTypes.Integer64)); @@ -92,7 +90,7 @@ public static void SeedUsers(IServiceScope scope, IConfiguration configuration) .GetAwaiter().GetResult(); var roleList = configuration.GetSection($"InitialData:Users:{index}:Roles").Get>(); - ExcuteAndCheckResult(() => userMgr.AddToRolesAsync(user, roleList)) + ExcuteAndCheckResult(() => userMgr.AddToRolesAsync(user, roleList!)) .GetAwaiter().GetResult(); logger.LogInformation("{UserName} created", user.UserName); @@ -155,12 +153,9 @@ private static void SeedCultureFile(IServiceProvider provider, string file) } var exsitings = culture.Resources.ToList(); - var resources = JsonSerializer.Deserialize>(File.ReadAllText(file), new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }); + var resources = JsonSerializer.Deserialize>(File.ReadAllText(file), _jsonSerializerOptions); - foreach (var resource in resources) + foreach (var resource in resources!) { if (!exsitings.Any(r => r.Key == resource.Key)) { @@ -533,7 +528,14 @@ private static void SeedClients(IConfiguration configuration, IServiceProvider p SlidingRefreshTokenLifetime = client.SlidingRefreshTokenLifetime, UpdateAccessTokenClaimsOnRefresh = client.UpdateAccessTokenClaimsOnRefresh, UserCodeType = client.UserCodeType, - UserSsoLifetime = client.UserSsoLifetime + UserSsoLifetime = client.UserSsoLifetime, + CibaLifetime = client.CibaLifetime, + CoordinateLifetimeWithUserSession = client.CoordinateLifetimeWithUserSession, + PollingInterval = client.PollingInterval, + RequireRequestObject = client.RequireRequestObject, + RequireDPoP = client.RequireDPoP, + PushedAuthorizationLifetime = client.PushedAuthorizationLifetime, + RequirePushedAuthorization = client.RequirePushedAuthorization }).GetAwaiter().GetResult(); } catch (ArgumentException) diff --git a/src/Aguacongas.TheIdServer.Duende/Views/Account/LoggedOut.cshtml b/src/Aguacongas.TheIdServer.Duende/Views/Account/LoggedOut.cshtml index 754ae4361..7478ed04f 100644 --- a/src/Aguacongas.TheIdServer.Duende/Views/Account/LoggedOut.cshtml +++ b/src/Aguacongas.TheIdServer.Duende/Views/Account/LoggedOut.cshtml @@ -20,7 +20,7 @@ Copyright (c) 2023 @Olivier Lefebvre @if (Model.PostLogoutRedirectUri != null) {
- @Localizer["Click here to return to the {1} application.", Model.PostLogoutRedirectUri, Model.ClientName] + @Localizer["Click here to return to the {1} application.", Model.PostLogoutRedirectUri, Model.ClientName!]
} diff --git a/src/Aguacongas.TheIdServer.Duende/Views/Account/Login.cshtml b/src/Aguacongas.TheIdServer.Duende/Views/Account/Login.cshtml index c560dd0b2..7aedae74a 100644 --- a/src/Aguacongas.TheIdServer.Duende/Views/Account/Login.cshtml +++ b/src/Aguacongas.TheIdServer.Duende/Views/Account/Login.cshtml @@ -80,7 +80,7 @@
} - @if (Model.VisibleExternalProviders.Any()) + @if (Model.VisibleExternalProviders?.Any() == true) {
@@ -107,7 +107,7 @@
} - @if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any()) + @if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders?.Any() == true) {
diff --git a/src/Aguacongas.TheIdServer.Duende/Views/Ciba/Consent.cshtml b/src/Aguacongas.TheIdServer.Duende/Views/Ciba/Consent.cshtml index b11b481c6..e18bc8d9c 100644 --- a/src/Aguacongas.TheIdServer.Duende/Views/Ciba/Consent.cshtml +++ b/src/Aguacongas.TheIdServer.Duende/Views/Ciba/Consent.cshtml @@ -28,10 +28,10 @@ Copyright (c) 2023 @Olivier Lefebvre
- +
- @if (Model.IdentityScopes.Any()) + @if (Model.IdentityScopes?.Any() == true) {
@@ -49,7 +49,7 @@ Copyright (c) 2023 @Olivier Lefebvre
} - @if (Model.ApiScopes.Any()) + @if (Model.ApiScopes?.Any() == true) {
@@ -74,7 +74,7 @@ Copyright (c) 2023 @Olivier Lefebvre @Localizer["Description"]
- +
diff --git a/src/Aguacongas.TheIdServer.Duende/Views/Consent/Index.cshtml b/src/Aguacongas.TheIdServer.Duende/Views/Consent/Index.cshtml index 9f91e829f..b296aaa68 100644 --- a/src/Aguacongas.TheIdServer.Duende/Views/Consent/Index.cshtml +++ b/src/Aguacongas.TheIdServer.Duende/Views/Consent/Index.cshtml @@ -14,7 +14,7 @@ Copyright (c) 2023 @Olivier Lefebvre }

- @Localizer["{0} is requesting your permission", @Model.ClientName] + @Localizer["{0} is requesting your permission", @Model.ClientName!]

@@ -28,7 +28,7 @@ Copyright (c) 2023 @Olivier Lefebvre
@Localizer["Uncheck the permissions you do not wish to grant."]
- @if (Model.IdentityScopes.Any()) + @if (Model.IdentityScopes?.Any() == true) {
@Localizer["Pending Backchannel Login Requests"]
- - - - - - - - - - @foreach (var forecast in forecasts) - { - - - - - - - } - -
DateTemp. (C)Temp. (F)Summary
@forecast.Date.ToShortDateString()@forecast.TemperatureC@forecast.TemperatureF@forecast.Summary
-} - -@code { - private WeatherForecast[] forecasts; - - protected override async Task OnInitializedAsync() - { - forecasts = await ForecastService.GetForecastAsync(DateTime.Now); - } -} diff --git a/src/Aguacongas.TheIdServer.MauiApp/Pages/Index.razor b/src/Aguacongas.TheIdServer.MauiApp/Pages/Index.razor deleted file mode 100644 index e54d91439..000000000 --- a/src/Aguacongas.TheIdServer.MauiApp/Pages/Index.razor +++ /dev/null @@ -1,7 +0,0 @@ -@page "/" - -

Hello, world!

- -Welcome to your new app. - - diff --git a/src/Aguacongas.TheIdServer.MauiApp/Platforms/Tizen/Main.cs b/src/Aguacongas.TheIdServer.MauiApp/Platforms/Tizen/Main.cs deleted file mode 100644 index bfe69aee9..000000000 --- a/src/Aguacongas.TheIdServer.MauiApp/Platforms/Tizen/Main.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Microsoft.Maui; -using Microsoft.Maui.Hosting; -using System; - -namespace Aguacongas.TheIdServer.MauiApp -{ - internal class Program : MauiApplication - { - protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); - - static void Main(string[] args) - { - var app = new Program(); - app.Run(args); - } - } -} \ No newline at end of file diff --git a/src/Aguacongas.TheIdServer.MauiApp/Platforms/Tizen/tizen-manifest.xml b/src/Aguacongas.TheIdServer.MauiApp/Platforms/Tizen/tizen-manifest.xml deleted file mode 100644 index c07cadf2e..000000000 --- a/src/Aguacongas.TheIdServer.MauiApp/Platforms/Tizen/tizen-manifest.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - appicon.xhigh.png - - - - - http://tizen.org/privilege/internet - - - - \ No newline at end of file diff --git a/src/Aguacongas.TheIdServer.MauiApp/Resources/AppIcon/appicon.svg b/src/Aguacongas.TheIdServer.MauiApp/Resources/AppIcon/appicon.svg deleted file mode 100644 index 9d63b6513..000000000 --- a/src/Aguacongas.TheIdServer.MauiApp/Resources/AppIcon/appicon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/Aguacongas.TheIdServer.MauiApp/Resources/AppIcon/appiconfg.svg b/src/Aguacongas.TheIdServer.MauiApp/Resources/AppIcon/appiconfg.svg deleted file mode 100644 index 21dfb25f1..000000000 --- a/src/Aguacongas.TheIdServer.MauiApp/Resources/AppIcon/appiconfg.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/src/Aguacongas.TheIdServer.MauiApp/Resources/Raw/AboutAssets.txt b/src/Aguacongas.TheIdServer.MauiApp/Resources/Raw/AboutAssets.txt deleted file mode 100644 index 15d624484..000000000 --- a/src/Aguacongas.TheIdServer.MauiApp/Resources/Raw/AboutAssets.txt +++ /dev/null @@ -1,15 +0,0 @@ -Any raw assets you want to be deployed with your application can be placed in -this directory (and child directories). Deployment of the asset to your application -is automatically handled by the following `MauiAsset` Build Action within your `.csproj`. - - - -These files will be deployed with you package and will be accessible using Essentials: - - async Task LoadMauiAsset() - { - using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt"); - using var reader = new StreamReader(stream); - - var contents = reader.ReadToEnd(); - } diff --git a/src/Aguacongas.TheIdServer.MauiApp/Resources/Splash/splash.svg b/src/Aguacongas.TheIdServer.MauiApp/Resources/Splash/splash.svg deleted file mode 100644 index 21dfb25f1..000000000 --- a/src/Aguacongas.TheIdServer.MauiApp/Resources/Splash/splash.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/src/Aguacongas.TheIdServer.MauiApp/Shared/MainLayout.razor.css b/src/Aguacongas.TheIdServer.MauiApp/Shared/MainLayout.razor.css deleted file mode 100644 index 53213f49f..000000000 --- a/src/Aguacongas.TheIdServer.MauiApp/Shared/MainLayout.razor.css +++ /dev/null @@ -1,75 +0,0 @@ -.page { - position: relative; - display: flex; - flex-direction: column; -} - -main { - flex: 1; -} - -.sidebar { - background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); -} - -.top-row { - background-color: #f7f7f7; - border-bottom: 1px solid #d6d5d5; - justify-content: flex-end; - height: 3.5rem; - display: flex; - align-items: center; -} - - .top-row ::deep a, .top-row ::deep .btn-link { - white-space: nowrap; - margin-left: 1.5rem; - text-decoration: none; - } - - .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { - text-decoration: underline; - } - - .top-row ::deep a:first-child { - overflow: hidden; - text-overflow: ellipsis; - } - -@media (max-width: 640.98px) { - .top-row:not(.auth) { - display: none; - } - - .top-row.auth { - justify-content: space-between; - } - - .top-row ::deep a, .top-row ::deep .btn-link { - margin-left: 0; - } -} - -@media (min-width: 641px) { - .page { - flex-direction: row; - } - - .sidebar { - width: 250px; - height: 100vh; - position: sticky; - top: 0; - } - - .top-row { - position: sticky; - top: 0; - z-index: 1; - } - - .top-row, article { - padding-left: 2rem !important; - padding-right: 1.5rem !important; - } -} diff --git a/src/Aguacongas.TheIdServer.MauiApp/Shared/NavMenu.razor b/src/Aguacongas.TheIdServer.MauiApp/Shared/NavMenu.razor deleted file mode 100644 index e91521128..000000000 --- a/src/Aguacongas.TheIdServer.MauiApp/Shared/NavMenu.razor +++ /dev/null @@ -1,39 +0,0 @@ - - -
- -
- -@code { - private bool collapseNavMenu = true; - - private string NavMenuCssClass => collapseNavMenu ? "collapse" : null; - - private void ToggleNavMenu() - { - collapseNavMenu = !collapseNavMenu; - } -} diff --git a/src/Aguacongas.TheIdServer.MauiApp/Shared/NavMenu.razor.css b/src/Aguacongas.TheIdServer.MauiApp/Shared/NavMenu.razor.css deleted file mode 100644 index acc5f9f81..000000000 --- a/src/Aguacongas.TheIdServer.MauiApp/Shared/NavMenu.razor.css +++ /dev/null @@ -1,62 +0,0 @@ -.navbar-toggler { - background-color: rgba(255, 255, 255, 0.1); -} - -.top-row { - height: 3.5rem; - background-color: rgba(0,0,0,0.4); -} - -.navbar-brand { - font-size: 1.1rem; -} - -.oi { - width: 2rem; - font-size: 1.1rem; - vertical-align: text-top; - top: -2px; -} - -.nav-item { - font-size: 0.9rem; - padding-bottom: 0.5rem; -} - - .nav-item:first-of-type { - padding-top: 1rem; - } - - .nav-item:last-of-type { - padding-bottom: 1rem; - } - - .nav-item ::deep a { - color: #d7d7d7; - border-radius: 4px; - height: 3rem; - display: flex; - align-items: center; - line-height: 3rem; - } - -.nav-item ::deep a.active { - background-color: rgba(255,255,255,0.25); - color: white; -} - -.nav-item ::deep a:hover { - background-color: rgba(255,255,255,0.1); - color: white; -} - -@media (min-width: 641px) { - .navbar-toggler { - display: none; - } - - .collapse { - /* Never collapse the sidebar for wide screens */ - display: block; - } -} diff --git a/src/Aguacongas.TheIdServer.MauiApp/Shared/SurveyPrompt.razor b/src/Aguacongas.TheIdServer.MauiApp/Shared/SurveyPrompt.razor deleted file mode 100644 index fc674356e..000000000 --- a/src/Aguacongas.TheIdServer.MauiApp/Shared/SurveyPrompt.razor +++ /dev/null @@ -1,16 +0,0 @@ -
- - @Title - - - Please take our - brief survey - - and tell us what you think. -
- -@code { - // Demonstrates how a parent component can supply parameters - [Parameter] - public string Title { get; set; } -} diff --git a/src/Aguacongas.TheIdServer/.config/dotnet-tools.json b/src/Aguacongas.TheIdServer/.config/dotnet-tools.json deleted file mode 100644 index e53f86600..000000000 --- a/src/Aguacongas.TheIdServer/.config/dotnet-tools.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "version": 1, - "isRoot": true, - "tools": { - "dotnet-ef": { - "version": "3.1.7", - "commands": [ - "dotnet-ef" - ] - } - } -} \ No newline at end of file diff --git a/src/Aguacongas.TheIdServer/Aguacongas.TheIdServer.csproj b/src/Aguacongas.TheIdServer/Aguacongas.TheIdServer.csproj index aeb7529e2..fc84e7368 100644 --- a/src/Aguacongas.TheIdServer/Aguacongas.TheIdServer.csproj +++ b/src/Aguacongas.TheIdServer/Aguacongas.TheIdServer.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre Apache-2.0 @@ -30,45 +30,45 @@ 1701;1702;CA1416;NU1603;NU1608 - - + + - + - - - - - - - - - - + + + + + + + + + + - + - - - + + + - - - - + + + + - - + + - + diff --git a/src/Aguacongas.TheIdServer/Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml.cs b/src/Aguacongas.TheIdServer/Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml.cs index 486d79410..90f872ca1 100644 --- a/src/Aguacongas.TheIdServer/Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml.cs +++ b/src/Aguacongas.TheIdServer/Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml.cs @@ -46,7 +46,7 @@ public async Task OnPostAsync() personalData.Add(p.Name, p.GetValue(user)?.ToString() ?? "null"); } - Response.Headers.Add("Content-Disposition", "attachment; filename=PersonalData.json"); + Response.Headers.ContentDisposition = "attachment; filename=PersonalData.json"; return new FileContentResult(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(personalData)), "text/json"); } } diff --git a/src/Aguacongas.TheIdServer/Dockerfile-windows b/src/Aguacongas.TheIdServer/Dockerfile-windows deleted file mode 100644 index 0f24681f1..000000000 --- a/src/Aguacongas.TheIdServer/Dockerfile-windows +++ /dev/null @@ -1,67 +0,0 @@ -#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. - -#Depending on the operating system of the host machines(s) that will build or run the containers, the image specified in the FROM statement may need to be changed. -#For more information, please see https://aka.ms/containercompat - -FROM mcr.microsoft.com/dotnet/core/aspnet:6.0 AS base -WORKDIR /app -EXPOSE 80 -EXPOSE 443 - -FROM mcr.microsoft.com/dotnet/core/sdk:6.0 AS build -WORKDIR /src -COPY ["src/Aguacongas.TheIdServer/Aguacongas.TheIdServer.csproj", "src/Aguacongas.TheIdServer/"] -COPY ["src/Aguacongas.TheIdServer.Identity/Aguacongas.TheIdServer.Identity.csproj", "src/Aguacongas.TheIdServer.Identity/"] -COPY ["src/IdentityServer/Aguacongas.IdentityServer.Http.Store/Aguacongas.IdentityServer.Http.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.Http.Store/"] -COPY ["src/IdentityServer/Aguacongas.IdentityServer.Admin.Http.Store/Aguacongas.IdentityServer.Admin.Http.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.Admin.Http.Store/"] -COPY ["src/IdentityServer/Aguacongas.IdentityServer.Store/Aguacongas.IdentityServer.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.Store/"] -COPY ["src/IdentityServer/Aguacongas.IdentityServer/Aguacongas.IdentityServer.csproj", "src/IdentityServer/Aguacongas.IdentityServer/"] -COPY ["src/IdentityServer/Aguacongas.IdentityServer.EntityFramework.Store/Aguacongas.IdentityServer.EntityFramework.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.EntityFramework.Store/"] -COPY ["src/IdentityServer/Aguacongas.IdentityServer.RavenDb.Store/Aguacongas.IdentityServer.RavenDb.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.RavenDb.Store/"] -COPY ["src/IdentityServer/Aguacongas.IdentityServer.MongoDb.Store/Aguacongas.IdentityServer.MongoDb.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.MongoDb.Store/"] -COPY ["src/IdentityServer/Aguacongas.IdentityServer.WsFederation/Aguacongas.IdentityServer.WsFederation.csproj", "src/IdentityServer/Aguacongas.IdentityServer.WsFederation/"] -COPY ["src/Aguacongas.TheIdServer.BlazorApp/Aguacongas.TheIdServer.BlazorApp.csproj", "src/Aguacongas.TheIdServer.BlazorApp/"] -COPY ["src/IdentityServer/Aguacongas.IdentityServer.Admin/Aguacongas.IdentityServer.Admin.csproj", "src/IdentityServer/Aguacongas.IdentityServer.Admin/"] -COPY ["src/Aguacongas.TheIdServer.Authentication/Aguacongas.TheIdServer.Authentication.csproj", "src/Aguacongas.TheIdServer.Authentication/"] -COPY ["src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.MySql/Aguacongas.TheIdServer.Migrations.MySql.csproj", "src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.MySql/"] -COPY ["src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Oracle/Aguacongas.TheIdServer.Migrations.Oracle.csproj", "src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Oracle/"] -COPY ["src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/Aguacongas.TheIdServer.Migrations.PostgreSQL.csproj", "src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/"] -COPY ["src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Aguacongas.TheIdServer.Migrations.Sqlite.csproj", "src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/"] -COPY ["src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Aguacongas.TheIdServer.Migrations.SqlServer.csproj", "src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/"] -COPY ["src/IdentityServer/Aguacongas.IdentityServer.KeysRotation/Aguacongas.IdentityServer.KeysRotation.csproj", "src/IdentityServer/Aguacongas.IdentityServer.KeysRotation/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Components/Aguacongas.TheIdServer.BlazorApp.Components.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Components/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Infrastructure/Aguacongas.TheIdServer.BlazorApp.Infrastructure.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Infrastructure/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages/Aguacongas.TheIdServer.BlazorApp.Pages.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Api/Aguacongas.TheIdServer.BlazorApp.Pages.Api.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Api/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Apis/Aguacongas.TheIdServer.BlazorApp.Pages.Apis.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Apis/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScope/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScope.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScope/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScopes/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScopes.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScopes/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Client/Aguacongas.TheIdServer.BlazorApp.Pages.Client.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Client/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Clients/Aguacongas.TheIdServer.BlazorApp.Pages.Clients.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Clients/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Culture/Aguacongas.TheIdServer.BlazorApp.Pages.Culture.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Culture/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Cultures/Aguacongas.TheIdServer.BlazorApp.Pages.Cultures.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Cultures/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProvider/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProvider.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProvider/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProviders/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProviders.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProviders/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Identities/Aguacongas.TheIdServer.BlazorApp.Pages.Identities.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Identities/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Identity/Aguacongas.TheIdServer.BlazorApp.Pages.Identity.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Identity/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Import/Aguacongas.TheIdServer.BlazorApp.Pages.Import.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Import/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Keys/Aguacongas.TheIdServer.BlazorApp.Pages.Keys.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Keys/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Role/Aguacongas.TheIdServer.BlazorApp.Pages.Role.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Role/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Roles/Aguacongas.TheIdServer.BlazorApp.Pages.Roles.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Roles/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.User/Aguacongas.TheIdServer.BlazorApp.Pages.User.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.User/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Users/Aguacongas.TheIdServer.BlazorApp.Pages.Users.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Users/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParties/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParties.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParties/"] -COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParty/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParty.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParty/"] -RUN dotnet restore "src/Aguacongas.TheIdServer/Aguacongas.TheIdServer.csproj" -RUN dotnet workload install microsoft-net-sdk-blazorwebassembly-aot -COPY . . -WORKDIR "/src/src/Aguacongas.TheIdServer" -RUN dotnet build "Aguacongas.TheIdServer.csproj" -c Release -o /app/build - -FROM build AS publish -RUN dotnet publish "Aguacongas.TheIdServer.csproj" -c Release -o /app/publish - -FROM base AS final -WORKDIR /app -COPY --from=publish /app/publish . -ENTRYPOINT ["dotnet", "Aguacongas.TheIdServer.dll"] \ No newline at end of file diff --git a/src/Aguacongas.TheIdServer/Extensions/TracerProviderBuilderExtensions.cs b/src/Aguacongas.TheIdServer/Extensions/TracerProviderBuilderExtensions.cs index d8a3875b1..85269b6a3 100644 --- a/src/Aguacongas.TheIdServer/Extensions/TracerProviderBuilderExtensions.cs +++ b/src/Aguacongas.TheIdServer/Extensions/TracerProviderBuilderExtensions.cs @@ -55,7 +55,6 @@ public static TracerProviderBuilder AddInstrumentation(this TracerProviderBuilde return; } - o.SetHttpFlavor = httpClientOptions.SetHttpFlavor; o.RecordException = httpClientOptions.RecordException; }) .AddAspNetCoreInstrumentation(o => diff --git a/src/Aguacongas.TheIdServer/Models/SiteOptions.cs b/src/Aguacongas.TheIdServer/Models/SiteOptions.cs index 3116e2043..5ac4d60e0 100644 --- a/src/Aguacongas.TheIdServer/Models/SiteOptions.cs +++ b/src/Aguacongas.TheIdServer/Models/SiteOptions.cs @@ -5,10 +5,13 @@ namespace Aguacongas.TheIdServer.Models { public class SiteOptions { - public static readonly string BOOTSTRAPCSSURL = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css"; - public static readonly string BOOTSTRAPJSURL = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"; + public static readonly string BOOTSTRAPCSSURL = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"; + public static readonly string BOOTSTRAPJSURL = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"; public static readonly string JQUERYURL = "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.slim.min.js"; + public static readonly string BOOTSTRAPCSSURLINTEGRITY = "sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN"; + public static readonly string BOOTSTRAPJSURLINTEGRITY = "sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"; + public static readonly string JQUERYURLINTEGRITY = "sha512-6ORWJX/LrnSjBzwefdNUyLCMTIsGoNP6NftMy2UAm1JBm6PRZCO1d7OHBStWpVFZLO+RerTvqX/Z9mBFfCJZ4A=="; public string Name { get; set; } } } diff --git a/src/Aguacongas.TheIdServer/Options/OpenTelemetry/RedisOptions.cs b/src/Aguacongas.TheIdServer/Options/OpenTelemetry/RedisOptions.cs index 52d8d3d6f..4c802e827 100644 --- a/src/Aguacongas.TheIdServer/Options/OpenTelemetry/RedisOptions.cs +++ b/src/Aguacongas.TheIdServer/Options/OpenTelemetry/RedisOptions.cs @@ -2,7 +2,7 @@ namespace Aguacongas.TheIdServer.Options.OpenTelemetry { - public class RedisOptions : StackExchangeRedisCallsInstrumentationOptions + public class RedisOptions : StackExchangeRedisInstrumentationOptions { public string ConnectionString { get; set; } } diff --git a/src/Aguacongas.TheIdServer/Pages/_Host.cshtml b/src/Aguacongas.TheIdServer/Pages/_Host.cshtml index cc8d3236c..2c91e0aca 100644 --- a/src/Aguacongas.TheIdServer/Pages/_Host.cshtml +++ b/src/Aguacongas.TheIdServer/Pages/_Host.cshtml @@ -18,16 +18,16 @@ Copyright (c) 2023 @Olivier Lefebvre diff --git a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Components/Aguacongas.TheIdServer.BlazorApp.Components.csproj b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Components/Aguacongas.TheIdServer.BlazorApp.Components.csproj index c72485abf..9531da394 100644 --- a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Components/Aguacongas.TheIdServer.BlazorApp.Components.csproj +++ b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Components/Aguacongas.TheIdServer.BlazorApp.Components.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre Apache-2.0 @@ -16,8 +16,8 @@ - - + + @@ -26,4 +26,9 @@ + + + true + + diff --git a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Components/Form/AuthorizeNumber.razor b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Components/Form/AuthorizeNumber.razor new file mode 100644 index 000000000..a0a62a21a --- /dev/null +++ b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Components/Form/AuthorizeNumber.razor @@ -0,0 +1,13 @@ +@typeparam T +@inherits InputNumber + + + + + +
+ @Value +
+
+
diff --git a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Components/Form/AuthorizeNumber.razor.cs b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Components/Form/AuthorizeNumber.razor.cs new file mode 100644 index 000000000..0733d9149 --- /dev/null +++ b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Components/Form/AuthorizeNumber.razor.cs @@ -0,0 +1,20 @@ +// Project: Aguafrommars/TheIdServer +// Copyright (c) 2023 @Olivier Lefebvre +using Microsoft.AspNetCore.Components; + +namespace Aguacongas.TheIdServer.BlazorApp.Components.Form +{ + public partial class AuthorizeNumber + { + [Parameter] + public string Id { get; set; } + [Parameter] + public string Placeholder { get; set; } + + [Parameter] + public int? Max { get; set; } + + [Parameter] + public int? Min { get; set; } + } +} diff --git a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Infrastructure/Aguacongas.TheIdServer.BlazorApp.Infrastructure.csproj b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Infrastructure/Aguacongas.TheIdServer.BlazorApp.Infrastructure.csproj index ee7d4fec2..39c75b917 100644 --- a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Infrastructure/Aguacongas.TheIdServer.BlazorApp.Infrastructure.csproj +++ b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Infrastructure/Aguacongas.TheIdServer.BlazorApp.Infrastructure.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre Apache-2.0 @@ -16,14 +16,14 @@ - - + + - - - - + + + + diff --git a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Infrastructure/Models/ApiAuthentication.cs b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Infrastructure/Models/ApiAuthentication.cs index 83943a4b4..0cd69f41a 100644 --- a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Infrastructure/Models/ApiAuthentication.cs +++ b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Infrastructure/Models/ApiAuthentication.cs @@ -1,5 +1,4 @@ using IdentityModel.Client; -using Microsoft.IdentityModel.Protocols.OpenIdConnect; using System; namespace Aguacongas.TheIdServer.BlazorApp.Models diff --git a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Infrastructure/Models/OpenIdConnectConfiguration.cs b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Infrastructure/Models/OpenIdConnectConfiguration.cs new file mode 100644 index 000000000..b99bbc4f4 --- /dev/null +++ b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Infrastructure/Models/OpenIdConnectConfiguration.cs @@ -0,0 +1,231 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Aguacongas.TheIdServer.BlazorApp.Models; +public class OpenIdConnectConfiguration +{ + /// + /// Gets or sets the collection of 'acr_values_supported' + /// + public IEnumerable AcrValuesSupported { get; set; } + + /// + /// Gets or sets the 'authorization_endpoint'. + /// + public string AuthorizationEndpoint { get; set; } + + /// + /// Gets or sets the 'check_session_iframe'. + /// + public string CheckSessionIframe { get; set; } + + /// + /// Gets or sets the collection of 'claims_supported' + /// + public IEnumerable ClaimsSupported { get; set; } + + /// + /// Gets or sets the collection of 'claims_locales_supported' + /// + public IEnumerable ClaimsLocalesSupported { get; set; } + + /// + /// Gets or sets the 'claims_parameter_supported' + /// + public bool ClaimsParameterSupported { get; set; } + + /// + /// Gets or sets the collection of 'claim_types_supported' + /// + public IEnumerable ClaimTypesSupported { get; set; } + + /// + /// Gets or sets the collection of 'display_values_supported' + /// + public IEnumerable DisplayValuesSupported { get; set; } + + /// + /// Gets or sets the 'end_session_endpoint'. + /// + public string EndSessionEndpoint { get; set; } + + /// + /// Gets or sets the 'frontchannel_logout_session_supported'. + /// + public string FrontchannelLogoutSessionSupported { get; set; } + + /// + /// Gets or sets the 'frontchannel_logout_supported'. + /// + public string FrontchannelLogoutSupported { get; set; } + + /// + /// Gets or sets the collection of 'grant_types_supported' + /// + public IEnumerable GrantTypesSupported { get; set; } + + /// + /// Boolean value specifying whether the OP supports HTTP-based logout. Default is false. + /// + public bool HttpLogoutSupported { get; set; } + + /// + /// Gets or sets the collection of 'id_token_encryption_alg_values_supported'. + /// + public IEnumerable IdTokenEncryptionAlgValuesSupported { get; set; } + + /// + /// Gets or sets the collection of 'id_token_encryption_enc_values_supported'. + /// + public IEnumerable IdTokenEncryptionEncValuesSupported { get; set; } + + /// + /// Gets or sets the collection of 'id_token_signing_alg_values_supported'. + /// + public IEnumerable IdTokenSigningAlgValuesSupported { get; set; } + + /// + /// Gets or sets the 'introspection_endpoint'. + /// + public string IntrospectionEndpoint { get; set; } + + /// + /// Gets or sets the collection of 'introspection_endpoint_auth_methods_supported'. + /// + public IEnumerable IntrospectionEndpointAuthMethodsSupported { get; set; } + + /// + /// Gets or sets the collection of 'introspection_endpoint_auth_signing_alg_values_supported'. + /// + public IEnumerable IntrospectionEndpointAuthSigningAlgValuesSupported { get; set; } + + /// + /// Gets or sets the 'issuer'. + /// + public string Issuer { get; set; } + + /// + /// Gets or sets the 'jwks_uri' + /// + public string JwksUri { get; set; } + + /// + /// Boolean value specifying whether the OP can pass a sid (session ID) query parameter to identify the RP session at the OP when the logout_uri is used. Dafault Value is false. + /// + public bool LogoutSessionSupported { get; set; } + + /// + /// Gets or sets the 'op_policy_uri' + /// + public string OpPolicyUri { get; set; } + + /// + /// Gets or sets the 'op_tos_uri' + /// + public string OpTosUri { get; set; } + + /// + /// Gets or sets the 'registration_endpoint' + /// + public string RegistrationEndpoint { get; set; } + + /// + /// Gets or sets the collection of 'request_object_encryption_alg_values_supported'. + /// + public IEnumerable RequestObjectEncryptionAlgValuesSupported { get; set; } + + /// + /// Gets or sets the collection of 'request_object_encryption_enc_values_supported'. + /// + public IEnumerable RequestObjectEncryptionEncValuesSupported { get; set; } + + /// + /// Gets or sets the collection of 'request_object_signing_alg_values_supported'. + /// + public IEnumerable RequestObjectSigningAlgValuesSupported { get; set; } + + /// + /// Gets or sets the 'request_parameter_supported' + /// + public bool RequestParameterSupported { get; set; } + + /// + /// Gets or sets the 'request_uri_parameter_supported' + /// + public bool RequestUriParameterSupported { get; set; } + + /// + /// Gets or sets the 'require_request_uri_registration' + /// + public bool RequireRequestUriRegistration { get; set; } + + /// + /// Gets or sets the collection of 'response_modes_supported'. + /// + public IEnumerable ResponseModesSupported { get; set; } + + /// + /// Gets or sets the collection of 'response_types_supported'. + /// + public IEnumerable ResponseTypesSupported { get; set; } + + /// + /// Gets or sets the 'service_documentation' + /// + public string ServiceDocumentation { get; set; } + + /// + /// Gets or sets the collection of 'scopes_supported' + /// + public IEnumerable ScopesSupported { get; set; } + + /// + /// Gets or sets the collection of 'subject_types_supported'. + /// + public IEnumerable SubjectTypesSupported { get; set; } + + /// + /// Gets or sets the 'token_endpoint'. + /// + public string TokenEndpoint { get; set; } + + /// + /// Gets or sets the collection of 'token_endpoint_auth_methods_supported'. + /// + public IEnumerable TokenEndpointAuthMethodsSupported { get; set; } + + /// + /// Gets or sets the collection of 'token_endpoint_auth_signing_alg_values_supported'. + /// + public IEnumerable TokenEndpointAuthSigningAlgValuesSupported { get; set; } + + /// + /// Gets or sets the collection of 'ui_locales_supported' + /// + public IEnumerable UILocalesSupported { get; set; } + + /// + /// Gets or sets the 'user_info_endpoint'. + /// + public string UserInfoEndpoint { get; set; } + + /// + /// Gets or sets the collection of 'userinfo_encryption_alg_values_supported' + /// + public IEnumerable UserInfoEndpointEncryptionAlgValuesSupported { get; set; } + + /// + /// Gets or sets the collection of 'userinfo_encryption_enc_values_supported' + /// + public IEnumerable UserInfoEndpointEncryptionEncValuesSupported { get; set; } + + /// + /// Gets or sets the collection of 'userinfo_signing_alg_values_supported' + /// + public IEnumerable UserInfoEndpointSigningAlgValuesSupported { get; set; } + +} + diff --git a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Api/Aguacongas.TheIdServer.BlazorApp.Pages.Api.csproj b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Api/Aguacongas.TheIdServer.BlazorApp.Pages.Api.csproj index 4595f7a04..1158bc8ff 100644 --- a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Api/Aguacongas.TheIdServer.BlazorApp.Pages.Api.csproj +++ b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Api/Aguacongas.TheIdServer.BlazorApp.Pages.Api.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre Apache-2.0 @@ -20,7 +20,7 @@
- + diff --git a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScope/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScope.csproj b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScope/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScope.csproj index 92bd28806..e9491d91d 100644 --- a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScope/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScope.csproj +++ b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScope/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScope.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre Apache-2.0 @@ -21,7 +21,7 @@ - + diff --git a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScopes/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScopes.csproj b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScopes/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScopes.csproj index 468c01b63..eae37904b 100644 --- a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScopes/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScopes.csproj +++ b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScopes/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScopes.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre Apache-2.0 @@ -21,7 +21,7 @@ - + diff --git a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Apis/Aguacongas.TheIdServer.BlazorApp.Pages.Apis.csproj b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Apis/Aguacongas.TheIdServer.BlazorApp.Pages.Apis.csproj index 8daceb939..2df746783 100644 --- a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Apis/Aguacongas.TheIdServer.BlazorApp.Pages.Apis.csproj +++ b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Apis/Aguacongas.TheIdServer.BlazorApp.Pages.Apis.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre Apache-2.0 @@ -21,7 +21,7 @@ - + diff --git a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Client/Aguacongas.TheIdServer.BlazorApp.Pages.Client.csproj b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Client/Aguacongas.TheIdServer.BlazorApp.Pages.Client.csproj index d90ac734f..6bdf6707f 100644 --- a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Client/Aguacongas.TheIdServer.BlazorApp.Pages.Client.csproj +++ b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Client/Aguacongas.TheIdServer.BlazorApp.Pages.Client.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre Apache-2.0 @@ -21,7 +21,7 @@ - + diff --git a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Client/Components/ClientTokens.razor b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Client/Components/ClientTokens.razor index 8a64fd631..ea71d1798 100644 --- a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Client/Components/ClientTokens.razor +++ b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Client/Components/ClientTokens.razor @@ -233,6 +233,15 @@ } +
+ +
+ + +
+
} else { diff --git a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Client/Components/ClientTokens.razor.cs b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Client/Components/ClientTokens.razor.cs index c9b7f0d14..04e5ae5e0 100644 --- a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Client/Components/ClientTokens.razor.cs +++ b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Client/Components/ClientTokens.razor.cs @@ -5,14 +5,12 @@ using Microsoft.AspNetCore.Components; using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using Entity = Aguacongas.IdentityServer.Store.Entity; namespace Aguacongas.TheIdServer.BlazorApp.Pages.Client.Components { public partial class ClientTokens { - [SuppressMessage("Style", "IDE0044:Add readonly modifier", Justification = "Used in component")] private bool _showAllOptions; private readonly Dictionary _accessTokenQuickValues = new Dictionary diff --git a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Clients/Aguacongas.TheIdServer.BlazorApp.Pages.Clients.csproj b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Clients/Aguacongas.TheIdServer.BlazorApp.Pages.Clients.csproj index ff32d51f0..84359cf18 100644 --- a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Clients/Aguacongas.TheIdServer.BlazorApp.Pages.Clients.csproj +++ b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Clients/Aguacongas.TheIdServer.BlazorApp.Pages.Clients.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre Apache-2.0 @@ -21,7 +21,7 @@
- + diff --git a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Culture/Aguacongas.TheIdServer.BlazorApp.Pages.Culture.csproj b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Culture/Aguacongas.TheIdServer.BlazorApp.Pages.Culture.csproj index 49f9dc8d3..f8a7b1ff7 100644 --- a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Culture/Aguacongas.TheIdServer.BlazorApp.Pages.Culture.csproj +++ b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Culture/Aguacongas.TheIdServer.BlazorApp.Pages.Culture.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre Apache-2.0 @@ -21,7 +21,7 @@ - + diff --git a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Cultures/Aguacongas.TheIdServer.BlazorApp.Pages.Cultures.csproj b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Cultures/Aguacongas.TheIdServer.BlazorApp.Pages.Cultures.csproj index 74dac49d5..6715c44a2 100644 --- a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Cultures/Aguacongas.TheIdServer.BlazorApp.Pages.Cultures.csproj +++ b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Cultures/Aguacongas.TheIdServer.BlazorApp.Pages.Cultures.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre Apache-2.0 @@ -21,7 +21,7 @@ - + diff --git a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProvider/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProvider.csproj b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProvider/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProvider.csproj index c86e1af1e..6e2308d7d 100644 --- a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProvider/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProvider.csproj +++ b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProvider/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProvider.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre Apache-2.0 @@ -20,7 +20,7 @@ - + diff --git a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProviders/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProviders.csproj b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProviders/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProviders.csproj index 7614e5f54..2fad80beb 100644 --- a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProviders/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProviders.csproj +++ b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProviders/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProviders.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre Apache-2.0 @@ -20,7 +20,7 @@ - + diff --git a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Identities/Aguacongas.TheIdServer.BlazorApp.Pages.Identities.csproj b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Identities/Aguacongas.TheIdServer.BlazorApp.Pages.Identities.csproj index 91a42ea3e..70a5d346c 100644 --- a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Identities/Aguacongas.TheIdServer.BlazorApp.Pages.Identities.csproj +++ b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Identities/Aguacongas.TheIdServer.BlazorApp.Pages.Identities.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre Apache-2.0 @@ -20,7 +20,7 @@ - + diff --git a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Identity/Aguacongas.TheIdServer.BlazorApp.Pages.Identity.csproj b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Identity/Aguacongas.TheIdServer.BlazorApp.Pages.Identity.csproj index ea585762d..a8602844f 100644 --- a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Identity/Aguacongas.TheIdServer.BlazorApp.Pages.Identity.csproj +++ b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Identity/Aguacongas.TheIdServer.BlazorApp.Pages.Identity.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre Apache-2.0 @@ -20,7 +20,7 @@ - + diff --git a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Import/Aguacongas.TheIdServer.BlazorApp.Pages.Import.csproj b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Import/Aguacongas.TheIdServer.BlazorApp.Pages.Import.csproj index 656b13dee..2e641b8d3 100644 --- a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Import/Aguacongas.TheIdServer.BlazorApp.Pages.Import.csproj +++ b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Import/Aguacongas.TheIdServer.BlazorApp.Pages.Import.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre Apache-2.0 @@ -20,7 +20,7 @@ - + diff --git a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Keys/Aguacongas.TheIdServer.BlazorApp.Pages.Keys.csproj b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Keys/Aguacongas.TheIdServer.BlazorApp.Pages.Keys.csproj index f773bdfe5..1e4ab19a1 100644 --- a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Keys/Aguacongas.TheIdServer.BlazorApp.Pages.Keys.csproj +++ b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Keys/Aguacongas.TheIdServer.BlazorApp.Pages.Keys.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre Apache-2.0 @@ -20,7 +20,7 @@ - + diff --git a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParties/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParties.csproj b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParties/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParties.csproj index a866c3e60..db2596c60 100644 --- a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParties/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParties.csproj +++ b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParties/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParties.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre Apache-2.0 @@ -20,7 +20,7 @@ - + diff --git a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParty/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParty.csproj b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParty/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParty.csproj index 8ebae0f67..bb0e7b589 100644 --- a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParty/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParty.csproj +++ b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParty/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParty.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre Apache-2.0 @@ -20,7 +20,7 @@ - + diff --git a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParty/RelyingParty.razor b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParty/RelyingParty.razor index 369e3ff45..30811c7ed 100644 --- a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParty/RelyingParty.razor +++ b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParty/RelyingParty.razor @@ -114,7 +114,11 @@ else } else { - + + if (_invalidCertError != null) + { + @_invalidCertError + } }
diff --git a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParty/RelyingParty.razor.cs b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParty/RelyingParty.razor.cs index 41ee99e0b..d21333ab7 100644 --- a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParty/RelyingParty.razor.cs +++ b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParty/RelyingParty.razor.cs @@ -51,6 +51,7 @@ public partial class RelyingParty }; private IEnumerable _thumbprint; + private string _invalidCertError; protected override string Expand => $"{nameof(Entity.RelyingParty.ClaimMappings)}"; @@ -174,7 +175,7 @@ private async Task SetThrumprint(byte[] content) if (!response.IsSuccessStatusCode) { - _thumbprint = new[] { Localizer["Invalid file"].Value }; + _invalidCertError = Localizer["Invalid file"].Value; await InvokeAsync(StateHasChanged).ConfigureAwait(false); return; } @@ -186,7 +187,7 @@ private async Task SetThrumprint(byte[] content) } catch (CryptographicException) { - _thumbprint = new[] { Localizer["Invalid file"].Value }; + _invalidCertError = Localizer["Invalid file"].Value; await InvokeAsync(StateHasChanged).ConfigureAwait(false); } } diff --git a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Role/Aguacongas.TheIdServer.BlazorApp.Pages.Role.csproj b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Role/Aguacongas.TheIdServer.BlazorApp.Pages.Role.csproj index acda5a28b..7da66590d 100644 --- a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Role/Aguacongas.TheIdServer.BlazorApp.Pages.Role.csproj +++ b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Role/Aguacongas.TheIdServer.BlazorApp.Pages.Role.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre Apache-2.0 @@ -20,7 +20,7 @@ - + diff --git a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Roles/Aguacongas.TheIdServer.BlazorApp.Pages.Roles.csproj b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Roles/Aguacongas.TheIdServer.BlazorApp.Pages.Roles.csproj index a4f5f167e..2bb0333ce 100644 --- a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Roles/Aguacongas.TheIdServer.BlazorApp.Pages.Roles.csproj +++ b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Roles/Aguacongas.TheIdServer.BlazorApp.Pages.Roles.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre Apache-2.0 @@ -20,7 +20,7 @@ - + diff --git a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Settings/Aguacongas.TheIdServer.BlazorApp.Pages.Settings.csproj b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Settings/Aguacongas.TheIdServer.BlazorApp.Pages.Settings.csproj index 62e038603..8b95666c3 100644 --- a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Settings/Aguacongas.TheIdServer.BlazorApp.Pages.Settings.csproj +++ b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Settings/Aguacongas.TheIdServer.BlazorApp.Pages.Settings.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre Apache-2.0 @@ -14,7 +14,7 @@ package-icon.png enable enable - + true @@ -23,7 +23,7 @@ - + diff --git a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.User/Aguacongas.TheIdServer.BlazorApp.Pages.User.csproj b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.User/Aguacongas.TheIdServer.BlazorApp.Pages.User.csproj index a6c61cc83..43eb73ec1 100644 --- a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.User/Aguacongas.TheIdServer.BlazorApp.Pages.User.csproj +++ b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.User/Aguacongas.TheIdServer.BlazorApp.Pages.User.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre Apache-2.0 @@ -20,7 +20,7 @@ - + diff --git a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Users/Aguacongas.TheIdServer.BlazorApp.Pages.Users.csproj b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Users/Aguacongas.TheIdServer.BlazorApp.Pages.Users.csproj index b6a22b3ad..9a2d23674 100644 --- a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Users/Aguacongas.TheIdServer.BlazorApp.Pages.Users.csproj +++ b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Users/Aguacongas.TheIdServer.BlazorApp.Pages.Users.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre Apache-2.0 @@ -20,7 +20,7 @@ - + diff --git a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages/Aguacongas.TheIdServer.BlazorApp.Pages.csproj b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages/Aguacongas.TheIdServer.BlazorApp.Pages.csproj index 05d9b4570..ee61a81b4 100644 --- a/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages/Aguacongas.TheIdServer.BlazorApp.Pages.csproj +++ b/src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages/Aguacongas.TheIdServer.BlazorApp.Pages.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre Apache-2.0 @@ -16,7 +16,7 @@ - + diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.csproj b/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.csproj new file mode 100644 index 000000000..e9494bcc1 --- /dev/null +++ b/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.csproj @@ -0,0 +1,41 @@ + + + + net8.0 + enable + enable + Olivier Lefebvre + Argon2 Password Hasher for ASP.NET Core Identity. + Copyright (c) 2023 @Olivier Lefebvre + https://github.com/Aguafrommars/TheIdServer/tree/master/src/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher + https://github.com/aguacongas/TheIdServer + git + aspnetcore;identity;Argon2;password;hashing;hash;security + Apache-2.0 + true + package-icon.png + + + + + + + + + + + + + PreserveNewest + Never + + + PreserveNewest + Never + + + PreserveNewest + + + + \ No newline at end of file diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/Argon2Id.cs b/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/Argon2Id.cs new file mode 100644 index 000000000..af65249d1 --- /dev/null +++ b/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/Argon2Id.cs @@ -0,0 +1,31 @@ +using Geralt; +using Microsoft.Extensions.Options; + +namespace Aguacongas.TheIdServer.Identity.Argon2PasswordHasher; +internal class Argon2Id : IArgon2Id +{ + private readonly IOptions _options; + + public Argon2Id(IOptions options) + { + ArgumentNullException.ThrowIfNull(options); + _options = options; + } + public Span ComputeHash(ReadOnlySpan password) + { + var settings = _options.Value; + var hash = new Span(new byte[Argon2id.MaxHashSize]); + Argon2id.ComputeHash(hash, password, settings.Interations, settings.Memory); + return hash; + } + + public bool NeedsRehash(ReadOnlySpan hash) + { + var settings = _options.Value; + return Argon2id.NeedsRehash(hash, settings.Interations, settings.Memory); + } + + public bool VerifyHash(ReadOnlySpan hash, ReadOnlySpan password) + => Argon2id.VerifyHash(hash, password); + +} diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/Argon2PasswordHasher.cs b/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/Argon2PasswordHasher.cs new file mode 100644 index 000000000..e35e32bcb --- /dev/null +++ b/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/Argon2PasswordHasher.cs @@ -0,0 +1,67 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using System.Buffers; +using System.Text; + +namespace Aguacongas.TheIdServer.Identity.Argon2PasswordHasher; + +/// +/// Argon2 password hasher +/// +/// +public class Argon2PasswordHasher : IPasswordHasher where TUser : class +{ + private readonly IArgon2Id _argon2Id; + private readonly IOptions _options; + + /// + /// Initialize a new instance of + /// + /// implementation + /// password hasher options + public Argon2PasswordHasher(IArgon2Id argon2Id, IOptions options) + { + ArgumentNullException.ThrowIfNull(argon2Id); + ArgumentNullException.ThrowIfNull(options); + _argon2Id = argon2Id; + _options = options; + } + + /// + public string HashPassword(TUser user, string password) + { + ArgumentException.ThrowIfNullOrWhiteSpace(password); + + var passwordSpan = new ReadOnlySpan(Encoding.UTF8.GetBytes(password)); + var hash = _argon2Id.ComputeHash(passwordSpan); + return Convert.ToBase64String(new byte[] + { + _options.Value.HashPrefix + } + .Concat(hash.ToArray()) + .ToArray()); + } + + /// + public PasswordVerificationResult VerifyHashedPassword(TUser user, string hashedPassword, string providedPassword) + { + ArgumentException.ThrowIfNullOrWhiteSpace(hashedPassword); + ArgumentException.ThrowIfNullOrWhiteSpace(providedPassword); + + var decodedHashedPassword = Convert.FromBase64String(hashedPassword); + var hashSpan = decodedHashedPassword.AsSpan()[1..]; + + if (!_argon2Id.VerifyHash(hashSpan, + new ReadOnlySpan(Encoding.UTF8.GetBytes(providedPassword)))) + { + return PasswordVerificationResult.Failed; + } + + if (_argon2Id.NeedsRehash(hashSpan)) + { + return PasswordVerificationResult.SuccessRehashNeeded; + } + + return PasswordVerificationResult.Success; + } +} diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/Argon2PasswordHasherOptions.cs b/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/Argon2PasswordHasherOptions.cs new file mode 100644 index 000000000..119da630a --- /dev/null +++ b/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/Argon2PasswordHasherOptions.cs @@ -0,0 +1,25 @@ +using Geralt; +using System.ComponentModel.DataAnnotations; + +namespace Aguacongas.TheIdServer.Identity.Argon2PasswordHasher; + +/// +/// Argon2 password hasher options +/// +public class Argon2PasswordHasherOptions +{ + /// + /// Number of iteration to use. 2 by default. + /// + public int Interations { get; set; } = 2; + + /// + /// Memory to use. 67108864 by default. + /// + public int Memory { get; set; } = 67108864; + + /// + /// Hash prefix to inform it was generated by this hasher. 0xA0 by default. + /// + public byte HashPrefix { get; set; } = 0xA2; +} diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/AssemblyInfo.cs b/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/AssemblyInfo.cs new file mode 100644 index 000000000..1cf142cad --- /dev/null +++ b/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Test")] \ No newline at end of file diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/Extensions/IdentityBuilderExtensions.cs b/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/Extensions/IdentityBuilderExtensions.cs new file mode 100644 index 000000000..9d4abca13 --- /dev/null +++ b/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/Extensions/IdentityBuilderExtensions.cs @@ -0,0 +1,37 @@ +using Aguacongas.TheIdServer.Identity.Argon2PasswordHasher; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Configuration; + +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// extensions +/// +public static class IdentityBuilderExtensions +{ + /// + /// Add argon2 password hasher services in DI + /// + /// + /// + /// + /// + public static IdentityBuilder AddArgon2PasswordHasher(this IdentityBuilder builder, Action? configure = null) where TUser : class + { + builder.Services.AddArgon2PasswordHasher(configure); + return builder; + } + + /// + /// Add argon2 password hasher services in DI + /// + /// + /// + /// + /// + public static IdentityBuilder AddArgon2PasswordHasher(this IdentityBuilder builder, IConfiguration configuration) where TUser : class + { + builder.Services.AddArgon2PasswordHasher(configuration); + return builder; + } +} diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/Extensions/ServiceCollectionExtensions.cs b/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 000000000..59e20c5e9 --- /dev/null +++ b/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,39 @@ +using Aguacongas.TheIdServer.Identity.Argon2PasswordHasher; +using Geralt; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Configuration; + +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// extensions +/// +public static class ServiceCollectionExtensions +{ + /// + /// Add argon2 password hasher services in DI + /// + /// + /// + /// + /// + public static IServiceCollection AddArgon2PasswordHasher(this IServiceCollection services, Action? configure = null) where TUser : class + { + services.AddOptions() + .Configure(options => configure?.Invoke(options)) + .Validate(options => options.Memory > Argon2id.MinMemorySize && options.Interations > Argon2id.MinIterations); + + return services.AddTransient() + .AddScoped, Argon2PasswordHasher>(); + } + + /// + /// Add argon2 password hasher services in DI + /// + /// + /// + /// + /// + public static IServiceCollection AddArgon2PasswordHasher(this IServiceCollection services, IConfiguration configuration) where TUser : class + => services.AddArgon2PasswordHasher(configuration.Bind); +} diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/IArgon2Id.cs b/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/IArgon2Id.cs new file mode 100644 index 000000000..ee6b4c822 --- /dev/null +++ b/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/IArgon2Id.cs @@ -0,0 +1,28 @@ +namespace Aguacongas.TheIdServer.Identity.Argon2PasswordHasher; + +/// +/// Implement Argon2 +/// +public interface IArgon2Id +{ + /// + /// Compute the hash + /// + /// + Span ComputeHash(ReadOnlySpan password); + + /// + /// Verify the hash + /// + /// + /// + /// + bool VerifyHash(ReadOnlySpan hash, ReadOnlySpan password); + + /// + /// Verify if the hash needs to be rehashed + /// + /// + /// + bool NeedsRehash(ReadOnlySpan hash); +} diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/README.md b/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/README.md new file mode 100644 index 000000000..e0ef755b4 --- /dev/null +++ b/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/README.md @@ -0,0 +1,34 @@ +# Argon2 Password Hasher for ASP.NET Core Identity + +An implementation of IPasswordHasher using [Geralt](https://www.geralt.xyz/). + +## Installation + +```csharp +services.AddIdentity() + .AddArgon2PasswordHasher(); +``` + +### Options + +Default values: + +``` json +"Argon2PasswordHasherOptions": { + "Interations": 2, + "Memory": 67108864, + "HashPrefix": 0xA2 +} +``` + +- **Interations** can not be less than 1 +- **Memory** can not be less than 8192 + +Read [Geralt Password hashing Notes](https://www.geralt.xyz/password-hashing#notes) for more information to configure the hasher. + +`AddArgon2PasswordHasher` can take an action to configure an `Argon2PasswordHasherOptions` instance: + +```cs +services.AddArgon2PasswordHasher(options => configuration.Bind(options)); +``` + diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/THIRD-PARTY-NOTICES b/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/THIRD-PARTY-NOTICES new file mode 100644 index 000000000..397ae94a7 --- /dev/null +++ b/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/THIRD-PARTY-NOTICES @@ -0,0 +1,36 @@ +Aguacongas.TheIdServer.Identity.Argon2PasswordHasher uses third-party libraries or other resources that may be +distributed under licenses different than the TheIdServer software. + +In the event that we accidentally failed to list a required notice, please +bring it to our attention. Post an issue or email us: + + olivier.lefebve@live.com + +The attached notices are provided for information only. + + +License notice for samuel-lucas6/Geralt +--------------------------------------- + +MIT License + +Copyright (c) 2022 Samuel Lucas + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/package-icon.png b/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/package-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..623b0285f2a11366fcee8737da9817ec9d675bbb GIT binary patch literal 12354 zcmV-IFul)-P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>DFV{&#K~#8N?cE8O zWoKC@@b6SrcV}O^I}H&U^L%yhIrp4z zdH47ImMR++>T+6DmmS|(mz}Rqxh&tGp3m9EpEnH5EPif4m^1coO1WRkT}r8bxRfm? z`DG))24;Y|4$;4yFaI~?pYr`fDK|GiZ6K%?KQ|;qm22l0f7&pxVHuz<+e7&3-{k89^T`LeC_eF< zs&duU%>$co$A-lmz*|zTPdS+K<+_~lt998G$8h7ohID>HWGer=^Yy_gC#P&lxqE(j zR4Icq^L@j>2K9ay{5c`zcwah@>&pEN0~?e9>OsoK^X0;nL(Q#HZb$c)2lP z13Q5`PRj5fk*`0NvLofQx%(HT?7kpKsXen1V55hDvHjJqd=(1D``>-TMk4+uY{DjN zblBh~;EaTWGx7WfOu+|t_LBYLqbxOWpp7Oy`WsQ#M2-umKrhPo%rNJf`5v z;q@}09TEzb4^(CMf#%K~95!eec-rv^1|FH=KPi{@PGR@=zr*0`HoiI}_yTanow#Wt^2!rg;`xcAJ59E4~>p{NH-BijoxagQ?kYkzw zh6Fz)gx<0vgxZxmUz^)^O}SHwod5QegnG>qaKxA2H_5XtWv=kG?6*dEd z$Q59Qy(zo%(-mpL67w&F2QEm{mqZKQP?gJW_uAo@V1T-8kIJ6;x_rA&E+3w5BRYdl zvJo4R8Sm-Gb7Ee7!Y`KcYfmU;SNP^*pDg83k1OS}HE#<*WRg#7IAG%H+*&+vqJ*QW zi>q?A^z-c!ZgmEz8ykIG2zJdv8t+w^b=kEw!gfl!N1cCr%1@^J>lCYZNcNFIm6jj& z?@D?8A4PvSB_?nv^n2NBOZlDLyMEp175|}*MyTFDEcIu?t5?PiIaHNP#rVo9zGp_hbU$N;Bf zd?lCj@i(p$wu~{$dcjd=fZ?FdkaPdmTn5`!bnMp4t8)3$c|3JFB~rarPX2zm`k~x% zaFb1%y!G{k*>vIOA3c=*Q$`jA9g2B>-v>+i%|9sRvoYuW{=ks}-vJ|hBn`Ybypit* z<3R3j8jFvU4(^ZhYK~%{gO}zr_}Y6^&M!Ku3{aOdgkSdHe_u)lJotQg;OZ*sfB5@O z3=u-tKbXsiWBINWOSU>mecvJwfEkgb0 zln>i3!Dy|NeZ9=T-h~im;7CcQpE>%|YY%o&D6A^0X&ZWt$n`3GkUn|KpxK zD4+av9W8(vkCve57hChil)sGM^SW^0Rkz?syE{cVKa&EVe^tuKABg`T5?L2m*MUXi zgRFs2%v}2q=QE+v%uAzpFR98E6l&Vr!JhxCeEo&Ik%6&yM+^|gpAfSv@#5<1Qg^~L zDl~EX3CHg(e{}YJ%g_GQ50x`^V7AFH7}VucpZjEa+*4j%KJ}THr)K{kLbTzgK2y(a zPGLRpsQ)&ozBr$6kFZ>G3nOd~x6VE!-+nzM+*!Rmw=aw)Jz}%4ZVV80E+n(Kf<(uO z69U!IwY$G+Aayx8%&|2HtR9r#emTVf4G?&^z>Lt*!Y*<6Whocud!$Ji=H6TwVfLnS z)*}y=m%jAwdG_~H{zD3)tp*Z4k1rQr{HF4a_etN6OJ8La=V>&XO1?QID!SYw?TS{I zE{UcURLiP>;Et$yQ%V?NE=2xnBklPieb|}Mr5OX?D1ueJGiKw6j7$&Q3{Rbs9t zv|Fo0Kzf36&QJN+x%qr!1o0x= z@5fQ7%h%?og$(41{@?35{iG%v20R>~$^Ibag<*m*rz;Up+TWJ;Cu^ZFM9jpjaPC)f z=~NPoum%%Y2OrfdGS1%*6LigLn4qR1L{XRLdzhelU1%_MDs#;YFig`9Wayrh*LnI7 zl<;K`6?^Ta2$dK%QrD=b-%NQxid_9PKDt0~V(Ib4SmVdeGD3Iklw+*yR7B<_myJBVXwns^Yz?>z(ui;pb9 z@Y(IBWn4d=Oa2Irkn5>w5UpDOk39a|5V&JrcZ8v;SLMl(OF zgWE~I#Lm;x5T{S)owgkb8xlX?12d!!x0AYQi0?h#Y=~a5xxbgoPxKMLE_boH^UP9C zz3ESN(VoU0gs*fJzU}gV>F%zgrSys_`~tmVLE1J=6f^L5O>G-)aA}6*5z*A zTLRC9D0@n|={33J#BoTe?rB3gjQ)%H{F!JJ5!Yh-Gat^!%W{d}ac~Ld%&$ZUn`c&o zsTm-MPkVE`Tn?WEj(DC;%7CiR4m;z|T_K6LPyJ7m%4O$Gr7|aR6iC zkYaR~D7obs}iYg3};oARogkp&mO1;>qGzghW&N@_+KB?b9eem>VHnkz0%&8gbjsY=RHzhmtu^c zN_l*qjR0|2$03XZzrT?3rIbEgjg~zrPe`3h8a9lyBljYZ4TE@QA?1U4CYp$~=!aEJ zz;KXTe39Zq zv&>!8T^0YLa+aTW680>-Iqxh>%nTjVCJ`goz?3|&-0iNXm(x$%TFm{g2bf3r)^DoH zwrvtgzn1d)^y|^V4@YhZn6C8i%ju`^nDZ!O_}u&wm4D#D@ejt#Vtk#b@hXmV6H<%o zSBvMZdXjhHn=HZ!q5~O3{{;o0ZK8wfDX|JNmA$Yc0}MH*VnZLFBFEo?HF59GZyg6_ z)>*p`&6m~Y?&S#Bte3>rk1fAh1W1P$C)ioB;}C(`&>UejPZkE*q-Oj z1P){#9bFTjVnx8!mcvE59eMADFm&+Ad{y$j0k}*wHu%YC*zISz?za`tHTwcf}ropR}4(8dp?Ay;{DJ3bOdKCRLeUd z-h9kU9d$0moCxZYV_BNpsLFMox4ped#w|jgtIWQh737W4dm;lwQwuo|f}PP=3%D%d z*Fs1AH_Nfv>@jDq1DB_o#QF6{60$9uuvv8Y?vd``d?$Cch(Yj=M0h?J6}ddjD?6j# zYZ)*MIzFZRrI^CqQ5d9lJ>aIBO1UUX%i6(lh;69;OI=PKnxAEGocspW-ql_hR|uGh z=}u#sV>pje$Cs((304|FXjFgC;`c>3cxmogU(f~GCqBcjj?XZJUIrN2i-AcD8er63 zMKx8IrIU?#Wzd-8%-l0qK8c|+h&qFre^b<4Pt1D|0sm8q>|qxnupN_eB`q)lre@1S zQp_xN8FUDU|M4iCs^ld4FFtboJTAi!LN`s^hetyvzxmM6gJ5MXg$T-30@0PJ2O6U-k6SVsU;+cokx0@w>d_+v`*K}#)xd%}IE!PRZ3jM= z_TQf#^bDjT!5mHD(rAC8J_n|Od!W*0H!@icRR2Fj@ZJz6ICuoUz##Q}{=!n;__o#d z3`BD=g(vlx!CIOY^N-pO+U`fw$ODI$V_@ujh?TTJ{I0DdzC?qwK3iqo!hP#<_a!_} zV1NO8jv%9e0nAKwa`cw>H@3ew%m{G){w_a{4PuzdYj*drV}1U^p8Vcvs|z#wvNG&X zS6}|xQr;go;&4oX7XBrU-)qk;<@{*X`IUS!02Sk*XB<1kAT)iQ;5h%tVjg7~ROKq1 z%MbW}&5eG5w^2U=!}Ybr48%Hr_~GPKt0eK1<&i-Fs-pLppn4AhBcR(^RsW6wjUVSqmM5AsrTvUbNeI6jy?avokaD7*}Mf>=e;Qte1r}l>_10~KKoBgdHipb^866> zy&o7d!zFRHzY_B-Z2a&)l=AvFm-38fm-3`&jlWLxwyN510MQfcSMhcFV8Kcx4)v(2 zZh;!VS4)&G#0(fG=fh_o#JLg4^Q-y z&>;@(iIX&8v|X?C>PegmqCTvH-_?0$x8%#6IclQkSat7e1abGtpJD;^;?+`I=3p<^f-V1J$*Be zVjUoVXA(e09E}?$aNa=9V^mP|HXK}!_*acaHJ>sG)Dr=3mdo{W$!bUxe}7jxwWPYk zN_nS}w6N)M6pf^}C+EG#bJC+L2G;?c`sm>3XXxbkfYf z62IpDk*Z$p6o!iK9$3ArNS-hctJS*}fH+gpnA~!%c!~4x2=RM0*y1_?%h$|RtfWyv zq<>VEw@?u@j&fH68kdbRDYy7nb+f(Rg593Ekz2s?(Y$jF6Z9~^jX0}Mm3JX3ruvS$ zSsp%wNMV>}J`7Ns`RZx`p8kXsX|o_ws*{tm66hyYYMS`I8EEfBe^suHaj##SuTRM5 zZ{^b>i;Ge?P~ASQ1qg%bTqe$96N@hYD1<*hOgAPd#+m+^8=;X%|4ORA1&2s1TXB2~ zR8qGlak>m3@;NuH{c}~W?u=`OpxrlRr8Y5f@GX4rWq_O5p_MXEAa=ceWW1#jm3*C! z+1%Jo!0Y%!66%h5xI?Dl$=mR_DuL7^Wg0a<^RPVJXTDPB?n9|Jv0bym#_pM4NU>hv zl_`ny8k2*`F&|R_5lW=0$vQ$%)T&J=CEj7PspYI(hnV#X!Ve!!Xx5t;K>VO4CT|9# zJ~d^f>G4HqRuxBZjvEt#;V$dSf*1_Yj#vL|8+%t*`_$!5w9Er?-@N}y8Yg3yo8kO4 z9bOK@LF}~2gJZQ4nC5&$kWD2nK~$fl2LyP(l~hQ7c%HS~X&KbzE+OoJr{pV%odRJE zu~MH+5QEo63Rle148ySfVZMbhtMIkrl`~5*Ve$|hq9z&%@oRO(v!21o8DD!$-0aPL zdk(u0)0Dd9EGqjDsC|xGFOzlv(|@y zr^RN?{WSXs2#R>zL^wS&0a1-oqDP_=QClTrBHir%wY8C zR)xCk3bNE-nq?>TOJ;Qj}Wc8JXKrVVR;kc zQ~xm;^Ks%%=^^lLCg+7IOu>}UoEwBuUlN{)n%?=4AZ>fFG!62P2!;z-cAXr=e{*p4 zm^2yYuB~LUJ$~Bu<7d?&#=?6>i9ZBc(1NMd*)mJC-po6@#9st^G(F2%TnPy3h2euG z6FsT3u@QB5ap450Oc*^e1hDpfNt;A1N7Yo;4KgJ%b(laWBO%TLS`Nbj&2a|V+p#qR zD}WG5md%P~zCpCe&PRtZ_e#ikLbp%r0d=|OY?v(4e)B^^cz(s=W){-doI*M+Ff}%W z{6&U7wqRk%#Qt-cRo0F0SRC^F2vAJka#K{7ZGN|E+M0^hw5RYjKG(jbI(iWlFE(ns zah{%?%auY3AcTqIS3NIwW!dap2$9ZJ7Hvdr)zrI~HJYKeBG@FyG8n4U0W}J_yx+9~ z^TK179C6x)Q0a5Qrx5Wv4q~pSh9Lv(lo$)qE2&%)S7t9U9ckljuT|qj7^=Tcol)2G zi6olSNtXcz&W8=Hw&7Vw5-nG~Iv)R6z9G+Aw;>yC05asv}zh+XE; z*mO9u=ByWV?3uyEtKuP#_yc!N8w&}GnixGQyNUhdB>L~1%l89-FkhIU`l%F7q~+m< ztJ2lYtJfZ_Vy#>pttxi3F>jc4F%qbvu?B2zZ0R6GZ5%`d{>FUAh`!Tyd?L|^vQ`K` z$$ZY(>TbQ?mxHSjyWcGF)+T2zj--PxD(4zRiyqn+Un_MCVQ6-@*dF&kb1C*;4Nuw% zPH%j1Rc>4|AuBP!$@$qv1!XJ)*8`9oJ6MNPt%h9=A#7~gjE$N+eo|#+Y?$Ddld)P@ zbb!KmmEFbG{gJd)xO>e&8(^>D99mQJd`Hy)Qvs&PZPe_G#L8V3X_=3|Z5JiQ2AS=G(2n0Djvcu4KolqScUs zJ4u%i#sA(l=w%S5Vp^!In3rZ1akzW%&M1(c&7j)b5y`9UfMFI$;^9c%Z3!ferjaIp zeJ~48Se1aZMm#|ncTb0h&{(HCcbYl~@UEX)Na+ronHkpFc z?L3n0T5kzG8bGr+(k2Y<)UW#DJ|Bc*8Q+YVwRuBk^>6d}fe^np8yo4T_Gt+Y;?jJ~ z3IL~OOV0{DzSP|5z$y&j_w6CpOq`Hri8vdz8W^N3TI^i?mJE8rKz4?qDyw4Iuh=I$ zVh&zVm3_<99Z2XH#5iHhc?0#2=RJKfL9$~1ZzG`jR}XP-6_jQxLUI~4P2eKe=Fs$;&Y7&Fp%eK_}E55ZRJ+Net!Ar9NL$)lqN z#v9dMpP#Y3Ipy8?9-rvA>-)9Zl6j*6qn>Lab8Pcz2Qv1)=QF+?56ZKMFRGf>6!OJl zdYI({m-Q`In@WQ6RuI3F8HnRHmD*`R2jl@_>#Jo)p^id`n?WtaFAz`9CiIyP@~$20 za0_)gE`2)buKDIrX?_L7Fb_O+&fL*r3L45d*m83Ul2{2h9rR#B#x?QQp{yV3a#H$j zZGLltYC_if%8YX~vD3ifR4_)7XLw?6IY@gdngybXoJGu0yylaW`ZR52Iuq%i`|!8Y@D1^Q?`?aUc_`Eqskag3)9ST5^0h+tThJ{ z;`l9c@~w9tgih^0K$y@8v8i?HlP06t5M6w3ujA*#=ON<$iOk)FX~!3Vh@cJ3y2|Z~ zo`j)O(<>01H!GFi)@M>$SPKILaWj#!CNy)R_e%2wqk!PTM5l+I8#m!&VS+Jl_();6 z#cDp0#scJ-ND;qV-w1g&k(m6U!IUa}hd&cW%=9^lzD(Dz z3TtM77NQNJre>a>Dwu=V;KKrTlL!2&2w(hznW(Ol6EdxQ^y{$>b@`g${ZKT~f@$Q0 z*|lWXj|G>^)Zx$s9_3%mz`i|dKas_BaM%n0>ar`GI&iKZm327k3Lv%)e=c9&9)!eC zu#@P({;J%t?l$(ehT-ur1HOrv-i)7J(Q+JX)ub&jP3NV&GWFVizpC2r!Vxk+vl2z; zod7PqD@-}ufnWppC(pP+TqkHZc-ASA8IwAj}nVvFdBm&MQjU-FIl#nql1;0MuoB5M}#VC4(h%NcF8XXD}^50^h;XDo3kX zJ|*d7!>W6G^T@vRg}uZai?KZ>8HoEyPwKE8&g$UCCNC1%A<)fMIr9}(Mqy@&B~mS& zEA<@dZcZyX5?j6!=qq#hp|lsm&wO}2&EQdCfEMcJRO^|nOar+RKkVls(Hs8-vns+{8AG2`xbGwz-i|8n0{Ubvfin>7bV`}Oq$<<> zql{_cXb%Dv(#8%GA6ED{c8+-%-RW{`L(3$f{XU9|qmG*ipg$PY4l4^s6A>M{s4CZC z)^&$lZIDAoIGskYn&l^QX_2TE!bfY??pOnax3<#ip|`}J2?K12r+?MWL0p$%*A;FP zF0Y$|5Vr9VWWOXrtbI3g*Q(cz0?ZGS#JNkTYro&%(?P-Yaah-s&^cVVO_805AMM`U@yXqtYVnK zQD!2C=3lPyusLDJGBdE>rk4|$SU=Egnn?(20m&i%?c}J=G_C};ma5@ggIIha zCZXAeW(imrqR|YNfQiT3#T`d04qpv8XyS5H+%lGkOu{dYM!PUJ-62_V?q;I)tAka! z@~Am z&J3{+oD|w&jyRaj82~BGnS`==-J}jvvgmAD5!7S|5t{l|tk{iIS56+;n@02B0QTEVULQ-!JytRer zq{tI&!UPZxuLKZl%aubNNS7(fHeYQAaPQ9F+W@G3r*b;Sz#O{&)#sEATH#36C*I*< zH3pDS@!!I&WOWV=@^xK`SU9a*{dp7-4Y>XW^bz!|O2 zXW#k(GlO_wwrB{~(#)91Gc9<=^V;=HcZ}Zidwb76v+nB` zFdk;ebBggBn|Ip2wDl0UE-qS7w`W^?bnBDQ0zTE-w&5DSx?_SP3soH{XBz&5ZbQ6# zol=RH9qVQzs2sOr*sRTW4zFe3&hv8?u7B77ah7}8A*6Ap!JsXUv@VaM=v@aa8-OKF zwv}nev9daA3%UQ1XNr`wiRmz(x}{saZ%?A_PR~kl@`fk`K#sEl+*cRh#6MBKFvb5O zR0pl_(G&+UU|O1i6H9B@)B-88fbKm%h2sd}FGy+IHJM{E3xCMD!S>yoq_VKZxZa#% z43=wm0kIr$14kIgi6NGV!;c-twsP^M^wr#k>>U7WCz>lVk0;0Y#ML&!e;vJc*AFkmNj^auJHEbo2QCQ|P2lv`HZ*pG-nWoc4cWNhA0IdggsR7Vh>Me~OCue&F+r>b zNCPvVVw!*y;zXSGHz~k?F$JPnE{(VbhZA+oUViWS?j8oQFr+6FwHiVD1OB8INVD*Y*4~J|LR?%q+h2rfDlXu z&)WbIx|If|g9-5*)oXHv8r6b;VJd0&j{aigzfUn%{U*^0XB)0XHMFPa2n53Gllr_*>lm|pG_8AE7 zvb+2Xc2L_kmKY->>7RE0Jf$TP2K`(Lgh9MEAz2@d%~Z5|o-^Jq5Cr|>xETv+RA+0y zsbfq84;T<3+rH3(%oInC1Y0bs4os>an8LV>7d|mOpegi4-SDAL-r@h8o8mX`YQJFs zpn>+EFX0sSLLx)(RNrnkdF8|Wk9ZJ4h+D7 zK`8Y>vQBCjaY?elV7L|#*uZHa_45%(EM&G!;T`>L!L#}fFtgvaNmBJ6b88y`;0NO& zh;h)Ai0wVhXbdd_K(_IA0Uj~GdLf>=#xw*+-{Dew1cpiU8wNFg%mbJDKtPPac;M3b zFbyeZlH6@lJpbtj&30Iiygq8t-$`OGCQ`=M~?g9p>cu{8%7uR_o19;AF ztz)Qj?Q_%^LL!`N&l(t{xQ77{*8m{JyRQAFO?^@~wFp^$Z$Z7734FkkV>(2anio}hMnBr|!Y30V0#TRxX;7bjvw&g=bW-IA z0V%xI7TFqrIK(!1L~MU+5pJODCdq5Hf<}ZQ2JLx;M6Hwa_IHUb*``>C745NRZcTc}DvimJyK6oP}pdBYQ?!NKrlRogdXl;1ID6~+^0B`}{ zH1Atd&;iq8KFoSUmjMif8lrksqH{pS4k|)u4N!eJjb!=sJmm2o9+%=d_Yq8P0Fn$A zaS`63(c0B{_b`pT0ItTJ-Zvl~w)8Xepu_B@XCWE52Wc$>P_Ldf4#ZM7XN}tH0wT2c zFt)JDe;cFX~^xyNep8rmw@8|>c9kJU!=|61(XUv``!oi&S{=Srd>M{Ui zw)TwFwCW5O;u@SvLZ}_YHz)%p%@C!J5b4u%+T^ED zhl<>RevyV(r+AJErT(<7JxtjJ&yeuereiRS0dWKX17J*y?tS;w+4?Q+VE{m_VnTk4 z{&m~JjDGhl#Ct|P+IEjnVJbj4)C14@1J7{pdG+X@`rUJ_kLq*ZGsdJY?dW$G-~#^j z-}9ISZkBf+{mzL4`N(*HQFXj z0bPXQ3>hT3d*R?Tz`mPQAn}|-(u1?58FA-&1IdxSqaCzGeUnaVkeVHp^i4R*z{9mh zKsJj*L-nvvty8kzfB`s%w3HZ_Iwt+{|6S*66K*A7QD%qXEp4u*UoZ odq?w_u@oUpW3o138I0WsxmIsx+#1poj507*qoM6N<$g6kO+;Q#;t literal 0 HcmV?d00001 diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/vcruntime140.dll b/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/vcruntime140.dll new file mode 100644 index 0000000000000000000000000000000000000000..a9ed5c4ca4fc8757543c7e35da2843795257e01c GIT binary patch literal 119192 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 literal 0 HcmV?d00001 diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher.csproj b/src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher.csproj new file mode 100644 index 000000000..1e7380c72 --- /dev/null +++ b/src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher.csproj @@ -0,0 +1,39 @@ + + + + net8.0 + enable + enable + Olivier Lefebvre + bcrypt Password Hasher for ASP.NET Core Identity. + Copyright (c) 2023 @Olivier Lefebvre + https://github.com/Aguafrommars/TheIdServer/tree/master/src/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher + https://github.com/aguacongas/TheIdServer + git + aspnetcore;identity;bcrypt;password;hashing;hash;security + Apache-2.0 + true + package-icon.png + + + + + + + + + + + + + + + PreserveNewest + Never + + + PreserveNewest + Never + + + diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/BcryptPasswordHasher.cs b/src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/BcryptPasswordHasher.cs new file mode 100644 index 000000000..a0703dfff --- /dev/null +++ b/src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/BcryptPasswordHasher.cs @@ -0,0 +1,64 @@ +using BCrypt.Net; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using System.Text; +using BC = BCrypt.Net.BCrypt; + +namespace Aguacongas.TheIdServer.Identity.BcryptPasswordHasher; + +/// +/// bcrypt password hasher +/// +/// + +public class BcryptPasswordHasher : IPasswordHasher where TUser : class +{ + private readonly IOptions _options; + + /// + /// Initialize a new instance of + /// + /// + public BcryptPasswordHasher(IOptions options) + { + ArgumentNullException.ThrowIfNull(options); + _options = options; + } + + /// + public string HashPassword(TUser user, string password) + { + ArgumentException.ThrowIfNullOrWhiteSpace(password); + + var hash = BC.HashPassword(password, _options.Value.WorkFactor); + + return Convert.ToBase64String(new byte[] + { + _options.Value.HashPrefix + } + .Concat(Encoding.UTF8.GetBytes(hash)) + .ToArray()); + } + + /// + public PasswordVerificationResult VerifyHashedPassword(TUser user, string hashedPassword, string providedPassword) + { + ArgumentException.ThrowIfNullOrWhiteSpace(hashedPassword); + ArgumentException.ThrowIfNullOrWhiteSpace(providedPassword); + + var decodedHashedPassword = Convert.FromBase64String(hashedPassword); + var hash = Encoding.UTF8.GetString(decodedHashedPassword[1..]); + + if (!BC.Verify(providedPassword, hash)) + { + return PasswordVerificationResult.Failed; + } + + if (BC.PasswordNeedsRehash(hash, _options.Value.WorkFactor)) + { + return PasswordVerificationResult.SuccessRehashNeeded; + } + + return PasswordVerificationResult.Success; + } +} diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/BcryptPasswordHasherOptions.cs b/src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/BcryptPasswordHasherOptions.cs new file mode 100644 index 000000000..ea4e0409c --- /dev/null +++ b/src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/BcryptPasswordHasherOptions.cs @@ -0,0 +1,17 @@ +namespace Aguacongas.TheIdServer.Identity.BcryptPasswordHasher; + +/// +/// bcrypt password hasher options +/// +public class BcryptPasswordHasherOptions +{ + /// + /// Work factor. 11 by default. + /// + public int WorkFactor { get; set; } = 11; + + /// + /// Hash prefix: 0xBC by default. + /// + public byte HashPrefix { get; set; } = 0xBC; +} diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/Extensions/IdentityBuilderExtensions.cs b/src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/Extensions/IdentityBuilderExtensions.cs new file mode 100644 index 000000000..074941dc8 --- /dev/null +++ b/src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/Extensions/IdentityBuilderExtensions.cs @@ -0,0 +1,37 @@ +using Aguacongas.TheIdServer.Identity.BcryptPasswordHasher; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Configuration; + +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// extensions +/// +public static class IdentityBuilderExtensions +{ + /// + /// Add Bcrypt password hasher services in DI + /// + /// + /// + /// + /// + public static IdentityBuilder AddBcryptPasswordHasher(this IdentityBuilder builder, Action? configure = null) where TUser : class + { + builder.Services.AddBcryptPasswordHasher(configure); + return builder; + } + + /// + /// Add Bcrypt password hasher services in DI + /// + /// + /// + /// + /// + public static IdentityBuilder AddBcryptPasswordHasher(this IdentityBuilder builder, IConfiguration configuration) where TUser : class + { + builder.Services.AddBcryptPasswordHasher(configuration); + return builder; + } +} diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/Extensions/ServiceCollectionExtensions.cs b/src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 000000000..9b9e3e1e8 --- /dev/null +++ b/src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,37 @@ +using Aguacongas.TheIdServer.Identity.BcryptPasswordHasher; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Configuration; + +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// extensions +/// +public static class ServiceCollectionExtensions +{ + /// + /// Add bcrypt password hasher services in DI + /// + /// + /// + /// + /// + public static IServiceCollection AddBcryptPasswordHasher(this IServiceCollection services, Action? configure = null) where TUser : class + { + services.AddOptions() + .Configure(options => configure?.Invoke(options)) + .Validate(options => options.WorkFactor > 9); + + return services.AddScoped, BcryptPasswordHasher>(); + } + + /// + /// Add bcrypt password hasher services in DI + /// + /// + /// + /// + /// + public static IServiceCollection AddBcryptPasswordHasher(this IServiceCollection services, IConfiguration configuration) where TUser : class + => services.AddBcryptPasswordHasher(configuration.Bind); +} diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/README.md b/src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/README.md new file mode 100644 index 000000000..453343f0b --- /dev/null +++ b/src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/README.md @@ -0,0 +1,32 @@ +# bcrypt Password Hasher for ASP.NET Core Identity + +An implementation of IPasswordHasher using [BCrypt.Net-Next](https://github.com/BcryptNet/bcrypt.net). + +## Installation + +```csharp +services.AddIdentity() + .AddBcryptPasswordHasher(); +``` + +### Options + +Default values: + +``` json +"ScryptPasswordHasherOptions": { + "WorkFactor": 11, + "HashPrefix": 0xBC +} +``` + +- **WorkFactor** must be greater than 9 + +Read [Password Storage Cheat Sheet/bcrypt](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#bcrypt) for more information to configure the hasher. + +`AddBcryptPasswordHasher` can take an action to configure an `BcryptPasswordHasherOptions` instance: + +```cs +services.AddBcryptPasswordHasher(options => configuration.Bind(options)); +``` + diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/THIRD-PARTY-NOTICES b/src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/THIRD-PARTY-NOTICES new file mode 100644 index 000000000..5e2077467 --- /dev/null +++ b/src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/THIRD-PARTY-NOTICES @@ -0,0 +1,29 @@ +Aguacongas.TheIdServer.Identity.BcryptPasswordHasher uses third-party libraries or other resources that may be +distributed under licenses different than the TheIdServer software. + +In the event that we accidentally failed to list a required notice, please +bring it to our attention. Post an issue or email us: + + olivier.lefebve@live.com + +The attached notices are provided for information only. + +License notice for BcryptNet/bcrypt.net +--------------------------------------- + +The MIT License (MIT) +Copyright (c) 2006 Damien Miller djm@mindrot.org (jBCrypt) +Copyright (c) 2013 Ryan D. Emerle (.Net port) +Copyright (c) 2016/2021 Chris McKee (.Net-core port / patches) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/package-icon.png b/src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/package-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..623b0285f2a11366fcee8737da9817ec9d675bbb GIT binary patch literal 12354 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 literal 0 HcmV?d00001 diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasher/Aguacongas.TheIdServer.Identity.ScryptPasswordHasher.csproj b/src/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasher/Aguacongas.TheIdServer.Identity.ScryptPasswordHasher.csproj new file mode 100644 index 000000000..c194b7dad --- /dev/null +++ b/src/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasher/Aguacongas.TheIdServer.Identity.ScryptPasswordHasher.csproj @@ -0,0 +1,35 @@ + + + + net8.0 + enable + enable + Olivier Lefebvre + scrypt Password Hasher for ASP.NET Core Identity. + Copyright (c) 2023 @Olivier Lefebvre + https://github.com/Aguafrommars/TheIdServer/tree/master/src/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher + https://github.com/aguacongas/TheIdServer + git + aspnetcore;identity;scrypt;password;hashing;hash;security + Apache-2.0 + true + package-icon.png + + + + + + + + + + + + + + + Always + Never + + + diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasher/Extensions/IdentityBuilderExtensions.cs b/src/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasher/Extensions/IdentityBuilderExtensions.cs new file mode 100644 index 000000000..76f3363ef --- /dev/null +++ b/src/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasher/Extensions/IdentityBuilderExtensions.cs @@ -0,0 +1,37 @@ +using Aguacongas.TheIdServer.Identity.ScryptPasswordHasher; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Configuration; + +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// extensions +/// +public static class IdentityBuilderExtensions +{ + /// + /// Add Scrypt password hasher services in DI + /// + /// + /// + /// + /// + public static IdentityBuilder AddScryptPasswordHasher(this IdentityBuilder builder, Action? configure = null) where TUser : class + { + builder.Services.AddScryptPasswordHasher(configure); + return builder; + } + + /// + /// Add Scrypt password hasher services in DI + /// + /// + /// + /// + /// + public static IdentityBuilder AddScryptPasswordHasher(this IdentityBuilder builder, IConfiguration configuration) where TUser : class + { + builder.Services.AddScryptPasswordHasher(configuration); + return builder; + } +} diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasher/Extensions/ServiceCollectionExtensions.cs b/src/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasher/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 000000000..f77ff9b26 --- /dev/null +++ b/src/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasher/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,58 @@ +using Aguacongas.TheIdServer.Identity.ScryptPasswordHasher; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; +using Scrypt; + +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// extensions +/// +public static class ServiceCollectionExtensions +{ + /// + /// Add scrypt password hasher services in DI + /// + /// + /// + /// + /// + public static IServiceCollection AddScryptPasswordHasher(this IServiceCollection services, Action? configure = null) where TUser : class + { + services.AddOptions() + .Configure(options => configure?.Invoke(options)) + .Validate(options => + { + var N = options.IterationCount; + var r = options.BlockSize; + var p = options.ThreadCount; + + return N > 1 && + (N & (N - 1)) == 0 && + r > 0 && + p > 0 && + !((ulong)r * (ulong)p >= 1 << 30 || + r > int.MaxValue / 128 / p || + r > int.MaxValue / 256 || + N > int.MaxValue / 128 / r); + }); + + return services.AddTransient(p => + { + var options = p.GetRequiredService>().Value; + return new ScryptEncoder(options.IterationCount, options.BlockSize, options.ThreadCount); + }) + .AddScoped, ScryptPasswordHasher>(); + } + + /// + /// Add scrypt password hasher services in DI + /// + /// + /// + /// + /// + public static IServiceCollection AddScryptPasswordHasher(this IServiceCollection services, IConfiguration configuration) where TUser : class + => services.AddScryptPasswordHasher(configuration.Bind); +} diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasher/README.md b/src/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasher/README.md new file mode 100644 index 000000000..898911ba6 --- /dev/null +++ b/src/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasher/README.md @@ -0,0 +1,36 @@ +# scrypt Password Hasher for ASP.NET Core Identity + +An implementation of IPasswordHasher using [Scrypt.Net](https://github.com/viniciuschiele/scrypt/). + +## Installation + +```csharp +services.AddIdentity() + .AddScryptPasswordHasher(); +``` + +### Options + +Default values: + +``` json +"ScryptPasswordHasherOptions": { + "IterationCount": 131072, + "BlockSize": 8, + "ThreadCount": 1 + "HashPrefix": 0x0C +} +``` + +- **IterationCount** must be a power of two greater than 1 +- **BlockSize** must be greater than 0 +- **ThreadCount** must be greater than 0 + +Read [Password Storage Cheat Sheet/scrypt](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#scrypt) for more information to configure the hasher. + +`AddScryptPasswordHasher` can take an action to configure an `ScryptPasswordHasherOptions` instance: + +```cs +services.AddScryptPasswordHasher(options => configuration.Bind(options)); +``` + diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasher/ScryptPasswordHasher.cs b/src/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasher/ScryptPasswordHasher.cs new file mode 100644 index 000000000..7bbddbce9 --- /dev/null +++ b/src/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasher/ScryptPasswordHasher.cs @@ -0,0 +1,91 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using Scrypt; +using System.Text; + +namespace Aguacongas.TheIdServer.Identity.ScryptPasswordHasher; + +/// +/// scrypt password hasher +/// +/// + +public class ScryptPasswordHasher : IPasswordHasher where TUser : class +{ + private readonly ScryptEncoder _encoder; + private readonly IOptions _options; + + /// + /// Initialize a ne instance of + /// + /// + /// + public ScryptPasswordHasher(ScryptEncoder encoder, IOptions options) + { + _encoder = encoder; + _options = options; + } + + /// + public string HashPassword(TUser user, string password) + { + ArgumentException.ThrowIfNullOrWhiteSpace(password); + + var hash = _encoder.Encode(password); + return Convert.ToBase64String(new byte[] + { + _options.Value.HashPrefix + } + .Concat(Encoding.UTF8.GetBytes(hash)) + .ToArray()); + } + + /// + public PasswordVerificationResult VerifyHashedPassword(TUser user, string hashedPassword, string providedPassword) + { + ArgumentException.ThrowIfNullOrWhiteSpace(hashedPassword); + ArgumentException.ThrowIfNullOrWhiteSpace(providedPassword); + + var decodedHashedPassword = Convert.FromBase64String(hashedPassword); + var hash = Encoding.UTF8.GetString(decodedHashedPassword[1..]); + + var isValid = _encoder.Compare(providedPassword, hash); + + if (!isValid) + { + return PasswordVerificationResult.Failed; + } + + ScryptPasswordHasher.ExtractHeader(hash, out var _, out var iterationCount, out var blockSize, out var threadCount, out var _); + var settings = _options.Value; + if (settings.IterationCount != iterationCount || settings.BlockSize != blockSize || settings.ThreadCount != threadCount) + { + return PasswordVerificationResult.SuccessRehashNeeded; + } + return PasswordVerificationResult.Success; + } + + private static void ExtractHeader(string hashedPassword, out int version, out int iterationCount, out int blockSize, out int threadCount, out byte[] saltBytes) + { + var parts = hashedPassword.Split('$'); + + version = parts[1][1] - '0'; + + if (version >= 2) + { + iterationCount = Convert.ToInt32(parts[2]); + blockSize = Convert.ToInt32(parts[3]); + threadCount = Convert.ToInt32(parts[4]); + saltBytes = Convert.FromBase64String(parts[5]); + } + else + { + var config = Convert.ToInt64(parts[2], 16); + iterationCount = (int)config >> 16 & 0xffff; + blockSize = (int)config >> 8 & 0xff; + threadCount = (int)config & 0xff; + saltBytes = Convert.FromBase64String(parts[3]); + } + } + +} diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasher/ScryptPasswordHasherOptions.cs b/src/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasher/ScryptPasswordHasherOptions.cs new file mode 100644 index 000000000..e857e17ba --- /dev/null +++ b/src/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasher/ScryptPasswordHasherOptions.cs @@ -0,0 +1,28 @@ +namespace Aguacongas.TheIdServer.Identity.ScryptPasswordHasher; + +/// +/// scrypt password hasher options +/// +public class ScryptPasswordHasherOptions +{ + /// + /// Iteration count (N parameter in Kb). 131072 by default (128 MiB). + /// + public int IterationCount { get; set; } = 131072; + + /// + /// Block size (r parameter). 8 by default (1024 bytes). + /// + public int BlockSize { get; set; } = 8; + + /// + /// Thread count (p parameter). 1 by default. + /// + public int ThreadCount { get; set; } = 1; + + /// + /// Hash prefix to inform it was generated by this hasher. 0xA0 by default. + /// + public byte HashPrefix { get; set; } = 0x0C; + +} diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasher/package-icon.png b/src/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasher/package-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..623b0285f2a11366fcee8737da9817ec9d675bbb GIT binary patch literal 12354 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 literal 0 HcmV?d00001 diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher.csproj b/src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher.csproj new file mode 100644 index 000000000..50223151b --- /dev/null +++ b/src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher.csproj @@ -0,0 +1,34 @@ + + + + net8.0 + enable + enable + Olivier Lefebvre + Password Hasher to rehash password to a new algorithm for ASP.NET Core Identity. + Copyright (c) 2023 @Olivier Lefebvre + https://github.com/Aguafrommars/TheIdServer/tree/master/src/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher + https://github.com/aguacongas/TheIdServer + git + aspnetcore;identity;password;hashing;hash;security + Apache-2.0 + true + package-icon.png + + + + + + + + + + + + + + Always + Never + + + diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/Extensions/IdentityBuilderExtensions.cs b/src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/Extensions/IdentityBuilderExtensions.cs new file mode 100644 index 000000000..24d5ed18c --- /dev/null +++ b/src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/Extensions/IdentityBuilderExtensions.cs @@ -0,0 +1,37 @@ +using Aguacongas.TheIdServer.Identity.UpgradePasswordHasher; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Configuration; + +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// extensions +/// +public static class IdentityBuilderExtensions +{ + /// + /// Add Upgrade password hasher services in DI + /// + /// + /// + /// + /// + public static IdentityBuilder AddUpgradePasswordHasher(this IdentityBuilder builder, Action? configure = null) where TUser : class + { + builder.Services.AddUpgradePasswordHasher(configure); + return builder; + } + + /// + /// Add Upgrade password hasher services in DI + /// + /// + /// + /// + /// + public static IdentityBuilder AddUpgradePasswordHasher(this IdentityBuilder builder, IConfiguration configuration) where TUser : class + { + builder.Services.AddUpgradePasswordHasher(configuration); + return builder; + } +} diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/Extensions/ServiceCollectionExtensions.cs b/src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 000000000..9cd38c5c6 --- /dev/null +++ b/src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,46 @@ +using Aguacongas.TheIdServer.Identity.UpgradePasswordHasher; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Configuration; + +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// extensions. +/// +public static class ServiceCollectionExtensions +{ + /// + /// Add in DI + /// + /// + /// + /// + /// + public static IServiceCollection AddUpgradePasswordHasher(this IServiceCollection services, Action? configure = null) where TUser : class + { + services.AddOptions() + .Configure(options => configure?.Invoke(options)) + .Validate((options, provider) => + { + using var scope = provider.CreateScope(); + var hasherList= scope.ServiceProvider.GetServices>() + .Where(h => h.GetType() != typeof(UpgradePasswordHasher)); + return options.HashPrefixMaps is not null && + options.HashPrefixMaps.Values.All(m => hasherList.Any(h => h.GetType().FullName!.StartsWith($"{m}`1"))) && + hasherList.Any(h => h.GetType().FullName!.StartsWith($"{options.UsePasswordHasherTypeName}`1")); + }); + + return services.AddScoped, UpgradePasswordHasher>(); + } + + /// + /// Add in DI + /// + /// + /// + /// + /// + public static IServiceCollection AddUpgradePasswordHasher(this IServiceCollection services, IConfiguration configuration) where TUser : class + => services.AddUpgradePasswordHasher(configuration.Bind); + +} diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/README.md b/src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/README.md new file mode 100644 index 000000000..5efa2ff93 --- /dev/null +++ b/src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/README.md @@ -0,0 +1,48 @@ +# Password Hasher to rehash password to a new algorithm for ASP.NET Core Identity. + +An implementation of `IPasswordHasher` and fallback to the configured default `IPasswordHasher`. + +## Installation + +```csharp +services.AddScoped, My.MyPasswordHasher>(); +services.AddIdentity() + .AddUpgradePasswordHasher(options => + { + options.HashPrefixMaps = new Dictionary + { + [0x00] = "Microsoft.AspNetCore.Identity.PasswordHasher", + [0x01] = "Microsoft.AspNetCore.Identity.PasswordHasher", + [0x03] = "My.MyPasswordHasher" + } + options.UsePasswordHasherTypeName = "My.MyPasswordHasher"; + options.DeadLine = new DateTime(2024, 1, 1); // after this date each `SuccessRehashNeeded` result will be considered as `Failed`. It forces the user to update its password. + }); // it must be added at last position, after adding all needed IPasswordHasher. +``` + +### Options + +- **HashPrefixMaps** defines de map between prefix and password hasher implementation. +- **UsePasswordHasherTypeName** the password hasher implementation to use. +- **DeadLine** (optional) after this date hash using old algorithm or old configuration will be considered invalid to prevent password shucking. It forces the user to update its password. + +```json +{ + "HashPrefixMaps": { + "0": "Microsoft.AspNetCore.Identity.PasswordHasher", + "1": "Microsoft.AspNetCore.Identity.PasswordHasher", + "162": "Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Argon2PasswordHasher", + "12": "Aguacongas.TheIdServer.Identity.ScryptPasswordHasher.ScryptPasswordHasher", + "188": "Aguacongas.TheIdServer.Identity.BcryptPasswordHasher.BcryptPasswordHasher" + }, + "UsePasswordHasherTypeName": "Microsoft.AspNetCore.Identity.PasswordHasher", + "DeadLineUtc": "2024-01-01" +``` + +### How it works + +The hashed password must be a base 64 string containing a hash prefixed by a byte defining the hash format. +`UpgradePasswordHasher` gets the hasher defined by this hash prefix and call it to verify the password. +If the hasher is not the same as the hasher to use, defined by the option `UsePasswordHasherTypeName`, `Success` results are transformed to `SuccessRehashNeeded`. +If a dead line is defined and the dead line expired, `SuccessRehashNeeded` results are transformed to `Failed`. +`UpgradePasswordHasher` uses the hasher defined by the option `UsePasswordHasherTypeName` to hash the password. diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/UpgradePasswordHasher.cs b/src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/UpgradePasswordHasher.cs new file mode 100644 index 000000000..2e9e93524 --- /dev/null +++ b/src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/UpgradePasswordHasher.cs @@ -0,0 +1,61 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace Aguacongas.TheIdServer.Identity.UpgradePasswordHasher; + +/// +/// Upgrade password hasher +/// +/// +public class UpgradePasswordHasher : IPasswordHasher where TUser : class +{ + private readonly IServiceProvider _serviceProvider; + private readonly IOptions _options; + + /// + /// Initialize a new instance of + /// + /// + /// + public UpgradePasswordHasher(IServiceProvider serviceProvider, IOptions options) + { + ArgumentNullException.ThrowIfNull(serviceProvider); + ArgumentNullException.ThrowIfNull(options); + + _serviceProvider = serviceProvider; + _options = options; + } + + /// + public string HashPassword(TUser user, string password) + => GetHasher(_options.Value.UsePasswordHasherTypeName) + .HashPassword(user, password); + + /// + public PasswordVerificationResult VerifyHashedPassword(TUser user, string hashedPassword, string providedPassword) + { + ArgumentException.ThrowIfNullOrWhiteSpace(hashedPassword); + + var settings = _options.Value; + var hash = Convert.FromBase64String(hashedPassword); + var hashPrefix = hash[0]; + var hasherTypeName = settings.HashPrefixMaps![hashPrefix]; + var hasher = GetHasher(hasherTypeName); + + var result = hasher.VerifyHashedPassword(user, hashedPassword, providedPassword); + if (result == PasswordVerificationResult.Failed) + { + return result; + } + result = hasherTypeName == settings.UsePasswordHasherTypeName ? result : PasswordVerificationResult.SuccessRehashNeeded; + var deadLineExpired = settings.DeadLineUtc.HasValue && settings.DeadLineUtc.Value < DateTime.UtcNow; + + return deadLineExpired && result == PasswordVerificationResult.SuccessRehashNeeded ? + PasswordVerificationResult.Failed : + result; + } + + private IPasswordHasher GetHasher(string? hasherTypeName) + => _serviceProvider.GetServices>().First(h => h.GetType().FullName!.StartsWith($"{hasherTypeName}`1")); +} diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/UpgradePasswordHasherOptions.cs b/src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/UpgradePasswordHasherOptions.cs new file mode 100644 index 000000000..b286fe08b --- /dev/null +++ b/src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/UpgradePasswordHasherOptions.cs @@ -0,0 +1,22 @@ +namespace Aguacongas.TheIdServer.Identity.UpgradePasswordHasher; + +/// +/// Upgrade password hasher options +/// +public class UpgradePasswordHasherOptions +{ + /// + /// Hash prefixes map + /// + public IDictionary? HashPrefixMaps { get; set; } + + /// + /// Password hasher to use. + /// + public string? UsePasswordHasherTypeName { get; set; } + + /// + /// (optional) after this date hash using old algorithm will be considered invalid to prevent password shucking. It forces the user to update its password. + /// + public DateTime? DeadLineUtc { get; set; } +} diff --git a/src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/package-icon.png b/src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/package-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..623b0285f2a11366fcee8737da9817ec9d675bbb GIT binary patch literal 12354 zcmV-IFul)-P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>DFV{&#K~#8N?cE8O zWoKC@@b6SrcV}O^I}H&U^L%yhIrp4z zdH47ImMR++>T+6DmmS|(mz}Rqxh&tGp3m9EpEnH5EPif4m^1coO1WRkT}r8bxRfm? z`DG))24;Y|4$;4yFaI~?pYr`fDK|GiZ6K%?KQ|;qm22l0f7&pxVHuz<+e7&3-{k89^T`LeC_eF< zs&duU%>$co$A-lmz*|zTPdS+K<+_~lt998G$8h7ohID>HWGer=^Yy_gC#P&lxqE(j zR4Icq^L@j>2K9ay{5c`zcwah@>&pEN0~?e9>OsoK^X0;nL(Q#HZb$c)2lP z13Q5`PRj5fk*`0NvLofQx%(HT?7kpKsXen1V55hDvHjJqd=(1D``>-TMk4+uY{DjN zblBh~;EaTWGx7WfOu+|t_LBYLqbxOWpp7Oy`WsQ#M2-umKrhPo%rNJf`5v z;q@}09TEzb4^(CMf#%K~95!eec-rv^1|FH=KPi{@PGR@=zr*0`HoiI}_yTanow#Wt^2!rg;`xcAJ59E4~>p{NH-BijoxagQ?kYkzw zh6Fz)gx<0vgxZxmUz^)^O}SHwod5QegnG>qaKxA2H_5XtWv=kG?6*dEd z$Q59Qy(zo%(-mpL67w&F2QEm{mqZKQP?gJW_uAo@V1T-8kIJ6;x_rA&E+3w5BRYdl zvJo4R8Sm-Gb7Ee7!Y`KcYfmU;SNP^*pDg83k1OS}HE#<*WRg#7IAG%H+*&+vqJ*QW zi>q?A^z-c!ZgmEz8ykIG2zJdv8t+w^b=kEw!gfl!N1cCr%1@^J>lCYZNcNFIm6jj& z?@D?8A4PvSB_?nv^n2NBOZlDLyMEp175|}*MyTFDEcIu?t5?PiIaHNP#rVo9zGp_hbU$N;Bf zd?lCj@i(p$wu~{$dcjd=fZ?FdkaPdmTn5`!bnMp4t8)3$c|3JFB~rarPX2zm`k~x% zaFb1%y!G{k*>vIOA3c=*Q$`jA9g2B>-v>+i%|9sRvoYuW{=ks}-vJ|hBn`Ybypit* z<3R3j8jFvU4(^ZhYK~%{gO}zr_}Y6^&M!Ku3{aOdgkSdHe_u)lJotQg;OZ*sfB5@O z3=u-tKbXsiWBINWOSU>mecvJwfEkgb0 zln>i3!Dy|NeZ9=T-h~im;7CcQpE>%|YY%o&D6A^0X&ZWt$n`3GkUn|KpxK zD4+av9W8(vkCve57hChil)sGM^SW^0Rkz?syE{cVKa&EVe^tuKABg`T5?L2m*MUXi zgRFs2%v}2q=QE+v%uAzpFR98E6l&Vr!JhxCeEo&Ik%6&yM+^|gpAfSv@#5<1Qg^~L zDl~EX3CHg(e{}YJ%g_GQ50x`^V7AFH7}VucpZjEa+*4j%KJ}THr)K{kLbTzgK2y(a zPGLRpsQ)&ozBr$6kFZ>G3nOd~x6VE!-+nzM+*!Rmw=aw)Jz}%4ZVV80E+n(Kf<(uO z69U!IwY$G+Aayx8%&|2HtR9r#emTVf4G?&^z>Lt*!Y*<6Whocud!$Ji=H6TwVfLnS z)*}y=m%jAwdG_~H{zD3)tp*Z4k1rQr{HF4a_etN6OJ8La=V>&XO1?QID!SYw?TS{I zE{UcURLiP>;Et$yQ%V?NE=2xnBklPieb|}Mr5OX?D1ueJGiKw6j7$&Q3{Rbs9t zv|Fo0Kzf36&QJN+x%qr!1o0x= z@5fQ7%h%?og$(41{@?35{iG%v20R>~$^Ibag<*m*rz;Up+TWJ;Cu^ZFM9jpjaPC)f z=~NPoum%%Y2OrfdGS1%*6LigLn4qR1L{XRLdzhelU1%_MDs#;YFig`9Wayrh*LnI7 zl<;K`6?^Ta2$dK%QrD=b-%NQxid_9PKDt0~V(Ib4SmVdeGD3Iklw+*yR7B<_myJBVXwns^Yz?>z(ui;pb9 z@Y(IBWn4d=Oa2Irkn5>w5UpDOk39a|5V&JrcZ8v;SLMl(OF zgWE~I#Lm;x5T{S)owgkb8xlX?12d!!x0AYQi0?h#Y=~a5xxbgoPxKMLE_boH^UP9C zz3ESN(VoU0gs*fJzU}gV>F%zgrSys_`~tmVLE1J=6f^L5O>G-)aA}6*5z*A zTLRC9D0@n|={33J#BoTe?rB3gjQ)%H{F!JJ5!Yh-Gat^!%W{d}ac~Ld%&$ZUn`c&o zsTm-MPkVE`Tn?WEj(DC;%7CiR4m;z|T_K6LPyJ7m%4O$Gr7|aR6iC zkYaR~D7obs}iYg3};oARogkp&mO1;>qGzghW&N@_+KB?b9eem>VHnkz0%&8gbjsY=RHzhmtu^c zN_l*qjR0|2$03XZzrT?3rIbEgjg~zrPe`3h8a9lyBljYZ4TE@QA?1U4CYp$~=!aEJ zz;KXTe39Zq zv&>!8T^0YLa+aTW680>-Iqxh>%nTjVCJ`goz?3|&-0iNXm(x$%TFm{g2bf3r)^DoH zwrvtgzn1d)^y|^V4@YhZn6C8i%ju`^nDZ!O_}u&wm4D#D@ejt#Vtk#b@hXmV6H<%o zSBvMZdXjhHn=HZ!q5~O3{{;o0ZK8wfDX|JNmA$Yc0}MH*VnZLFBFEo?HF59GZyg6_ z)>*p`&6m~Y?&S#Bte3>rk1fAh1W1P$C)ioB;}C(`&>UejPZkE*q-Oj z1P){#9bFTjVnx8!mcvE59eMADFm&+Ad{y$j0k}*wHu%YC*zISz?za`tHTwcf}ropR}4(8dp?Ay;{DJ3bOdKCRLeUd z-h9kU9d$0moCxZYV_BNpsLFMox4ped#w|jgtIWQh737W4dm;lwQwuo|f}PP=3%D%d z*Fs1AH_Nfv>@jDq1DB_o#QF6{60$9uuvv8Y?vd``d?$Cch(Yj=M0h?J6}ddjD?6j# zYZ)*MIzFZRrI^CqQ5d9lJ>aIBO1UUX%i6(lh;69;OI=PKnxAEGocspW-ql_hR|uGh z=}u#sV>pje$Cs((304|FXjFgC;`c>3cxmogU(f~GCqBcjj?XZJUIrN2i-AcD8er63 zMKx8IrIU?#Wzd-8%-l0qK8c|+h&qFre^b<4Pt1D|0sm8q>|qxnupN_eB`q)lre@1S zQp_xN8FUDU|M4iCs^ld4FFtboJTAi!LN`s^hetyvzxmM6gJ5MXg$T-30@0PJ2O6U-k6SVsU;+cokx0@w>d_+v`*K}#)xd%}IE!PRZ3jM= z_TQf#^bDjT!5mHD(rAC8J_n|Od!W*0H!@icRR2Fj@ZJz6ICuoUz##Q}{=!n;__o#d z3`BD=g(vlx!CIOY^N-pO+U`fw$ODI$V_@ujh?TTJ{I0DdzC?qwK3iqo!hP#<_a!_} zV1NO8jv%9e0nAKwa`cw>H@3ew%m{G){w_a{4PuzdYj*drV}1U^p8Vcvs|z#wvNG&X zS6}|xQr;go;&4oX7XBrU-)qk;<@{*X`IUS!02Sk*XB<1kAT)iQ;5h%tVjg7~ROKq1 z%MbW}&5eG5w^2U=!}Ybr48%Hr_~GPKt0eK1<&i-Fs-pLppn4AhBcR(^RsW6wjUVSqmM5AsrTvUbNeI6jy?avokaD7*}Mf>=e;Qte1r}l>_10~KKoBgdHipb^866> zy&o7d!zFRHzY_B-Z2a&)l=AvFm-38fm-3`&jlWLxwyN510MQfcSMhcFV8Kcx4)v(2 zZh;!VS4)&G#0(fG=fh_o#JLg4^Q-y z&>;@(iIX&8v|X?C>PegmqCTvH-_?0$x8%#6IclQkSat7e1abGtpJD;^;?+`I=3p<^f-V1J$*Be zVjUoVXA(e09E}?$aNa=9V^mP|HXK}!_*acaHJ>sG)Dr=3mdo{W$!bUxe}7jxwWPYk zN_nS}w6N)M6pf^}C+EG#bJC+L2G;?c`sm>3XXxbkfYf z62IpDk*Z$p6o!iK9$3ArNS-hctJS*}fH+gpnA~!%c!~4x2=RM0*y1_?%h$|RtfWyv zq<>VEw@?u@j&fH68kdbRDYy7nb+f(Rg593Ekz2s?(Y$jF6Z9~^jX0}Mm3JX3ruvS$ zSsp%wNMV>}J`7Ns`RZx`p8kXsX|o_ws*{tm66hyYYMS`I8EEfBe^suHaj##SuTRM5 zZ{^b>i;Ge?P~ASQ1qg%bTqe$96N@hYD1<*hOgAPd#+m+^8=;X%|4ORA1&2s1TXB2~ zR8qGlak>m3@;NuH{c}~W?u=`OpxrlRr8Y5f@GX4rWq_O5p_MXEAa=ceWW1#jm3*C! z+1%Jo!0Y%!66%h5xI?Dl$=mR_DuL7^Wg0a<^RPVJXTDPB?n9|Jv0bym#_pM4NU>hv zl_`ny8k2*`F&|R_5lW=0$vQ$%)T&J=CEj7PspYI(hnV#X!Ve!!Xx5t;K>VO4CT|9# zJ~d^f>G4HqRuxBZjvEt#;V$dSf*1_Yj#vL|8+%t*`_$!5w9Er?-@N}y8Yg3yo8kO4 z9bOK@LF}~2gJZQ4nC5&$kWD2nK~$fl2LyP(l~hQ7c%HS~X&KbzE+OoJr{pV%odRJE zu~MH+5QEo63Rle148ySfVZMbhtMIkrl`~5*Ve$|hq9z&%@oRO(v!21o8DD!$-0aPL zdk(u0)0Dd9EGqjDsC|xGFOzlv(|@y zr^RN?{WSXs2#R>zL^wS&0a1-oqDP_=QClTrBHir%wY8C zR)xCk3bNE-nq?>TOJ;Qj}Wc8JXKrVVR;kc zQ~xm;^Ks%%=^^lLCg+7IOu>}UoEwBuUlN{)n%?=4AZ>fFG!62P2!;z-cAXr=e{*p4 zm^2yYuB~LUJ$~Bu<7d?&#=?6>i9ZBc(1NMd*)mJC-po6@#9st^G(F2%TnPy3h2euG z6FsT3u@QB5ap450Oc*^e1hDpfNt;A1N7Yo;4KgJ%b(laWBO%TLS`Nbj&2a|V+p#qR zD}WG5md%P~zCpCe&PRtZ_e#ikLbp%r0d=|OY?v(4e)B^^cz(s=W){-doI*M+Ff}%W z{6&U7wqRk%#Qt-cRo0F0SRC^F2vAJka#K{7ZGN|E+M0^hw5RYjKG(jbI(iWlFE(ns zah{%?%auY3AcTqIS3NIwW!dap2$9ZJ7Hvdr)zrI~HJYKeBG@FyG8n4U0W}J_yx+9~ z^TK179C6x)Q0a5Qrx5Wv4q~pSh9Lv(lo$)qE2&%)S7t9U9ckljuT|qj7^=Tcol)2G zi6olSNtXcz&W8=Hw&7Vw5-nG~Iv)R6z9G+Aw;>yC05asv}zh+XE; z*mO9u=ByWV?3uyEtKuP#_yc!N8w&}GnixGQyNUhdB>L~1%l89-FkhIU`l%F7q~+m< ztJ2lYtJfZ_Vy#>pttxi3F>jc4F%qbvu?B2zZ0R6GZ5%`d{>FUAh`!Tyd?L|^vQ`K` z$$ZY(>TbQ?mxHSjyWcGF)+T2zj--PxD(4zRiyqn+Un_MCVQ6-@*dF&kb1C*;4Nuw% zPH%j1Rc>4|AuBP!$@$qv1!XJ)*8`9oJ6MNPt%h9=A#7~gjE$N+eo|#+Y?$Ddld)P@ zbb!KmmEFbG{gJd)xO>e&8(^>D99mQJd`Hy)Qvs&PZPe_G#L8V3X_=3|Z5JiQ2AS=G(2n0Djvcu4KolqScUs zJ4u%i#sA(l=w%S5Vp^!In3rZ1akzW%&M1(c&7j)b5y`9UfMFI$;^9c%Z3!ferjaIp zeJ~48Se1aZMm#|ncTb0h&{(HCcbYl~@UEX)Na+ronHkpFc z?L3n0T5kzG8bGr+(k2Y<)UW#DJ|Bc*8Q+YVwRuBk^>6d}fe^np8yo4T_Gt+Y;?jJ~ z3IL~OOV0{DzSP|5z$y&j_w6CpOq`Hri8vdz8W^N3TI^i?mJE8rKz4?qDyw4Iuh=I$ zVh&zVm3_<99Z2XH#5iHhc?0#2=RJKfL9$~1ZzG`jR}XP-6_jQxLUI~4P2eKe=Fs$;&Y7&Fp%eK_}E55ZRJ+Net!Ar9NL$)lqN z#v9dMpP#Y3Ipy8?9-rvA>-)9Zl6j*6qn>Lab8Pcz2Qv1)=QF+?56ZKMFRGf>6!OJl zdYI({m-Q`In@WQ6RuI3F8HnRHmD*`R2jl@_>#Jo)p^id`n?WtaFAz`9CiIyP@~$20 za0_)gE`2)buKDIrX?_L7Fb_O+&fL*r3L45d*m83Ul2{2h9rR#B#x?QQp{yV3a#H$j zZGLltYC_if%8YX~vD3ifR4_)7XLw?6IY@gdngybXoJGu0yylaW`ZR52Iuq%i`|!8Y@D1^Q?`?aUc_`Eqskag3)9ST5^0h+tThJ{ z;`l9c@~w9tgih^0K$y@8v8i?HlP06t5M6w3ujA*#=ON<$iOk)FX~!3Vh@cJ3y2|Z~ zo`j)O(<>01H!GFi)@M>$SPKILaWj#!CNy)R_e%2wqk!PTM5l+I8#m!&VS+Jl_();6 z#cDp0#scJ-ND;qV-w1g&k(m6U!IUa}hd&cW%=9^lzD(Dz z3TtM77NQNJre>a>Dwu=V;KKrTlL!2&2w(hznW(Ol6EdxQ^y{$>b@`g${ZKT~f@$Q0 z*|lWXj|G>^)Zx$s9_3%mz`i|dKas_BaM%n0>ar`GI&iKZm327k3Lv%)e=c9&9)!eC zu#@P({;J%t?l$(ehT-ur1HOrv-i)7J(Q+JX)ub&jP3NV&GWFVizpC2r!Vxk+vl2z; zod7PqD@-}ufnWppC(pP+TqkHZc-ASA8IwAj}nVvFdBm&MQjU-FIl#nql1;0MuoB5M}#VC4(h%NcF8XXD}^50^h;XDo3kX zJ|*d7!>W6G^T@vRg}uZai?KZ>8HoEyPwKE8&g$UCCNC1%A<)fMIr9}(Mqy@&B~mS& zEA<@dZcZyX5?j6!=qq#hp|lsm&wO}2&EQdCfEMcJRO^|nOar+RKkVls(Hs8-vns+{8AG2`xbGwz-i|8n0{Ubvfin>7bV`}Oq$<<> zql{_cXb%Dv(#8%GA6ED{c8+-%-RW{`L(3$f{XU9|qmG*ipg$PY4l4^s6A>M{s4CZC z)^&$lZIDAoIGskYn&l^QX_2TE!bfY??pOnax3<#ip|`}J2?K12r+?MWL0p$%*A;FP zF0Y$|5Vr9VWWOXrtbI3g*Q(cz0?ZGS#JNkTYro&%(?P-Yaah-s&^cVVO_805AMM`U@yXqtYVnK zQD!2C=3lPyusLDJGBdE>rk4|$SU=Egnn?(20m&i%?c}J=G_C};ma5@ggIIha zCZXAeW(imrqR|YNfQiT3#T`d04qpv8XyS5H+%lGkOu{dYM!PUJ-62_V?q;I)tAka! z@~Am z&J3{+oD|w&jyRaj82~BGnS`==-J}jvvgmAD5!7S|5t{l|tk{iIS56+;n@02B0QTEVULQ-!JytRer zq{tI&!UPZxuLKZl%aubNNS7(fHeYQAaPQ9F+W@G3r*b;Sz#O{&)#sEATH#36C*I*< zH3pDS@!!I&WOWV=@^xK`SU9a*{dp7-4Y>XW^bz!|O2 zXW#k(GlO_wwrB{~(#)91Gc9<=^V;=HcZ}Zidwb76v+nB` zFdk;ebBggBn|Ip2wDl0UE-qS7w`W^?bnBDQ0zTE-w&5DSx?_SP3soH{XBz&5ZbQ6# zol=RH9qVQzs2sOr*sRTW4zFe3&hv8?u7B77ah7}8A*6Ap!JsXUv@VaM=v@aa8-OKF zwv}nev9daA3%UQ1XNr`wiRmz(x}{saZ%?A_PR~kl@`fk`K#sEl+*cRh#6MBKFvb5O zR0pl_(G&+UU|O1i6H9B@)B-88fbKm%h2sd}FGy+IHJM{E3xCMD!S>yoq_VKZxZa#% z43=wm0kIr$14kIgi6NGV!;c-twsP^M^wr#k>>U7WCz>lVk0;0Y#ML&!e;vJc*AFkmNj^auJHEbo2QCQ|P2lv`HZ*pG-nWoc4cWNhA0IdggsR7Vh>Me~OCue&F+r>b zNCPvVVw!*y;zXSGHz~k?F$JPnE{(VbhZA+oUViWS?j8oQFr+6FwHiVD1OB8INVD*Y*4~J|LR?%q+h2rfDlXu z&)WbIx|If|g9-5*)oXHv8r6b;VJd0&j{aigzfUn%{U*^0XB)0XHMFPa2n53Gllr_*>lm|pG_8AE7 zvb+2Xc2L_kmKY->>7RE0Jf$TP2K`(Lgh9MEAz2@d%~Z5|o-^Jq5Cr|>xETv+RA+0y zsbfq84;T<3+rH3(%oInC1Y0bs4os>an8LV>7d|mOpegi4-SDAL-r@h8o8mX`YQJFs zpn>+EFX0sSLLx)(RNrnkdF8|Wk9ZJ4h+D7 zK`8Y>vQBCjaY?elV7L|#*uZHa_45%(EM&G!;T`>L!L#}fFtgvaNmBJ6b88y`;0NO& zh;h)Ai0wVhXbdd_K(_IA0Uj~GdLf>=#xw*+-{Dew1cpiU8wNFg%mbJDKtPPac;M3b zFbyeZlH6@lJpbtj&30Iiygq8t-$`OGCQ`=M~?g9p>cu{8%7uR_o19;AF ztz)Qj?Q_%^LL!`N&l(t{xQ77{*8m{JyRQAFO?^@~wFp^$Z$Z7734FkkV>(2anio}hMnBr|!Y30V0#TRxX;7bjvw&g=bW-IA z0V%xI7TFqrIK(!1L~MU+5pJODCdq5Hf<}ZQ2JLx;M6Hwa_IHUb*``>C745NRZcTc}DvimJyK6oP}pdBYQ?!NKrlRogdXl;1ID6~+^0B`}{ zH1Atd&;iq8KFoSUmjMif8lrksqH{pS4k|)u4N!eJjb!=sJmm2o9+%=d_Yq8P0Fn$A zaS`63(c0B{_b`pT0ItTJ-Zvl~w)8Xepu_B@XCWE52Wc$>P_Ldf4#ZM7XN}tH0wT2c zFt)JDe;cFX~^xyNep8rmw@8|>c9kJU!=|61(XUv``!oi&S{=Srd>M{Ui zw)TwFwCW5O;u@SvLZ}_YHz)%p%@C!J5b4u%+T^ED zhl<>RevyV(r+AJErT(<7JxtjJ&yeuereiRS0dWKX17J*y?tS;w+4?Q+VE{m_VnTk4 z{&m~JjDGhl#Ct|P+IEjnVJbj4)C14@1J7{pdG+X@`rUJ_kLq*ZGsdJY?dW$G-~#^j z-}9ISZkBf+{mzL4`N(*HQFXj z0bPXQ3>hT3d*R?Tz`mPQAn}|-(u1?58FA-&1IdxSqaCzGeUnaVkeVHp^i4R*z{9mh zKsJj*L-nvvty8kzfB`s%w3HZ_Iwt+{|6S*66K*A7QD%qXEp4u*UoZ odq?w_u@oUpW3o138I0WsxmIsx+#1poj507*qoM6N<$g6kO+;Q#;t literal 0 HcmV?d00001 diff --git a/src/IdentityServer/Aguacongas.IdentityServer.Admin.Http.Store/AdminStore.cs b/src/IdentityServer/Aguacongas.IdentityServer.Admin.Http.Store/AdminStore.cs index c83e897ae..c8a5f9300 100644 --- a/src/IdentityServer/Aguacongas.IdentityServer.Admin.Http.Store/AdminStore.cs +++ b/src/IdentityServer/Aguacongas.IdentityServer.Admin.Http.Store/AdminStore.cs @@ -26,16 +26,12 @@ public virtual async Task CreateAsync(T entity, CancellationToken cancellatio entity = entity ?? throw new ArgumentNullException(nameof(entity)); var httpClient = await HttpClientFactory .ConfigureAwait(false); - using (var content = new StringContent(SerializeEntity(entity), Encoding.UTF8, "application/json")) - { - using (var response = await httpClient.PostAsync(GetUri(httpClient, BaseUri), content, cancellationToken) - .ConfigureAwait(false)) - { - await EnsureSuccess(response).ConfigureAwait(false); - return await DeserializeResponse(response) - .ConfigureAwait(false); - } - } + using var content = new StringContent(SerializeEntity(entity), Encoding.UTF8, "application/json"); + using var response = await httpClient.PostAsync(GetUri(httpClient, BaseUri), content, cancellationToken) + .ConfigureAwait(false); + await EnsureSuccess(response).ConfigureAwait(false); + return await DeserializeResponse(response) + .ConfigureAwait(false); } public virtual async Task CreateAsync(object entity, CancellationToken cancellationToken = default) diff --git a/src/IdentityServer/Aguacongas.IdentityServer.Admin.Http.Store/Aguacongas.IdentityServer.Admin.Http.Store.csproj b/src/IdentityServer/Aguacongas.IdentityServer.Admin.Http.Store/Aguacongas.IdentityServer.Admin.Http.Store.csproj index 578607a05..5163f8e23 100644 --- a/src/IdentityServer/Aguacongas.IdentityServer.Admin.Http.Store/Aguacongas.IdentityServer.Admin.Http.Store.csproj +++ b/src/IdentityServer/Aguacongas.IdentityServer.Admin.Http.Store/Aguacongas.IdentityServer.Admin.Http.Store.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre TheIdServer administration HTTP stores. Copyright (c) 2023 @Olivier Lefebvre @@ -23,11 +23,11 @@ - - - - - + + + + + diff --git a/src/IdentityServer/Aguacongas.IdentityServer.Admin.Http.Store/HttpStoreBase.cs b/src/IdentityServer/Aguacongas.IdentityServer.Admin.Http.Store/HttpStoreBase.cs index c69cf2523..79c256250 100644 --- a/src/IdentityServer/Aguacongas.IdentityServer.Admin.Http.Store/HttpStoreBase.cs +++ b/src/IdentityServer/Aguacongas.IdentityServer.Admin.Http.Store/HttpStoreBase.cs @@ -40,15 +40,13 @@ public virtual async Task GetAsync(string id, GetRequest request, Cancellatio var expandParameter = request != null ? $"?expand={request.Expand}" : string.Empty; - using (var response = await httpClient.GetAsync(GetUri(httpClient, $"{BaseUri}/{id}{expandParameter}"), cancellationToken) - .ConfigureAwait(false)) - { - await EnsureSuccess(response) - .ConfigureAwait(false); + using var response = await httpClient.GetAsync(GetUri(httpClient, $"{BaseUri}/{id}{expandParameter}"), cancellationToken) + .ConfigureAwait(false); + await EnsureSuccess(response) + .ConfigureAwait(false); - return await DeserializeResponse(response) - .ConfigureAwait(false); - } + return await DeserializeResponse(response) + .ConfigureAwait(false); } public virtual async Task> GetAsync(PageRequest request, CancellationToken cancellationToken = default) @@ -63,16 +61,14 @@ public virtual async Task> GetAsync(PageRequest request, Cancell var httpClient = await HttpClientFactory .ConfigureAwait(false); - using (var response = await httpClient.GetAsync(GetUri(httpClient, QueryHelpers.AddQueryString(BaseUri, dictionary)), cancellationToken) - .ConfigureAwait(false)) - { + using var response = await httpClient.GetAsync(GetUri(httpClient, QueryHelpers.AddQueryString(BaseUri, dictionary)), cancellationToken) + .ConfigureAwait(false); - await EnsureSuccess(response) - .ConfigureAwait(false); + await EnsureSuccess(response) + .ConfigureAwait(false); - return await DeserializeResponse>(response) - .ConfigureAwait(false); - } + return await DeserializeResponse>(response) + .ConfigureAwait(false); } protected async Task EnsureSuccess(HttpResponseMessage response) @@ -119,9 +115,9 @@ protected async Task DeserializeResponse(HttpResponseMessa protected Uri GetUri(HttpClient httpClient, string uri) { var baseAddress = httpClient.BaseAddress.ToString(); - if (baseAddress.EndsWith("/", StringComparison.Ordinal)) + if (baseAddress.EndsWith('/')) { - baseAddress = baseAddress.Substring(0, baseAddress.Length - 1); + baseAddress = baseAddress[..^1]; } var result = new Uri($"{baseAddress}{uri}"); diff --git a/src/IdentityServer/Aguacongas.IdentityServer.Admin.Http.Store/ProblemException.cs b/src/IdentityServer/Aguacongas.IdentityServer.Admin.Http.Store/ProblemException.cs index 47773a181..c2ad0a184 100644 --- a/src/IdentityServer/Aguacongas.IdentityServer.Admin.Http.Store/ProblemException.cs +++ b/src/IdentityServer/Aguacongas.IdentityServer.Admin.Http.Store/ProblemException.cs @@ -1,7 +1,7 @@ // Project: Aguafrommars/TheIdServer // Copyright (c) 2023 @Olivier Lefebvre using System; -using System.Runtime.Serialization; +using System.Diagnostics.CodeAnalysis; namespace Aguacongas.IdentityServer.Admin.Http.Store { @@ -10,7 +10,8 @@ namespace Aguacongas.IdentityServer.Admin.Http.Store /// /// [Serializable] - public class ProblemException: Exception + [SuppressMessage("Major Code Smell", "S3925:\"ISerializable\" should be implemented correctly", Justification = "Obsolete")] + public class ProblemException : Exception { public ProblemDetails Details { get; set; } @@ -25,10 +26,5 @@ public ProblemException(string message, Exception innerException) : base(message public ProblemException() { } - - protected ProblemException(SerializationInfo serializationInfo, StreamingContext streamingContext) - : base(serializationInfo, streamingContext) - { - } } } diff --git a/src/IdentityServer/Aguacongas.IdentityServer.Admin/Aguacongas.IdentityServer.Admin.csproj b/src/IdentityServer/Aguacongas.IdentityServer.Admin/Aguacongas.IdentityServer.Admin.csproj index cb86aaf51..07eac0fe1 100644 --- a/src/IdentityServer/Aguacongas.IdentityServer.Admin/Aguacongas.IdentityServer.Admin.csproj +++ b/src/IdentityServer/Aguacongas.IdentityServer.Admin/Aguacongas.IdentityServer.Admin.csproj @@ -1,58 +1,57 @@ - - net7.0 - Olivier Lefebvre - Expose OData controllers to manage TheIdServer. - Copyright (c) 2023 @Olivier Lefebvre - https://github.com/Aguafrommars/TheIdServer/tree/master/src/IdentityServer/Aguacongas.IdentityServer.Admin - https://github.com/aguacongas/TheIdServer - git - theidserver;administration - Apache-2.0 - true - package-icon.png - - + + net8.0 + Olivier Lefebvre + Expose OData controllers to manage TheIdServer. + Copyright (c) 2023 @Olivier Lefebvre + https://github.com/Aguafrommars/TheIdServer/tree/master/src/IdentityServer/Aguacongas.IdentityServer.Admin + https://github.com/aguacongas/TheIdServer + git + theidserver;administration + Apache-2.0 + true + package-icon.png + - - Aguacongas.IdentityServer.Admin.ruleset - + + Aguacongas.IdentityServer.Admin.ruleset + - - Aguacongas.IdentityServer.Admin.ruleset - 1701;1702;AD0001 - + + Aguacongas.IdentityServer.Admin.ruleset + 1701;1702;AD0001 + - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/src/IdentityServer/Aguacongas.IdentityServer.Admin/CertificateController.cs b/src/IdentityServer/Aguacongas.IdentityServer.Admin/CertificateController.cs index 1d45d9a1a..58c798251 100644 --- a/src/IdentityServer/Aguacongas.IdentityServer.Admin/CertificateController.cs +++ b/src/IdentityServer/Aguacongas.IdentityServer.Admin/CertificateController.cs @@ -1,6 +1,7 @@ // Project: Aguafrommars/TheIdServer // Copyright (c) 2023 @Olivier Lefebvre using Aguacongas.IdentityServer.Abstractions; +using Aguacongas.IdentityServer.Admin.Configuration; using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; @@ -12,7 +13,7 @@ namespace Aguacongas.IdentityServer.Admin /// certificate controller /// /// - [Route("[controller]")] + [ApiRoute("[controller]")] public class CertificateController : Controller { private readonly ICertificateVerifierService _service; diff --git a/src/IdentityServer/Aguacongas.IdentityServer.Admin/ClaimsProviderController.cs b/src/IdentityServer/Aguacongas.IdentityServer.Admin/ClaimsProviderController.cs index 2de6d645a..f2bfe4eba 100644 --- a/src/IdentityServer/Aguacongas.IdentityServer.Admin/ClaimsProviderController.cs +++ b/src/IdentityServer/Aguacongas.IdentityServer.Admin/ClaimsProviderController.cs @@ -1,6 +1,7 @@ // Project: Aguafrommars/TheIdServer // Copyright (c) 2023 @Olivier Lefebvre using Aguacongas.IdentityServer.Abstractions; +using Aguacongas.IdentityServer.Admin.Configuration; using Aguacongas.IdentityServer.Store; using Aguacongas.IdentityServer.Store.Entity; using Microsoft.AspNetCore.Authorization; @@ -15,7 +16,7 @@ namespace Aguacongas.IdentityServer.Admin /// /// [Produces("application/json")] - [Route("[controller]")] + [ApiRoute("[controller]")] public class ClaimsProviderController { diff --git a/src/IdentityServer/Aguacongas.IdentityServer.Admin/Configuration/ApiPath.cs b/src/IdentityServer/Aguacongas.IdentityServer.Admin/Configuration/ApiPath.cs new file mode 100644 index 000000000..c01c741fc --- /dev/null +++ b/src/IdentityServer/Aguacongas.IdentityServer.Admin/Configuration/ApiPath.cs @@ -0,0 +1,12 @@ +namespace Aguacongas.IdentityServer.Admin.Configuration; + +/// +/// Api base path +/// +public static class ApiBasePath +{ + /// + /// The api base path value + /// + public static string Value { get; set; } = "api/"; +} diff --git a/src/IdentityServer/Aguacongas.IdentityServer.Admin/Configuration/ApiRouteAttribute.cs b/src/IdentityServer/Aguacongas.IdentityServer.Admin/Configuration/ApiRouteAttribute.cs new file mode 100644 index 000000000..42a5fa796 --- /dev/null +++ b/src/IdentityServer/Aguacongas.IdentityServer.Admin/Configuration/ApiRouteAttribute.cs @@ -0,0 +1,49 @@ +using Microsoft.AspNetCore.Mvc.Routing; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Aguacongas.IdentityServer.Admin.Configuration; + +/// +/// Api route attribute +/// +[AttributeUsage(AttributeTargets.Class)] +public class ApiRouteAttribute : Attribute, IRouteTemplateProvider +{ + private int? _order; + /// + /// Creates a new with the given route template. + /// + /// The route template. May not be null. + public ApiRouteAttribute([StringSyntax("Route")] string template) + { + var routeBasePath = $"{ApiBasePath.Value[1..]}/"; + Template = $"{routeBasePath}{template ?? throw new ArgumentNullException(nameof(template))}"; + } + + /// + [StringSyntax("Route")] + public string Template { get; } + + /// + /// Gets the route order. The order determines the order of route execution. Routes with a lower order + /// value are tried first. If an action defines a route by providing an + /// with a non null order, that order is used instead of this value. If neither the action nor the + /// controller defines an order, a default value of 0 is used. + /// + public int Order + { + get { return _order ?? 0; } + set { _order = value; } + } + + /// + int? IRouteTemplateProvider.Order => _order; + + /// + public string Name { get; set; } +} diff --git a/src/IdentityServer/Aguacongas.IdentityServer.Admin/EmailController.cs b/src/IdentityServer/Aguacongas.IdentityServer.Admin/EmailController.cs index 79a3e6a01..9f3345218 100644 --- a/src/IdentityServer/Aguacongas.IdentityServer.Admin/EmailController.cs +++ b/src/IdentityServer/Aguacongas.IdentityServer.Admin/EmailController.cs @@ -8,6 +8,7 @@ using System; using System.ComponentModel; using System.Threading.Tasks; +using Aguacongas.IdentityServer.Admin.Configuration; namespace Aguacongas.IdentityServer.Admin { @@ -16,7 +17,7 @@ namespace Aguacongas.IdentityServer.Admin /// /// [Produces("application/json")] - [Route("[controller]")] + [ApiRoute("[controller]")] public class EmailController : Controller { private readonly SendGridEmailSender _sender; diff --git a/src/IdentityServer/Aguacongas.IdentityServer.Admin/ExternalProviderKindController.cs b/src/IdentityServer/Aguacongas.IdentityServer.Admin/ExternalProviderKindController.cs index ec6763f83..6d5473b9b 100644 --- a/src/IdentityServer/Aguacongas.IdentityServer.Admin/ExternalProviderKindController.cs +++ b/src/IdentityServer/Aguacongas.IdentityServer.Admin/ExternalProviderKindController.cs @@ -1,5 +1,6 @@ // Project: Aguafrommars/TheIdServer // Copyright (c) 2023 @Olivier Lefebvre +using Aguacongas.IdentityServer.Admin.Configuration; using Aguacongas.IdentityServer.Store; using Aguacongas.IdentityServer.Store.Entity; using Microsoft.AspNetCore.Authorization; @@ -15,7 +16,7 @@ namespace Aguacongas.IdentityServer.Admin /// /// [Produces("application/json")] - [Route("[controller]")] + [ApiRoute("[controller]")] public class ExternalProviderKindController : Controller { private readonly IExternalProviderKindStore _store; diff --git a/src/IdentityServer/Aguacongas.IdentityServer.Admin/GenericApiController.cs b/src/IdentityServer/Aguacongas.IdentityServer.Admin/GenericApiController.cs index 61d32893a..e61d20667 100644 --- a/src/IdentityServer/Aguacongas.IdentityServer.Admin/GenericApiController.cs +++ b/src/IdentityServer/Aguacongas.IdentityServer.Admin/GenericApiController.cs @@ -1,5 +1,6 @@ // Project: Aguafrommars/TheIdServer // Copyright (c) 2023 @Olivier Lefebvre +using Aguacongas.IdentityServer.Admin.Configuration; using Aguacongas.IdentityServer.Admin.Services; using Aguacongas.IdentityServer.Store; using Microsoft.AspNetCore.Authorization; @@ -17,7 +18,7 @@ namespace Aguacongas.IdentityServer.Admin /// Type of entity /// [Produces(JsonFileOutputFormatter.SupportedContentType, "application/json")] - [Route("[controller]")] + [ApiRoute("[controller]")] [GenericControllerNameConvention] public class GenericApiController : Controller where T : class { diff --git a/src/IdentityServer/Aguacongas.IdentityServer.Admin/GenericKeyController.cs b/src/IdentityServer/Aguacongas.IdentityServer.Admin/GenericKeyController.cs index 647ff60d8..d2d94db12 100644 --- a/src/IdentityServer/Aguacongas.IdentityServer.Admin/GenericKeyController.cs +++ b/src/IdentityServer/Aguacongas.IdentityServer.Admin/GenericKeyController.cs @@ -1,5 +1,6 @@ // Project: Aguafrommars/TheIdServer // Copyright (c) 2023 @Olivier Lefebvre +using Aguacongas.IdentityServer.Admin.Configuration; using Aguacongas.IdentityServer.Admin.Services; using Aguacongas.IdentityServer.Store; using Aguacongas.IdentityServer.Store.Entity; @@ -17,7 +18,7 @@ namespace Aguacongas.IdentityServer.Admin /// /// [Produces(JsonFileOutputFormatter.SupportedContentType, "application/json")] - [Route("[controller]")] + [ApiRoute("[controller]")] [GenericControllerNameConvention] public class GenericKeyController : Controller where T : IAuthenticatedEncryptorDescriptor { diff --git a/src/IdentityServer/Aguacongas.IdentityServer.Admin/ImportController.cs b/src/IdentityServer/Aguacongas.IdentityServer.Admin/ImportController.cs index 7c83f729a..3e33e67c5 100644 --- a/src/IdentityServer/Aguacongas.IdentityServer.Admin/ImportController.cs +++ b/src/IdentityServer/Aguacongas.IdentityServer.Admin/ImportController.cs @@ -1,6 +1,7 @@ // Project: Aguafrommars/TheIdServer // Copyright (c) 2023 @Olivier Lefebvre using Aguacongas.IdentityServer.Abstractions; +using Aguacongas.IdentityServer.Admin.Configuration; using Aguacongas.IdentityServer.Store; using Aguacongas.IdentityServer.Store.Entity; using Microsoft.AspNetCore.Authorization; @@ -15,7 +16,7 @@ namespace Aguacongas.IdentityServer.Admin /// Import/export controller /// /// - [Route("[controller]")] + [ApiRoute("[controller]")] public class ImportController : Controller { private readonly IImportService _service; diff --git a/src/IdentityServer/Aguacongas.IdentityServer.Admin/Models/RegistrationException.cs b/src/IdentityServer/Aguacongas.IdentityServer.Admin/Models/RegistrationException.cs index 80df540a3..cee1c263c 100644 --- a/src/IdentityServer/Aguacongas.IdentityServer.Admin/Models/RegistrationException.cs +++ b/src/IdentityServer/Aguacongas.IdentityServer.Admin/Models/RegistrationException.cs @@ -1,7 +1,7 @@ // Project: Aguafrommars/TheIdServer // Copyright (c) 2023 @Olivier Lefebvre using System; -using System.Runtime.Serialization; +using System.Diagnostics.CodeAnalysis; namespace Aguacongas.IdentityServer.Admin.Models { @@ -10,6 +10,7 @@ namespace Aguacongas.IdentityServer.Admin.Models /// /// [Serializable] + [SuppressMessage("Major Code Smell", "S3925:\"ISerializable\" should be implemented correctly", Justification = "Obsolete")] public class RegistrationException : Exception { /// @@ -28,14 +29,5 @@ public RegistrationException(string errorCode, string message) : base(message) { ErrorCode = errorCode; } - - /// - /// Initializes a new instance of the class. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - protected RegistrationException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } } } diff --git a/src/IdentityServer/Aguacongas.IdentityServer.Admin/Services/WindowsAuthentication/WindowsHandler.cs b/src/IdentityServer/Aguacongas.IdentityServer.Admin/Services/WindowsAuthentication/WindowsHandler.cs index d7ae90e27..70b470740 100644 --- a/src/IdentityServer/Aguacongas.IdentityServer.Admin/Services/WindowsAuthentication/WindowsHandler.cs +++ b/src/IdentityServer/Aguacongas.IdentityServer.Admin/Services/WindowsAuthentication/WindowsHandler.cs @@ -18,10 +18,10 @@ public class WindowsHandler : AuthenticationHandler, IAuthentica /// Creates a new /// /// - public WindowsHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) - : base(options, logger, encoder, clock) + public WindowsHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder) + : base(options, logger, encoder) { - _innerHanlder = new NegotiateHandler(options, logger, encoder, clock); + _innerHanlder = new NegotiateHandler(options, logger, encoder); } /// diff --git a/src/IdentityServer/Aguacongas.IdentityServer.Admin/TokenController.cs b/src/IdentityServer/Aguacongas.IdentityServer.Admin/TokenController.cs index fd215a726..b353feb8a 100644 --- a/src/IdentityServer/Aguacongas.IdentityServer.Admin/TokenController.cs +++ b/src/IdentityServer/Aguacongas.IdentityServer.Admin/TokenController.cs @@ -1,4 +1,5 @@ using Aguacongas.IdentityServer.Abstractions; +using Aguacongas.IdentityServer.Admin.Configuration; using Aguacongas.IdentityServer.Admin.Models; using Aguacongas.IdentityServer.Store; using Microsoft.AspNetCore.Authorization; @@ -13,7 +14,7 @@ namespace Aguacongas.IdentityServer.Admin /// /// [Produces("application/json")] - [Route("[controller]")] + [ApiRoute("[controller]")] [Authorize(SharedConstants.TOKENPOLICY)] public class TokenController : Controller { diff --git a/src/IdentityServer/Aguacongas.IdentityServer.Admin/WelcomeFragmentController.cs b/src/IdentityServer/Aguacongas.IdentityServer.Admin/WelcomeFragmentController.cs index 8a6ee6b7f..a244da722 100644 --- a/src/IdentityServer/Aguacongas.IdentityServer.Admin/WelcomeFragmentController.cs +++ b/src/IdentityServer/Aguacongas.IdentityServer.Admin/WelcomeFragmentController.cs @@ -1,5 +1,6 @@ // Project: Aguafrommars/TheIdServer // Copyright (c) 2023 @Olivier Lefebvre +using Aguacongas.IdentityServer.Admin.Configuration; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using System; @@ -12,7 +13,7 @@ namespace Aguacongas.IdentityServer.Admin /// Welcome fragment controller /// /// - [Route("[controller]")] + [ApiRoute("[controller]")] public class WelcomeFragmentController : Controller { private readonly IWebHostEnvironment _environment; diff --git a/src/IdentityServer/Aguacongas.IdentityServer.EntityFramework.Store/AdminStore.cs b/src/IdentityServer/Aguacongas.IdentityServer.EntityFramework.Store/AdminStore.cs index c61fd8070..d26d911a5 100644 --- a/src/IdentityServer/Aguacongas.IdentityServer.EntityFramework.Store/AdminStore.cs +++ b/src/IdentityServer/Aguacongas.IdentityServer.EntityFramework.Store/AdminStore.cs @@ -69,10 +69,7 @@ public async Task DeleteAsync(string id, CancellationToken cancellationToken = d public async Task CreateAsync(TEntity entity, CancellationToken cancellationToken = default) { entity = entity ?? throw new ArgumentNullException(nameof(entity)); - if (entity.Id == null) - { - entity.Id = Guid.NewGuid().ToString(); - } + entity.Id ??= Guid.NewGuid().ToString(); if (entity is IAuditable auditable) { auditable.CreatedAt = DateTime.UtcNow; diff --git a/src/IdentityServer/Aguacongas.IdentityServer.EntityFramework.Store/Aguacongas.IdentityServer.EntityFramework.Store.csproj b/src/IdentityServer/Aguacongas.IdentityServer.EntityFramework.Store/Aguacongas.IdentityServer.EntityFramework.Store.csproj index c13c3fdde..4a965339b 100644 --- a/src/IdentityServer/Aguacongas.IdentityServer.EntityFramework.Store/Aguacongas.IdentityServer.EntityFramework.Store.csproj +++ b/src/IdentityServer/Aguacongas.IdentityServer.EntityFramework.Store/Aguacongas.IdentityServer.EntityFramework.Store.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre EF Core stores implementation for TheIdServer. Copyright (c) 2023 @Olivier Lefebvre @@ -24,7 +24,7 @@ - + diff --git a/src/IdentityServer/Aguacongas.IdentityServer.EntityFramework.Store/OperationalDbContext.cs b/src/IdentityServer/Aguacongas.IdentityServer.EntityFramework.Store/OperationalDbContext.cs index 9828f9d49..5bd3eca81 100644 --- a/src/IdentityServer/Aguacongas.IdentityServer.EntityFramework.Store/OperationalDbContext.cs +++ b/src/IdentityServer/Aguacongas.IdentityServer.EntityFramework.Store/OperationalDbContext.cs @@ -28,16 +28,18 @@ public OperationalDbContext(DbContextOptions options):base public virtual DbSet OneTimeTokens { get; set; } - public DbSet DataProtectionKeys { get; set; } + public virtual DbSet DataProtectionKeys { get; set; } - public DbSet KeyRotationKeys { get; set; } + public virtual DbSet KeyRotationKeys { get; set; } - public DbSet UserSessions { get; set; } + public virtual DbSet UserSessions { get; set; } - public DbSet Saml2pArtifact { get; set; } + public virtual DbSet Saml2pArtifact { get; set; } public virtual DbSet BackChannelAuthenticationRequests { get; set; } + public virtual DbSet PushedAuthorizationRequests { get; set; } + public override int SaveChanges() { SetAuditFields(); diff --git a/src/IdentityServer/Aguacongas.IdentityServer.Http.Store/Aguacongas.IdentityServer.Http.Store.csproj b/src/IdentityServer/Aguacongas.IdentityServer.Http.Store/Aguacongas.IdentityServer.Http.Store.csproj index d5b54b315..a31bcd59a 100644 --- a/src/IdentityServer/Aguacongas.IdentityServer.Http.Store/Aguacongas.IdentityServer.Http.Store.csproj +++ b/src/IdentityServer/Aguacongas.IdentityServer.Http.Store/Aguacongas.IdentityServer.Http.Store.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Duende IdentityServer HTTP stores implementation for TheIdServer API. Copyright (c) 2023 @Olivier Lefebvre @@ -24,8 +24,8 @@ - - + + diff --git a/src/IdentityServer/Aguacongas.IdentityServer.KeysRotation/Aguacongas.IdentityServer.KeysRotation.csproj b/src/IdentityServer/Aguacongas.IdentityServer.KeysRotation/Aguacongas.IdentityServer.KeysRotation.csproj index c0f44114e..59ab4e5c8 100644 --- a/src/IdentityServer/Aguacongas.IdentityServer.KeysRotation/Aguacongas.IdentityServer.KeysRotation.csproj +++ b/src/IdentityServer/Aguacongas.IdentityServer.KeysRotation/Aguacongas.IdentityServer.KeysRotation.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre TheIdServer signing keys rotation. Copyright (c) 2023 @Olivier Lefebvre @@ -18,16 +18,16 @@ - - - + + + - + - - + + diff --git a/src/IdentityServer/Aguacongas.IdentityServer.KeysRotation/EntityFrameworkCore/EntityFrameworkCoreXmlRepository.cs b/src/IdentityServer/Aguacongas.IdentityServer.KeysRotation/EntityFrameworkCore/EntityFrameworkCoreXmlRepository.cs index a7dce8d94..a0e0cd5cb 100644 --- a/src/IdentityServer/Aguacongas.IdentityServer.KeysRotation/EntityFrameworkCore/EntityFrameworkCoreXmlRepository.cs +++ b/src/IdentityServer/Aguacongas.IdentityServer.KeysRotation/EntityFrameworkCore/EntityFrameworkCoreXmlRepository.cs @@ -35,10 +35,7 @@ public class EntityFrameworkCoreXmlRepository : IXmlRepository /// The . public EntityFrameworkCoreXmlRepository(IServiceProvider services, ILoggerFactory loggerFactory) { - if (loggerFactory == null) - { - throw new ArgumentNullException(nameof(loggerFactory)); - } + ArgumentNullException.ThrowIfNull(loggerFactory); _logger = loggerFactory.CreateLogger>(); _services = services ?? throw new ArgumentNullException(nameof(services)); @@ -67,7 +64,7 @@ public void StoreElement(XElement element, string friendlyName) }; context.KeyRotationKeys.Add(newKey); // generic context interface change from original IDataProtectionKeyContext - _logger.LogDebug("Saving key '{FriendlyName}' to '{DbContext}'.", typeof(TContext).Name); + _logger.LogDebug("Saving key '{FriendlyName}' to '{DbContext}'.", friendlyName, typeof(TContext).Name); context.SaveChanges(); } diff --git a/src/IdentityServer/Aguacongas.IdentityServer.MongoDb.Store/AdminStores/AdminStore.cs b/src/IdentityServer/Aguacongas.IdentityServer.MongoDb.Store/AdminStores/AdminStore.cs index 7c262ed72..93a154685 100644 --- a/src/IdentityServer/Aguacongas.IdentityServer.MongoDb.Store/AdminStores/AdminStore.cs +++ b/src/IdentityServer/Aguacongas.IdentityServer.MongoDb.Store/AdminStores/AdminStore.cs @@ -121,10 +121,7 @@ public async Task DeleteAsync(string id, CancellationToken cancellationToken = d public async Task CreateAsync(TEntity entity, CancellationToken cancellationToken = default) { entity = entity ?? throw new ArgumentNullException(nameof(entity)); - if (entity.Id == null) - { - entity.Id = Guid.NewGuid().ToString(); - } + entity.Id ??= Guid.NewGuid().ToString(); if (entity is IAuditable auditable) { auditable.CreatedAt = DateTime.UtcNow; diff --git a/src/IdentityServer/Aguacongas.IdentityServer.MongoDb.Store/Aguacongas.IdentityServer.MongoDb.Store.csproj b/src/IdentityServer/Aguacongas.IdentityServer.MongoDb.Store/Aguacongas.IdentityServer.MongoDb.Store.csproj index 462edd256..0c20dbdf3 100644 --- a/src/IdentityServer/Aguacongas.IdentityServer.MongoDb.Store/Aguacongas.IdentityServer.MongoDb.Store.csproj +++ b/src/IdentityServer/Aguacongas.IdentityServer.MongoDb.Store/Aguacongas.IdentityServer.MongoDb.Store.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre MongoDb stores implementation for TheIdServer. Copyright (c) 2023 @Olivier Lefebvre diff --git a/src/IdentityServer/Aguacongas.IdentityServer.RavenDb.Store/Aguacongas.IdentityServer.RavenDb.Store.csproj b/src/IdentityServer/Aguacongas.IdentityServer.RavenDb.Store/Aguacongas.IdentityServer.RavenDb.Store.csproj index d31b16299..1ae97a00c 100644 --- a/src/IdentityServer/Aguacongas.IdentityServer.RavenDb.Store/Aguacongas.IdentityServer.RavenDb.Store.csproj +++ b/src/IdentityServer/Aguacongas.IdentityServer.RavenDb.Store/Aguacongas.IdentityServer.RavenDb.Store.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre RavenDb stores implementation for TheIdServer. Copyright (c) 2023 @Olivier Lefebvre @@ -24,7 +24,7 @@ - + diff --git a/src/IdentityServer/Aguacongas.IdentityServer.Store/Aguacongas.IdentityServer.Store.csproj b/src/IdentityServer/Aguacongas.IdentityServer.Store/Aguacongas.IdentityServer.Store.csproj index a74640e95..a1dfec4b8 100644 --- a/src/IdentityServer/Aguacongas.IdentityServer.Store/Aguacongas.IdentityServer.Store.csproj +++ b/src/IdentityServer/Aguacongas.IdentityServer.Store/Aguacongas.IdentityServer.Store.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre TheIdServer entities and interfaces. Copyright (c) 2023 @Olivier Lefebvre diff --git a/src/IdentityServer/Aguacongas.IdentityServer.Store/Entity/Client.cs b/src/IdentityServer/Aguacongas.IdentityServer.Store/Entity/Client.cs index 2affb8661..3f157588a 100644 --- a/src/IdentityServer/Aguacongas.IdentityServer.Store/Entity/Client.cs +++ b/src/IdentityServer/Aguacongas.IdentityServer.Store/Entity/Client.cs @@ -389,6 +389,16 @@ public class Client : IAuditable, ICloneable, ILocalizable public bool RequireDPoP { get; set; } + /// + /// Specifies whether pushed authorization requests are required for this client. + /// + public bool RequirePushedAuthorization { get; set; } + + /// + /// Lifetime of pushed authorization requests for this client. + /// + public int? PushedAuthorizationLifetime { get; set; } + /// /// Gets or sets the relying. /// diff --git a/src/IdentityServer/Aguacongas.IdentityServer.Store/Entity/PushedAuthorizationRequest.cs b/src/IdentityServer/Aguacongas.IdentityServer.Store/Entity/PushedAuthorizationRequest.cs new file mode 100644 index 000000000..8689c8923 --- /dev/null +++ b/src/IdentityServer/Aguacongas.IdentityServer.Store/Entity/PushedAuthorizationRequest.cs @@ -0,0 +1,36 @@ +using System; + +namespace Aguacongas.IdentityServer.Store.Entity; + +/// +/// Represents a persisted Pushed Authorization Request. +/// +public class PushedAuthorizationRequest : IAuditable +{ + /// + /// Gets the identifier. + /// + /// + /// The identifier. + /// + /// + /// This is the hash + /// + public string Id { get; set; } + + /// + /// Gets the Expires at + /// + public DateTime ExpiresAtUtc { get; set; } + + /// + /// Gets the request parameters. + /// + public string Parameters { get; set; } + + /// + public DateTime CreatedAt { get; set; } + + /// + public DateTime? ModifiedAt { get; set; } +} diff --git a/src/IdentityServer/Aguacongas.IdentityServer.WsFederation/Aguacongas.IdentityServer.WsFederation.csproj b/src/IdentityServer/Aguacongas.IdentityServer.WsFederation/Aguacongas.IdentityServer.WsFederation.csproj index 51e90f88b..8b467c004 100644 --- a/src/IdentityServer/Aguacongas.IdentityServer.WsFederation/Aguacongas.IdentityServer.WsFederation.csproj +++ b/src/IdentityServer/Aguacongas.IdentityServer.WsFederation/Aguacongas.IdentityServer.WsFederation.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre WS-Federation controller classes. Copyright (c) 2023 @Olivier Lefebvre diff --git a/src/IdentityServer/Aguacongas.IdentityServer/Aguacongas.IdentityServer.csproj b/src/IdentityServer/Aguacongas.IdentityServer/Aguacongas.IdentityServer.csproj index 9df103534..e6543b37b 100644 --- a/src/IdentityServer/Aguacongas.IdentityServer/Aguacongas.IdentityServer.csproj +++ b/src/IdentityServer/Aguacongas.IdentityServer/Aguacongas.IdentityServer.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre TheIdServer bases classes Copyright (c) 2023 @Olivier Lefebvre @@ -33,10 +33,10 @@ - - - - + + + + diff --git a/src/IdentityServer/Aguacongas.IdentityServer/IdentityException.cs b/src/IdentityServer/Aguacongas.IdentityServer/IdentityException.cs index e9bf756b9..7e9387c2e 100644 --- a/src/IdentityServer/Aguacongas.IdentityServer/IdentityException.cs +++ b/src/IdentityServer/Aguacongas.IdentityServer/IdentityException.cs @@ -3,11 +3,12 @@ using Microsoft.AspNetCore.Identity; using System; using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Diagnostics.CodeAnalysis; namespace Aguacongas.IdentityServer { [Serializable] + [SuppressMessage("Major Code Smell", "S3925:\"ISerializable\" should be implemented correctly", Justification = "Obsolete")] public class IdentityException: Exception { public IEnumerable Errors { get; set; } @@ -22,10 +23,5 @@ public IdentityException(string message, Exception innerException) : base(messag public IdentityException() { } - - protected IdentityException(SerializationInfo serializationInfo, StreamingContext streamingContext) - : base(serializationInfo, streamingContext) - { - } } } diff --git a/src/IdentityServer/Duende/Aguacongas.IdentityServer.Admin.Duende/Aguacongas.IdentityServer.Admin.Duende.csproj b/src/IdentityServer/Duende/Aguacongas.IdentityServer.Admin.Duende/Aguacongas.IdentityServer.Admin.Duende.csproj index 26156b28e..e01c95386 100644 --- a/src/IdentityServer/Duende/Aguacongas.IdentityServer.Admin.Duende/Aguacongas.IdentityServer.Admin.Duende.csproj +++ b/src/IdentityServer/Duende/Aguacongas.IdentityServer.Admin.Duende/Aguacongas.IdentityServer.Admin.Duende.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Expose OData controllers to manage TheIdServer for Duende.IdentityServer. Copyright (c) 2023 @Olivier Lefebvre @@ -38,7 +38,7 @@ - + diff --git a/src/IdentityServer/Duende/Aguacongas.IdentityServer.Admin.Duende/Extensions/EndpointRoutingApplicationBuilderExtensions.cs b/src/IdentityServer/Duende/Aguacongas.IdentityServer.Admin.Duende/Extensions/EndpointRoutingApplicationBuilderExtensions.cs index 695823c04..03cbde4b3 100644 --- a/src/IdentityServer/Duende/Aguacongas.IdentityServer.Admin.Duende/Extensions/EndpointRoutingApplicationBuilderExtensions.cs +++ b/src/IdentityServer/Duende/Aguacongas.IdentityServer.Admin.Duende/Extensions/EndpointRoutingApplicationBuilderExtensions.cs @@ -17,6 +17,7 @@ using System.Net; using System.Security.Claims; using System.Threading.Tasks; +using Aguacongas.IdentityServer.Admin.Configuration; namespace Microsoft.AspNetCore.Builder { @@ -31,38 +32,17 @@ public static class EndpointRoutingApplicationBuilderExtensions /// The builder. /// The base path. /// The configure. - /// (Optional) authentication scheme to use /// (Optional) the rewritten path when an api route match outside the base path /// public static IApplicationBuilder UseIdentityServerAdminApi(this IApplicationBuilder builder, string basePath, Action configure, - string authicationScheme = "Bearer", string notAllowedApiRewritePath = "not-allowed") { - var entityTypeList = Utils.GetEntityTypeList(); + ApiBasePath.Value = basePath; - return builder.Map(basePath, child => - { - configure(child); - AuthenticateUserMiddleware(child, basePath, authicationScheme); - }) - .Use((context, next) => - { - // avoid accessing the api outside the path. - var path = context.Request.Path; - if (path.HasValue) - { - var segments = path.Value.Split('/'); - if (path.Equals(basePath, StringComparison.OrdinalIgnoreCase) || - (!path.StartsWithSegments(basePath) && - segments.Any(s => entityTypeList.Any(t => t.Name.Equals(s, StringComparison.OrdinalIgnoreCase))))) - { - context.Request.Path = new PathString($"/{notAllowedApiRewritePath}"); - } - } - return next(); - }); + configure(builder); + return builder; } /// @@ -70,72 +50,69 @@ public static IApplicationBuilder UseIdentityServerAdminApi(this IApplicationBui /// /// The builder. /// The base path. - /// The authication scheme. /// - public static IApplicationBuilder UseIdentityServerAdminAuthentication(this IApplicationBuilder builder, - string basePath, string authicationScheme) + public static IApplicationBuilder UseIdentityServerAdminAuthentication(this IApplicationBuilder builder, string basePath = null) { return builder.Use((context, next) => { - return Authenticate(context, next, basePath, authicationScheme); + return Authenticate(context, next, basePath ?? ApiBasePath.Value); }); } - private static void AuthenticateUserMiddleware(IApplicationBuilder child, string basePath, string authicationScheme) - { - child - .UseRouting() - .UseIdentityServerAdminAuthentication(basePath, authicationScheme) - .UseAuthorization() - .UseEndpoints(enpoints => - { - enpoints.MapAdminApiControllers(); - }); - } - - private static async Task Authenticate(HttpContext context, Func next, string basePath, string authicationScheme) + private static async Task Authenticate(HttpContext context, Func next, string basePath) { var request = context.Request; var path = request.Path; - if (request.Method.Equals("option", StringComparison.OrdinalIgnoreCase) || - (request.PathBase.HasValue && !request.PathBase.StartsWithSegments(basePath)) && !path.StartsWithSegments(basePath)) + if (request.Method.Equals("option", StringComparison.OrdinalIgnoreCase) || + !path.HasValue || + !path.StartsWithSegments(basePath)) { await next().ConfigureAwait(false); return; } - ByBassAuthentication(context, request, path); + if (!context.User.IsAuthenticated()) + { + await AuthenticateUsingAuthorizationHeader(context, request).ConfigureAwait(false); + } + + ByBassAuthentication(context, request, path, basePath); - if (!await GetRegistrationTokenAsync(context, request, path).ConfigureAwait(false)) + if (!await GetRegistrationTokenAsync(context, request, path, basePath).ConfigureAwait(false)) { return; } var logger = context.RequestServices.GetRequiredService>(); - if (context.User.Identity.IsAuthenticated) + + using var scope = logger.BeginScope(new Dictionary { ["User"] = context.User.GetDisplayName() }); + await next().ConfigureAwait(false); + + var response = context.Response; + if (context.Response.StatusCode == (int)HttpStatusCode.Redirect) { - using var authscope = logger.BeginScope(new Dictionary { ["User"] = context.User.GetDisplayName() }); - await next().ConfigureAwait(false); - return; + response.StatusCode = context.User.IsAuthenticated() ? (int)HttpStatusCode.Unauthorized : (int)HttpStatusCode.Forbidden; + await response.CompleteAsync().ConfigureAwait(false); } + } - var result = await context.AuthenticateAsync(authicationScheme) - .ConfigureAwait(false); - - context.User = result.Principal; - using var scope = logger.BeginScope(new Dictionary { ["User"] = context.User.GetDisplayName() }); + private static async Task AuthenticateUsingAuthorizationHeader(HttpContext context, HttpRequest request) + { + var authorizationHeader = request.Headers.Authorization; + var authenticationScheme = authorizationHeader.FirstOrDefault(); - if (!result.Succeeded && - path.StartsWithSegments("/register", StringComparison.OrdinalIgnoreCase) && - request.Method != HttpMethods.Post) + if (authenticationScheme is null) { - await SetForbiddenResponse(context).ConfigureAwait(false); return; } + var result = await context.AuthenticateAsync(authenticationScheme.Split(' ')[0]).ConfigureAwait(false); - await next().ConfigureAwait(false); + if (result.Succeeded) + { + context.User = result.Principal; + } } private static async Task SetForbiddenResponse(HttpContext context) @@ -146,11 +123,11 @@ await response.CompleteAsync() .ConfigureAwait(false); } - private static void ByBassAuthentication(HttpContext context, HttpRequest request, PathString path) + private static void ByBassAuthentication(HttpContext context, HttpRequest request, PathString path, string basePath) { - if ((path.StartsWithSegments("/welcomefragment", StringComparison.OrdinalIgnoreCase) || - path.StartsWithSegments($"/{nameof(Culture)}", StringComparison.OrdinalIgnoreCase) || - path.StartsWithSegments($"/{nameof(LocalizedResource)}", StringComparison.OrdinalIgnoreCase)) && + if ((path.StartsWithSegments($"{basePath}/welcomefragment", StringComparison.OrdinalIgnoreCase) || + path.StartsWithSegments($"{basePath}/{nameof(Culture)}", StringComparison.OrdinalIgnoreCase) || + path.StartsWithSegments($"{basePath}/{nameof(LocalizedResource)}", StringComparison.OrdinalIgnoreCase)) && !context.User.IsInRole(SharedConstants.READERPOLICY) && !context.User.HasClaim(c => c.Type == JwtClaimTypes.Role && c.Value == SharedConstants.ADMINSCOPE) && request.Method == HttpMethods.Get) @@ -165,9 +142,9 @@ private static void ByBassAuthentication(HttpContext context, HttpRequest reques } } - private static async Task GetRegistrationTokenAsync(HttpContext context, HttpRequest request, PathString path) + private static async Task GetRegistrationTokenAsync(HttpContext context, HttpRequest request, PathString path, string basePath) { - if (path.StartsWithSegments("/register", StringComparison.OrdinalIgnoreCase) && + if (path.StartsWithSegments($"{basePath}/register", StringComparison.OrdinalIgnoreCase) && request.Method != HttpMethods.Post) { if (!request.Headers.TryGetValue(HeaderNames.Authorization, out StringValues authorizationHeaderValue)) diff --git a/src/IdentityServer/Duende/Aguacongas.IdentityServer.Admin.Duende/Models/ClientRegisteration.cs b/src/IdentityServer/Duende/Aguacongas.IdentityServer.Admin.Duende/Models/ClientRegisteration.cs index 8bb878c0c..e8de06b0b 100644 --- a/src/IdentityServer/Duende/Aguacongas.IdentityServer.Admin.Duende/Models/ClientRegisteration.cs +++ b/src/IdentityServer/Duende/Aguacongas.IdentityServer.Admin.Duende/Models/ClientRegisteration.cs @@ -164,7 +164,13 @@ public class ClientRegisteration /// The client secret expire at. /// [JsonProperty("client_secret_expires_at")] - public int? ClientSecretExpireAt { get; internal set; } + public int? ClientSecretExpireAt { get; set; } + + /// + /// Specifies whether pushed authorization requests are required for this client. + /// + [JsonProperty("require_pushed_authorization_requests")] + public bool RequirePushedAuthorizationRequests { get; set; } } /// diff --git a/src/IdentityServer/Duende/Aguacongas.IdentityServer.Admin.Duende/RegisterController.cs b/src/IdentityServer/Duende/Aguacongas.IdentityServer.Admin.Duende/RegisterController.cs index 53e66c789..4060d5d1f 100644 --- a/src/IdentityServer/Duende/Aguacongas.IdentityServer.Admin.Duende/RegisterController.cs +++ b/src/IdentityServer/Duende/Aguacongas.IdentityServer.Admin.Duende/RegisterController.cs @@ -1,5 +1,6 @@ // Project: Aguafrommars/TheIdServer // Copyright (c) 2023 @Olivier Lefebvre +using Aguacongas.IdentityServer.Admin.Configuration; using Aguacongas.IdentityServer.Admin.Models; using Aguacongas.IdentityServer.Admin.Services; using Aguacongas.IdentityServer.Store; @@ -15,7 +16,7 @@ namespace Aguacongas.IdentityServer.Admin /// /// [Produces("application/json")] - [Route("[controller]")] + [ApiRoute("[controller]")] public class RegisterController : Controller { private readonly IRegisterClientService _registerClientService; diff --git a/src/IdentityServer/Duende/Aguacongas.IdentityServer.Admin.Duende/Services/RegisterClientService.cs b/src/IdentityServer/Duende/Aguacongas.IdentityServer.Admin.Duende/Services/RegisterClientService.cs index 0fe18f641..86a702e91 100644 --- a/src/IdentityServer/Duende/Aguacongas.IdentityServer.Admin.Duende/Services/RegisterClientService.cs +++ b/src/IdentityServer/Duende/Aguacongas.IdentityServer.Admin.Duende/Services/RegisterClientService.cs @@ -275,7 +275,8 @@ public async Task RegisterAsync(ClientRegisteration registr CibaLifetime = _defaultValues.CibaLifetime, PollingInterval = _defaultValues.PollingInterval, CoordinateLifetimeWithUserSession = _defaultValues.CoordinateLifetimeWithUserSession, - RegistrationToken = Guid.NewGuid() + RegistrationToken = Guid.NewGuid(), + RequirePushedAuthorization = registration.RequirePushedAuthorizationRequests }; await _clientStore.CreateAsync(client).ConfigureAwait(false); @@ -390,7 +391,8 @@ public async Task GetRegistrationAsync(string clientId, str { Culture = r.CultureId, Value = r.Value - }).Union(new[] { new LocalizableProperty { Value = client.TosUri } }) + }).Union(new[] { new LocalizableProperty { Value = client.TosUri } }), + RequirePushedAuthorizationRequests = client.RequirePushedAuthorization }; } diff --git a/src/IdentityServer/Duende/Aguacongas.IdentityServer.Duende/Aguacongas.IdentityServer.Duende.csproj b/src/IdentityServer/Duende/Aguacongas.IdentityServer.Duende/Aguacongas.IdentityServer.Duende.csproj index 3c595a47f..18425cf4d 100644 --- a/src/IdentityServer/Duende/Aguacongas.IdentityServer.Duende/Aguacongas.IdentityServer.Duende.csproj +++ b/src/IdentityServer/Duende/Aguacongas.IdentityServer.Duende/Aguacongas.IdentityServer.Duende.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre TheIdServer base classes for Duende.IdentityServer. Copyright (c) 2023 @Olivier Lefebvre @@ -36,7 +36,7 @@ - + diff --git a/src/IdentityServer/Duende/Aguacongas.IdentityServer.Duende/Extensions/EntityExtensions.cs b/src/IdentityServer/Duende/Aguacongas.IdentityServer.Duende/Extensions/EntityExtensions.cs index a93e5786b..5e484c64a 100644 --- a/src/IdentityServer/Duende/Aguacongas.IdentityServer.Duende/Extensions/EntityExtensions.cs +++ b/src/IdentityServer/Duende/Aguacongas.IdentityServer.Duende/Extensions/EntityExtensions.cs @@ -108,7 +108,9 @@ public static Client ToClient(this Entity.Client client) CoordinateLifetimeWithUserSession = client.CoordinateLifetimeWithUserSession, PollingInterval = client.PollingInterval, RequireRequestObject = client.RequireRequestObject, - RequireDPoP = client.RequireDPoP + RequireDPoP = client.RequireDPoP, + PushedAuthorizationLifetime = client.PushedAuthorizationLifetime, + RequirePushedAuthorization = client.RequirePushedAuthorization, }; } diff --git a/src/IdentityServer/Duende/Aguacongas.IdentityServer.Duende/Extensions/ServiceCollectionExtensions.cs b/src/IdentityServer/Duende/Aguacongas.IdentityServer.Duende/Extensions/ServiceCollectionExtensions.cs index 12b3d7424..862ce485b 100644 --- a/src/IdentityServer/Duende/Aguacongas.IdentityServer.Duende/Extensions/ServiceCollectionExtensions.cs +++ b/src/IdentityServer/Duende/Aguacongas.IdentityServer.Duende/Extensions/ServiceCollectionExtensions.cs @@ -64,7 +64,8 @@ public static IServiceCollection AddOperationalStores(this IServiceCollection se .AddTransient() .AddTransient(p => p.GetRequiredService()) .AddTransient(p => p.GetRequiredService()) - .AddTransient(); + .AddTransient() + .AddTransient(); } public static IServiceCollection AddTokenExchange(this IServiceCollection services) diff --git a/src/IdentityServer/Duende/Aguacongas.IdentityServer.Duende/Store/GrantStore.cs b/src/IdentityServer/Duende/Aguacongas.IdentityServer.Duende/Store/GrantStore.cs index ec14bb2d4..8cdd20864 100644 --- a/src/IdentityServer/Duende/Aguacongas.IdentityServer.Duende/Store/GrantStore.cs +++ b/src/IdentityServer/Duende/Aguacongas.IdentityServer.Duende/Store/GrantStore.cs @@ -77,7 +77,7 @@ await RemoveEntityAsync(entity) .ConfigureAwait(false); } - private Task RemoveEntityAsync(TEntity entity) + protected Task RemoveEntityAsync(TEntity entity) { if (entity == null) { diff --git a/src/IdentityServer/Duende/Aguacongas.IdentityServer.Duende/Store/PushedAuthorizationRequestStore.cs b/src/IdentityServer/Duende/Aguacongas.IdentityServer.Duende/Store/PushedAuthorizationRequestStore.cs new file mode 100644 index 000000000..785d4844c --- /dev/null +++ b/src/IdentityServer/Duende/Aguacongas.IdentityServer.Duende/Store/PushedAuthorizationRequestStore.cs @@ -0,0 +1,39 @@ +using Duende.IdentityServer.Models; +using Duende.IdentityServer.Stores; +using System; +using System.Threading.Tasks; + +namespace Aguacongas.IdentityServer.Store; +public class PushedAuthorizationRequestStore : IPushedAuthorizationRequestStore +{ + private readonly IAdminStore _store; + + public PushedAuthorizationRequestStore(IAdminStore store) + { + ArgumentNullException.ThrowIfNull(store); + _store = store; + } + + public Task ConsumeByHashAsync(string referenceValueHash) + => _store.DeleteAsync(referenceValueHash); + + + public async Task GetByHashAsync(string referenceValueHash) + { + var entity = await _store.GetAsync(referenceValueHash, null).ConfigureAwait(false); + return entity is null ? null : new PushedAuthorizationRequest + { + ExpiresAtUtc = entity.ExpiresAtUtc, + Parameters = entity.Parameters, + ReferenceValueHash = entity.Id + }; + } + + public Task StoreAsync(PushedAuthorizationRequest pushedAuthorizationRequest) + => _store.CreateAsync(new Entity.PushedAuthorizationRequest + { + ExpiresAtUtc = pushedAuthorizationRequest.ExpiresAtUtc, + Id = pushedAuthorizationRequest.ReferenceValueHash, + Parameters = pushedAuthorizationRequest.Parameters + }); +} diff --git a/src/IdentityServer/Duende/Aguacongas.IdentityServer.Duende/Store/ReferenceTokenStore.cs b/src/IdentityServer/Duende/Aguacongas.IdentityServer.Duende/Store/ReferenceTokenStore.cs index 30eeb0577..4d6198ab6 100644 --- a/src/IdentityServer/Duende/Aguacongas.IdentityServer.Duende/Store/ReferenceTokenStore.cs +++ b/src/IdentityServer/Duende/Aguacongas.IdentityServer.Duende/Store/ReferenceTokenStore.cs @@ -5,15 +5,18 @@ using Duende.IdentityServer.Stores; using Duende.IdentityServer.Stores.Serialization; using System; +using System.Linq; using System.Threading.Tasks; namespace Aguacongas.IdentityServer.Store { public class ReferenceTokenStore : GrantStore, IReferenceTokenStore { + private readonly IAdminStore _store; public ReferenceTokenStore(IAdminStore store, IPersistentGrantSerializer serializer) : base(store, serializer) { + _store = store; } public Task GetReferenceTokenAsync(string handle) @@ -22,8 +25,21 @@ public Task GetReferenceTokenAsync(string handle) public Task RemoveReferenceTokenAsync(string handle) => RemoveAsync(handle); - public Task RemoveReferenceTokensAsync(string subjectId, string clientId) - => RemoveAsync(subjectId, clientId); + public async Task RemoveReferenceTokensAsync(string subjectId, string clientId, string sessionId = null) + { + if (sessionId is not null) + { + var entity = (await _store.GetAsync(new PageRequest + { + Filter = $"{nameof(ReferenceToken.UserId)} eq '{subjectId}' and {nameof(ReferenceToken.ClientId)} eq '{clientId}' and {nameof(ReferenceToken.SessionId)} eq '{sessionId}'" + }).ConfigureAwait(false)).Items.FirstOrDefault(); + + await RemoveEntityAsync(entity).ConfigureAwait(false); + } + + await RemoveAsync(subjectId, clientId).ConfigureAwait(false); + } + public Task StoreReferenceTokenAsync(Token token) => StoreAsync(token, token.CreationTime.AddSeconds(token.Lifetime)); diff --git a/src/IdentityServer/Duende/Aguacongas.IdentityServer.EntityFramework.Store.Duende/Aguacongas.IdentityServer.EntityFramework.Store.Duende.csproj b/src/IdentityServer/Duende/Aguacongas.IdentityServer.EntityFramework.Store.Duende/Aguacongas.IdentityServer.EntityFramework.Store.Duende.csproj index fa31f53f4..e24392042 100644 --- a/src/IdentityServer/Duende/Aguacongas.IdentityServer.EntityFramework.Store.Duende/Aguacongas.IdentityServer.EntityFramework.Store.Duende.csproj +++ b/src/IdentityServer/Duende/Aguacongas.IdentityServer.EntityFramework.Store.Duende/Aguacongas.IdentityServer.EntityFramework.Store.Duende.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Duende.IdentityServer EF Core stores implementation for TheIdServer. Copyright (c) 2023 @Olivier Lefebvre diff --git a/src/IdentityServer/Duende/Aguacongas.IdentityServer.KeysRotation.Duende/Aguacongas.IdentityServer.KeysRotation.Duende.csproj b/src/IdentityServer/Duende/Aguacongas.IdentityServer.KeysRotation.Duende/Aguacongas.IdentityServer.KeysRotation.Duende.csproj index 6a04e5d05..cc6146f74 100644 --- a/src/IdentityServer/Duende/Aguacongas.IdentityServer.KeysRotation.Duende/Aguacongas.IdentityServer.KeysRotation.Duende.csproj +++ b/src/IdentityServer/Duende/Aguacongas.IdentityServer.KeysRotation.Duende/Aguacongas.IdentityServer.KeysRotation.Duende.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Duende.IdentityServer signing keys rotation. Copyright (c) 2023 @Olivier Lefebvre @@ -28,7 +28,7 @@ - + diff --git a/src/IdentityServer/Duende/Aguacongas.IdentityServer.MongoDb.Store.Duende/Aguacongas.IdentityServer.MongoDb.Store.Duende.csproj b/src/IdentityServer/Duende/Aguacongas.IdentityServer.MongoDb.Store.Duende/Aguacongas.IdentityServer.MongoDb.Store.Duende.csproj index 3fe4dede4..cc60cb5d8 100644 --- a/src/IdentityServer/Duende/Aguacongas.IdentityServer.MongoDb.Store.Duende/Aguacongas.IdentityServer.MongoDb.Store.Duende.csproj +++ b/src/IdentityServer/Duende/Aguacongas.IdentityServer.MongoDb.Store.Duende/Aguacongas.IdentityServer.MongoDb.Store.Duende.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Duende.IdentityServer MongoDb stores implementation for TheIdServer. Copyright (c) 2023 @Olivier Lefebvre diff --git a/src/IdentityServer/Duende/Aguacongas.IdentityServer.MongoDb.Store.Duende/ServiceCollectionExtensions.cs b/src/IdentityServer/Duende/Aguacongas.IdentityServer.MongoDb.Store.Duende/ServiceCollectionExtensions.cs index a7f5d2a23..22ac9a9bb 100644 --- a/src/IdentityServer/Duende/Aguacongas.IdentityServer.MongoDb.Store.Duende/ServiceCollectionExtensions.cs +++ b/src/IdentityServer/Duende/Aguacongas.IdentityServer.MongoDb.Store.Duende/ServiceCollectionExtensions.cs @@ -57,8 +57,6 @@ private static void AddTheIdServerMongoDbStores(IServiceCollection services, Typ var iAdminStoreType = typeof(IAdminStore<>) .MakeGenericType(entityType.GetTypeInfo()).GetTypeInfo(); services.AddTransient(iAdminStoreType, cacheAdminStoreType); - - } private static object GetCollection(Func getDatabase, IServiceProvider provider, Type entityType) diff --git a/src/IdentityServer/Duende/Aguacongas.IdentityServer.RavenDb.Store.Duende/Aguacongas.IdentityServer.RavenDb.Store.Duende.csproj b/src/IdentityServer/Duende/Aguacongas.IdentityServer.RavenDb.Store.Duende/Aguacongas.IdentityServer.RavenDb.Store.Duende.csproj index 349d7477d..078e5d4b9 100644 --- a/src/IdentityServer/Duende/Aguacongas.IdentityServer.RavenDb.Store.Duende/Aguacongas.IdentityServer.RavenDb.Store.Duende.csproj +++ b/src/IdentityServer/Duende/Aguacongas.IdentityServer.RavenDb.Store.Duende/Aguacongas.IdentityServer.RavenDb.Store.Duende.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Duende.IdentityServer RavenDb stores implementation for TheIdServer. Copyright (c) 2023 @Olivier Lefebvre diff --git a/src/IdentityServer/Duende/Aguacongas.IdentityServer.RavenDb.Store.Duende/ServiceCollectionExtensions.cs b/src/IdentityServer/Duende/Aguacongas.IdentityServer.RavenDb.Store.Duende/ServiceCollectionExtensions.cs index 0a8d9412c..81ae4c8a7 100644 --- a/src/IdentityServer/Duende/Aguacongas.IdentityServer.RavenDb.Store.Duende/ServiceCollectionExtensions.cs +++ b/src/IdentityServer/Duende/Aguacongas.IdentityServer.RavenDb.Store.Duende/ServiceCollectionExtensions.cs @@ -89,6 +89,7 @@ public static IServiceCollection AddTheIdServerRavenDbStores(this IServiceCollec .AddTransient>() .AddTransient() .AddTransient>() + .AddTransient>() .AddTransient, CacheAdminStore>() .AddTransient, CacheAdminStore>() .AddTransient, CacheAdminStore>() @@ -135,10 +136,11 @@ public static IServiceCollection AddTheIdServerRavenDbStores(this IServiceCollec .AddTransient, CacheAdminStore, Entity.RelyingParty>>() .AddTransient, CacheAdminStore>() .AddTransient, CacheAdminStore, Entity.Saml2PArtifact>>() + .AddTransient, CacheAdminStore, Entity.PushedAuthorizationRequest>>() .AddTransient, Entity.User>>() .AddTransient, Entity.Role>>() .AddTransient, Entity.ExternalProvider>>() - .AddTransient, Entity.Saml2PArtifact>>(); + .AddTransient, Entity.Saml2PArtifact>>(); } } } diff --git a/src/IdentityServer/Duende/Aguacongas.IdentityServer.Saml2p.Duende/Aguacongas.IdentityServer.Saml2p.Duende.csproj b/src/IdentityServer/Duende/Aguacongas.IdentityServer.Saml2p.Duende/Aguacongas.IdentityServer.Saml2p.Duende.csproj index db01fa28c..9b83cc1ec 100644 --- a/src/IdentityServer/Duende/Aguacongas.IdentityServer.Saml2p.Duende/Aguacongas.IdentityServer.Saml2p.Duende.csproj +++ b/src/IdentityServer/Duende/Aguacongas.IdentityServer.Saml2p.Duende/Aguacongas.IdentityServer.Saml2p.Duende.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Olivier Lefebvre Add Saml2P controller to your Duende.IdentityServer server. @@ -28,8 +28,8 @@ - - + + diff --git a/src/IdentityServer/Duende/Aguacongas.IdentityServer.Saml2p.Duende/Services/Signin/SignInResponseGenerator.cs b/src/IdentityServer/Duende/Aguacongas.IdentityServer.Saml2p.Duende/Services/Signin/SignInResponseGenerator.cs index 564a212fd..1d8eeb76e 100644 --- a/src/IdentityServer/Duende/Aguacongas.IdentityServer.Saml2p.Duende/Services/Signin/SignInResponseGenerator.cs +++ b/src/IdentityServer/Duende/Aguacongas.IdentityServer.Saml2p.Duende/Services/Signin/SignInResponseGenerator.cs @@ -84,9 +84,12 @@ public async Task GenerateArtifactResponseAsync(SignInValidationR { InResponseTo = saml2ArtifactResolve.Id }; - soapEnvelope!.Bind(saml2ArtifactResponse); + soapEnvelope.Bind(saml2ArtifactResponse); - await _userSession.AddClientIdAsync(result.Client!.ClientId).ConfigureAwait(false); + if (result.Client?.ClientId is not null) + { + await _userSession.AddClientIdAsync(result.Client.ClientId).ConfigureAwait(false); + } return soapEnvelope.ToActionResult(); } @@ -181,7 +184,7 @@ private async Task LoginPostResponse(Saml2Id? inResponseTo, Saml2 issuedTokenLifetime: settings.IssuedTokenLifetime); } - if (status == Saml2StatusCodes.Success) + if (status == Saml2StatusCodes.Success && clientId is not null) { await _userSession.AddClientIdAsync(clientId).ConfigureAwait(false); } @@ -223,7 +226,7 @@ private async Task LoginArtifactResponseAsync(Saml2Id? inResponse claimsIdentity.RemoveClaim(claimsIdentity.FindFirst(JwtClaimTypes.Subject)); saml2AuthnResponse.ClaimsIdentity = claimsIdentity; - saml2AuthnResponse.CreateSecurityToken(relyingParty?.Issuer, + saml2AuthnResponse.CreateSecurityToken(relyingParty.Issuer, subjectConfirmationLifetime: settings.SubjectConfirmationLifetime, issuedTokenLifetime: settings.IssuedTokenLifetime); } @@ -343,7 +346,7 @@ protected async Task CreateSubjectAsync(SignInValidationResult - net7.0 + net8.0 Olivier Lefebvre Add WS-Federation controller to your Duende.IdentityServer server. Copyright (c) 2023 @Olivier Lefebvre diff --git a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.MySql/Aguacongas.TheIdServer.Migrations.MySql.csproj b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.MySql/Aguacongas.TheIdServer.Migrations.MySql.csproj index 5ede84981..6d3118d48 100644 --- a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.MySql/Aguacongas.TheIdServer.Migrations.MySql.csproj +++ b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.MySql/Aguacongas.TheIdServer.Migrations.MySql.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Aguacongas.TheIdServer.MySql Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre @@ -27,8 +27,7 @@ - - + diff --git a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Oracle/Aguacongas.TheIdServer.Migrations.Oracle.csproj b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Oracle/Aguacongas.TheIdServer.Migrations.Oracle.csproj index e6267ad39..55867afc1 100644 --- a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Oracle/Aguacongas.TheIdServer.Migrations.Oracle.csproj +++ b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Oracle/Aguacongas.TheIdServer.Migrations.Oracle.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Aguacongas.TheIdServer.Oracle Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre @@ -16,8 +16,8 @@ - - + + diff --git a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/Aguacongas.TheIdServer.Migrations.PostgreSQL.csproj b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/Aguacongas.TheIdServer.Migrations.PostgreSQL.csproj index 7fa5b7a33..9929d68c2 100644 --- a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/Aguacongas.TheIdServer.Migrations.PostgreSQL.csproj +++ b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/Aguacongas.TheIdServer.Migrations.PostgreSQL.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Aguacongas.TheIdServer.PostgreSQL Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre @@ -27,7 +27,7 @@ - + diff --git a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/Migrations/ConfigurationDb/20231116210245_PushAuthorizationRequest.Designer.cs b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/Migrations/ConfigurationDb/20231116210245_PushAuthorizationRequest.Designer.cs new file mode 100644 index 000000000..927482e1a --- /dev/null +++ b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/Migrations/ConfigurationDb/20231116210245_PushAuthorizationRequest.Designer.cs @@ -0,0 +1,1459 @@ +// +using System; +using Aguacongas.IdentityServer.EntityFramework.Store; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Aguacongas.TheIdServer.PostgreSQL.Migrations.ConfigurationDb +{ + [DbContext(typeof(ConfigurationDbContext))] + [Migration("20231116210245_PushAuthorizationRequest")] + partial class PushAuthorizationRequest + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiApiScope", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ApiId") + .IsRequired() + .HasColumnType("text"); + + b.Property("ApiScopeId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ApiId"); + + b.HasIndex("ApiScopeId"); + + b.ToTable("ApiApiScope"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiClaim", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ApiId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.HasKey("Id"); + + b.HasIndex("ApiId", "Type") + .IsUnique(); + + b.ToTable("ApiClaims"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiLocalizedResource", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ApiId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CultureId") + .IsRequired() + .HasColumnType("text"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ResourceKind") + .HasColumnType("integer"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApiId"); + + b.ToTable("ApiLocalizedResources"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiProperty", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ApiId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Value") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.HasKey("Id"); + + b.HasIndex("ApiId", "Key") + .IsUnique(); + + b.ToTable("ApiProperty"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiScope", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Emphasize") + .HasColumnType("boolean"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Required") + .HasColumnType("boolean"); + + b.Property("ShowInDiscoveryDocument") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("ApiScopes"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiScopeClaim", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ApiScopeId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.HasKey("Id"); + + b.HasIndex("ApiScopeId", "Type") + .IsUnique(); + + b.ToTable("ApiScopeClaims"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiScopeLocalizedResource", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ApiScopeId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CultureId") + .IsRequired() + .HasColumnType("text"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ResourceKind") + .HasColumnType("integer"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApiScopeId"); + + b.ToTable("ApiScopeLocalizedResources"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiScopeProperty", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ApiScopeId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Value") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.HasKey("Id"); + + b.HasIndex("ApiScopeId", "Key") + .IsUnique(); + + b.ToTable("ApiScopeProperty"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiSecret", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ApiId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Expiration") + .HasColumnType("timestamp with time zone"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApiId"); + + b.ToTable("ApiSecrets"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.Client", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AbsoluteRefreshTokenLifetime") + .HasColumnType("integer"); + + b.Property("AccessTokenLifetime") + .HasColumnType("integer"); + + b.Property("AccessTokenType") + .HasColumnType("integer"); + + b.Property("AllowAccessTokensViaBrowser") + .HasColumnType("boolean"); + + b.Property("AllowOfflineAccess") + .HasColumnType("boolean"); + + b.Property("AllowPlainTextPkce") + .HasColumnType("boolean"); + + b.Property("AllowRememberConsent") + .HasColumnType("boolean"); + + b.Property("AlwaysIncludeUserClaimsInIdToken") + .HasColumnType("boolean"); + + b.Property("AlwaysSendClientClaims") + .HasColumnType("boolean"); + + b.Property("AuthorizationCodeLifetime") + .HasColumnType("integer"); + + b.Property("BackChannelLogoutSessionRequired") + .HasColumnType("boolean"); + + b.Property("BackChannelLogoutUri") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("CibaLifetime") + .HasColumnType("integer"); + + b.Property("ClientClaimsPrefix") + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("ClientName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ClientUri") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ConsentLifetime") + .HasColumnType("integer"); + + b.Property("CoordinateLifetimeWithUserSession") + .HasColumnType("boolean"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("DeviceCodeLifetime") + .HasColumnType("integer"); + + b.Property("EnableLocalLogin") + .HasColumnType("boolean"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("FrontChannelLogoutSessionRequired") + .HasColumnType("boolean"); + + b.Property("FrontChannelLogoutUri") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("IdentityTokenLifetime") + .HasColumnType("integer"); + + b.Property("IncludeJwtId") + .HasColumnType("boolean"); + + b.Property("LogoUri") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("NonEditable") + .HasColumnType("boolean"); + + b.Property("PairWiseSubjectSalt") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PolicyUri") + .HasColumnType("text"); + + b.Property("PollingInterval") + .HasColumnType("integer"); + + b.Property("ProtocolType") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PushedAuthorizationLifetime") + .HasColumnType("integer"); + + b.Property("RefreshTokenExpiration") + .HasColumnType("integer"); + + b.Property("RefreshTokenUsage") + .HasColumnType("integer"); + + b.Property("RegistrationToken") + .HasColumnType("uuid"); + + b.Property("RelyingPartyId") + .HasColumnType("text"); + + b.Property("RequireClientSecret") + .HasColumnType("boolean"); + + b.Property("RequireConsent") + .HasColumnType("boolean"); + + b.Property("RequireDPoP") + .HasColumnType("boolean"); + + b.Property("RequirePkce") + .HasColumnType("boolean"); + + b.Property("RequirePushedAuthorization") + .HasColumnType("boolean"); + + b.Property("RequireRequestObject") + .HasColumnType("boolean"); + + b.Property("SlidingRefreshTokenLifetime") + .HasColumnType("integer"); + + b.Property("TosUri") + .HasColumnType("text"); + + b.Property("UpdateAccessTokenClaimsOnRefresh") + .HasColumnType("boolean"); + + b.Property("UserCodeType") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("UserSsoLifetime") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RelyingPartyId"); + + b.ToTable("Clients"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientAllowedIdentityTokenSigningAlgorithm", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Algorithm") + .IsRequired() + .HasColumnType("text"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ClientId", "Algorithm") + .IsUnique(); + + b.ToTable("ClientAllowedIdentityTokenSigningAlgorithms"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientClaim", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientClaims"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientGrantType", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GrantType") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ClientId", "GrantType") + .IsUnique(); + + b.ToTable("ClientGrantTypes"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientIdpRestriction", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Provider") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId", "Provider") + .IsUnique(); + + b.ToTable("ClientIdpRestriction"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientLocalizedResource", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CultureId") + .IsRequired() + .HasColumnType("text"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ResourceKind") + .HasColumnType("integer"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientLocalizedResources"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientProperty", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Value") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId", "Key") + .IsUnique(); + + b.ToTable("ClientProperties"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientScope", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId", "Scope") + .IsUnique(); + + b.ToTable("ClientScopes"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientSecret", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Expiration") + .HasColumnType("timestamp with time zone"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientSecrets"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientUri", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Kind") + .HasColumnType("integer"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SanetizedCorsUri") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Uri") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId", "Uri") + .IsUnique(); + + b.ToTable("ClientUris"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.Culture", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Cultures"); + + b.HasData( + new + { + Id = "en", + CreatedAt = new DateTime(2023, 11, 16, 21, 2, 44, 684, DateTimeKind.Utc).AddTicks(8194) + }); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ExternalClaimTransformation", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AsMultipleValues") + .HasColumnType("boolean"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("FromClaimType") + .IsRequired() + .HasColumnType("text"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Scheme") + .IsRequired() + .HasColumnType("text"); + + b.Property("ToClaimType") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Scheme", "FromClaimType") + .IsUnique(); + + b.ToTable("ExternalClaimTransformations"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ExternalProvider", b => + { + b.Property("Id") + .HasColumnType("text") + .HasColumnName("Scheme"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("MapDefaultOutboundClaimType") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SerializedHandlerType") + .HasColumnType("text"); + + b.Property("SerializedOptions") + .HasColumnType("text"); + + b.Property("StoreClaims") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Providers"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.IdentityClaim", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IdentityId") + .IsRequired() + .HasColumnType("text"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.HasKey("Id"); + + b.HasIndex("IdentityId", "Type") + .IsUnique(); + + b.ToTable("IdentityClaims"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.IdentityLocalizedResource", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CultureId") + .IsRequired() + .HasColumnType("text"); + + b.Property("IdentityId") + .IsRequired() + .HasColumnType("text"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ResourceKind") + .HasColumnType("integer"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("IdentityId"); + + b.ToTable("IdentityLocalizedResources"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.IdentityProperty", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IdentityId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Value") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.HasKey("Id"); + + b.HasIndex("IdentityId", "Key") + .IsUnique(); + + b.ToTable("IdentityProperties"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.IdentityResource", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Emphasize") + .HasColumnType("boolean"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("NonEditable") + .HasColumnType("boolean"); + + b.Property("Required") + .HasColumnType("boolean"); + + b.Property("ShowInDiscoveryDocument") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Identities"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.LocalizedResource", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("BaseName") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CultureId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Key") + .IsRequired() + .HasColumnType("text"); + + b.Property("Location") + .HasColumnType("text"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CultureId"); + + b.ToTable("LocalizedResources"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ProtectResource", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("NonEditable") + .HasColumnType("boolean"); + + b.Property("RequireResourceIndicator") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Apis"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.RelyingParty", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("DigestAlgorithm") + .IsRequired() + .HasColumnType("text"); + + b.Property("EncryptionCertificate") + .HasColumnType("bytea"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SamlNameIdentifierFormat") + .HasColumnType("text"); + + b.Property("SignatureAlgorithm") + .IsRequired() + .HasColumnType("text"); + + b.Property("TokenType") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("RelyingParties"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.RelyingPartyClaimMapping", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("FromClaimType") + .IsRequired() + .HasColumnType("text"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RelyingPartyId") + .HasColumnType("text"); + + b.Property("ToClaimType") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RelyingPartyId"); + + b.ToTable("RelyingPartyClaimMappings"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiApiScope", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.ProtectResource", "Api") + .WithMany("ApiScopes") + .HasForeignKey("ApiId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Aguacongas.IdentityServer.Store.Entity.ApiScope", "ApiScope") + .WithMany("Apis") + .HasForeignKey("ApiScopeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Api"); + + b.Navigation("ApiScope"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiClaim", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.ProtectResource", "Api") + .WithMany("ApiClaims") + .HasForeignKey("ApiId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Api"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiLocalizedResource", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.ProtectResource", "Api") + .WithMany("Resources") + .HasForeignKey("ApiId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Api"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiProperty", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.ProtectResource", "Api") + .WithMany("Properties") + .HasForeignKey("ApiId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Api"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiScopeClaim", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.ApiScope", "ApiScope") + .WithMany("ApiScopeClaims") + .HasForeignKey("ApiScopeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApiScope"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiScopeLocalizedResource", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.ApiScope", "ApiScope") + .WithMany("Resources") + .HasForeignKey("ApiScopeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApiScope"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiScopeProperty", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.ApiScope", "ApiScope") + .WithMany("Properties") + .HasForeignKey("ApiScopeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApiScope"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiSecret", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.ProtectResource", "Api") + .WithMany("Secrets") + .HasForeignKey("ApiId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Api"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.Client", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.RelyingParty", "RelyingParty") + .WithMany("Clients") + .HasForeignKey("RelyingPartyId"); + + b.Navigation("RelyingParty"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientAllowedIdentityTokenSigningAlgorithm", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.Client", "Client") + .WithMany("AllowedIdentityTokenSigningAlgorithms") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientClaim", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.Client", "Client") + .WithMany("ClientClaims") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientGrantType", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.Client", "Client") + .WithMany("AllowedGrantTypes") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientIdpRestriction", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.Client", "Client") + .WithMany("IdentityProviderRestrictions") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientLocalizedResource", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.Client", "Client") + .WithMany("Resources") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientProperty", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.Client", "Client") + .WithMany("Properties") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientScope", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.Client", "Client") + .WithMany("AllowedScopes") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientSecret", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.Client", "Client") + .WithMany("ClientSecrets") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientUri", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.Client", "Client") + .WithMany("RedirectUris") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ExternalClaimTransformation", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.ExternalProvider", null) + .WithMany("ClaimTransformations") + .HasForeignKey("Scheme") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.IdentityClaim", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.IdentityResource", "Identity") + .WithMany("IdentityClaims") + .HasForeignKey("IdentityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Identity"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.IdentityLocalizedResource", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.IdentityResource", "Identity") + .WithMany("Resources") + .HasForeignKey("IdentityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Identity"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.IdentityProperty", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.IdentityResource", "Identity") + .WithMany("Properties") + .HasForeignKey("IdentityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Identity"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.LocalizedResource", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.Culture", "Culture") + .WithMany("Resources") + .HasForeignKey("CultureId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Culture"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.RelyingPartyClaimMapping", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.RelyingParty", "RelyingParty") + .WithMany("ClaimMappings") + .HasForeignKey("RelyingPartyId"); + + b.Navigation("RelyingParty"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiScope", b => + { + b.Navigation("ApiScopeClaims"); + + b.Navigation("Apis"); + + b.Navigation("Properties"); + + b.Navigation("Resources"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.Client", b => + { + b.Navigation("AllowedGrantTypes"); + + b.Navigation("AllowedIdentityTokenSigningAlgorithms"); + + b.Navigation("AllowedScopes"); + + b.Navigation("ClientClaims"); + + b.Navigation("ClientSecrets"); + + b.Navigation("IdentityProviderRestrictions"); + + b.Navigation("Properties"); + + b.Navigation("RedirectUris"); + + b.Navigation("Resources"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.Culture", b => + { + b.Navigation("Resources"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ExternalProvider", b => + { + b.Navigation("ClaimTransformations"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.IdentityResource", b => + { + b.Navigation("IdentityClaims"); + + b.Navigation("Properties"); + + b.Navigation("Resources"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ProtectResource", b => + { + b.Navigation("ApiClaims"); + + b.Navigation("ApiScopes"); + + b.Navigation("Properties"); + + b.Navigation("Resources"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.RelyingParty", b => + { + b.Navigation("ClaimMappings"); + + b.Navigation("Clients"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/Migrations/ConfigurationDb/20231116210245_PushAuthorizationRequest.cs b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/Migrations/ConfigurationDb/20231116210245_PushAuthorizationRequest.cs new file mode 100644 index 000000000..5acfe9f5f --- /dev/null +++ b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/Migrations/ConfigurationDb/20231116210245_PushAuthorizationRequest.cs @@ -0,0 +1,40 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Aguacongas.TheIdServer.PostgreSQL.Migrations.ConfigurationDb +{ + /// + public partial class PushAuthorizationRequest : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "PushedAuthorizationLifetime", + table: "Clients", + type: "integer", + nullable: true); + + migrationBuilder.AddColumn( + name: "RequirePushedAuthorization", + table: "Clients", + type: "boolean", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "PushedAuthorizationLifetime", + table: "Clients"); + + migrationBuilder.DropColumn( + name: "RequirePushedAuthorization", + table: "Clients"); + } + } +} diff --git a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/Migrations/ConfigurationDb/ConfigurationDbContextModelSnapshot.cs b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/Migrations/ConfigurationDb/ConfigurationDbContextModelSnapshot.cs index 37fd714de..a42d33ff8 100644 --- a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/Migrations/ConfigurationDb/ConfigurationDbContextModelSnapshot.cs +++ b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/Migrations/ConfigurationDb/ConfigurationDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "7.0.5") + .HasAnnotation("ProductVersion", "8.0.0") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -426,6 +426,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(200) .HasColumnType("character varying(200)"); + b.Property("PushedAuthorizationLifetime") + .HasColumnType("integer"); + b.Property("RefreshTokenExpiration") .HasColumnType("integer"); @@ -450,6 +453,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("RequirePkce") .HasColumnType("boolean"); + b.Property("RequirePushedAuthorization") + .HasColumnType("boolean"); + b.Property("RequireRequestObject") .HasColumnType("boolean"); @@ -773,7 +779,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) new { Id = "en", - CreatedAt = new DateTime(2023, 5, 20, 8, 24, 40, 357, DateTimeKind.Utc).AddTicks(4532) + CreatedAt = new DateTime(2023, 11, 16, 21, 2, 44, 684, DateTimeKind.Utc).AddTicks(8194) }); }); diff --git a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/Migrations/OperationalDb/20231116205225_PushAuthorizationRequest.Designer.cs b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/Migrations/OperationalDb/20231116205225_PushAuthorizationRequest.Designer.cs new file mode 100644 index 000000000..94792939f --- /dev/null +++ b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/Migrations/OperationalDb/20231116205225_PushAuthorizationRequest.Designer.cs @@ -0,0 +1,404 @@ +// +using System; +using Aguacongas.IdentityServer.EntityFramework.Store; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Aguacongas.TheIdServer.PostgreSQL.Migrations.OperationalDb +{ + [DbContext(typeof(OperationalDbContext))] + [Migration("20231116205225_PushAuthorizationRequest")] + partial class PushAuthorizationRequest + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Aguacongas.IdentityServer.KeysRotation.EntityFrameworkCore.KeyRotationKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("text"); + + b.Property("Xml") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("KeyRotationKeys"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.AuthorizationCode", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Expiration") + .HasColumnType("timestamp with time zone"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SessionId") + .HasColumnType("text"); + + b.Property("UserId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.ToTable("AuthorizationCodes"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.BackChannelAuthenticationRequest", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Expiration") + .HasColumnType("timestamp with time zone"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SessionId") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.ToTable("BackChannelAuthenticationRequests"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.DeviceCode", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Code") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Expiration") + .HasColumnType("timestamp with time zone"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("UserCode") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.ToTable("DeviceCodes"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.OneTimeToken", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Expiration") + .HasColumnType("timestamp with time zone"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SessionId") + .HasColumnType("text"); + + b.Property("UserId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.ToTable("OneTimeTokens"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.PushedAuthorizationRequest", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpiresAtUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Parameters") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("PushedAuthorizationRequests"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ReferenceToken", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Expiration") + .HasColumnType("timestamp with time zone"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SessionId") + .HasColumnType("text"); + + b.Property("UserId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.ToTable("ReferenceTokens"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.RefreshToken", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Expiration") + .HasColumnType("timestamp with time zone"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SessionId") + .HasColumnType("text"); + + b.Property("UserId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.ToTable("RefreshTokens"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.Saml2PArtifact", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Expiration") + .HasColumnType("timestamp with time zone"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SessionId") + .HasColumnType("text"); + + b.Property("UserId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.ToTable("Saml2pArtifact"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.UserConsent", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Expiration") + .HasColumnType("timestamp with time zone"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SessionId") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.ToTable("UserConstents"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.UserSession", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("Expires") + .HasColumnType("timestamp with time zone"); + + b.Property("Renewed") + .HasColumnType("timestamp with time zone"); + + b.Property("Scheme") + .HasColumnType("text"); + + b.Property("SessionId") + .HasColumnType("text"); + + b.Property("Ticket") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("UserSessions"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("text"); + + b.Property("Xml") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("DataProtectionKeys"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/Migrations/OperationalDb/20231116205225_PushAuthorizationRequest.cs b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/Migrations/OperationalDb/20231116205225_PushAuthorizationRequest.cs new file mode 100644 index 000000000..7a02a978a --- /dev/null +++ b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/Migrations/OperationalDb/20231116205225_PushAuthorizationRequest.cs @@ -0,0 +1,37 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Aguacongas.TheIdServer.PostgreSQL.Migrations.OperationalDb +{ + /// + public partial class PushAuthorizationRequest : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "PushedAuthorizationRequests", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + ExpiresAtUtc = table.Column(type: "timestamp with time zone", nullable: false), + Parameters = table.Column(type: "text", nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + ModifiedAt = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PushedAuthorizationRequests", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "PushedAuthorizationRequests"); + } + } +} diff --git a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/Migrations/OperationalDb/OperationalDbContextModelSnapshot.cs b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/Migrations/OperationalDb/OperationalDbContextModelSnapshot.cs index aac2879fa..0a8d2a14f 100644 --- a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/Migrations/OperationalDb/OperationalDbContextModelSnapshot.cs +++ b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/Migrations/OperationalDb/OperationalDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "7.0.4") + .HasAnnotation("ProductVersion", "8.0.0") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -187,6 +187,28 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("OneTimeTokens"); }); + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.PushedAuthorizationRequest", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpiresAtUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Parameters") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("PushedAuthorizationRequests"); + }); + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ReferenceToken", b => { b.Property("Id") diff --git a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Aguacongas.TheIdServer.Migrations.SqlServer.csproj b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Aguacongas.TheIdServer.Migrations.SqlServer.csproj index fb42099cd..338e98a8b 100644 --- a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Aguacongas.TheIdServer.Migrations.SqlServer.csproj +++ b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Aguacongas.TheIdServer.Migrations.SqlServer.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Aguacongas.TheIdServer.SqlServer Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre @@ -16,7 +16,7 @@ - + diff --git a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Migrations/ConfigurationDb/20231116210305_PushAuthorizationRequest.Designer.cs b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Migrations/ConfigurationDb/20231116210305_PushAuthorizationRequest.Designer.cs new file mode 100644 index 000000000..58d9f4038 --- /dev/null +++ b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Migrations/ConfigurationDb/20231116210305_PushAuthorizationRequest.Designer.cs @@ -0,0 +1,1460 @@ +// +using System; +using Aguacongas.IdentityServer.EntityFramework.Store; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Aguacongas.TheIdServer.SqlServer.Migrations.ConfigurationDb +{ + [DbContext(typeof(ConfigurationDbContext))] + [Migration("20231116210305_PushAuthorizationRequest")] + partial class PushAuthorizationRequest + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiApiScope", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ApiId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("ApiScopeId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("ApiId"); + + b.HasIndex("ApiScopeId"); + + b.ToTable("ApiApiScope"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiClaim", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ApiId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.HasKey("Id"); + + b.HasIndex("ApiId", "Type") + .IsUnique(); + + b.ToTable("ApiClaims"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiLocalizedResource", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ApiId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CultureId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("ResourceKind") + .HasColumnType("int"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ApiId"); + + b.ToTable("ApiLocalizedResources"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiProperty", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ApiId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("Value") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.HasKey("Id"); + + b.HasIndex("ApiId", "Key") + .IsUnique(); + + b.ToTable("ApiProperty"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiScope", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Emphasize") + .HasColumnType("bit"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("Required") + .HasColumnType("bit"); + + b.Property("ShowInDiscoveryDocument") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.ToTable("ApiScopes"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiScopeClaim", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ApiScopeId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.HasKey("Id"); + + b.HasIndex("ApiScopeId", "Type") + .IsUnique(); + + b.ToTable("ApiScopeClaims"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiScopeLocalizedResource", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ApiScopeId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CultureId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("ResourceKind") + .HasColumnType("int"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ApiScopeId"); + + b.ToTable("ApiScopeLocalizedResources"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiScopeProperty", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ApiScopeId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("Value") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.HasKey("Id"); + + b.HasIndex("ApiScopeId", "Key") + .IsUnique(); + + b.ToTable("ApiScopeProperty"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiSecret", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ApiId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Expiration") + .HasColumnType("datetime2"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ApiId"); + + b.ToTable("ApiSecrets"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.Client", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AbsoluteRefreshTokenLifetime") + .HasColumnType("int"); + + b.Property("AccessTokenLifetime") + .HasColumnType("int"); + + b.Property("AccessTokenType") + .HasColumnType("int"); + + b.Property("AllowAccessTokensViaBrowser") + .HasColumnType("bit"); + + b.Property("AllowOfflineAccess") + .HasColumnType("bit"); + + b.Property("AllowPlainTextPkce") + .HasColumnType("bit"); + + b.Property("AllowRememberConsent") + .HasColumnType("bit"); + + b.Property("AlwaysIncludeUserClaimsInIdToken") + .HasColumnType("bit"); + + b.Property("AlwaysSendClientClaims") + .HasColumnType("bit"); + + b.Property("AuthorizationCodeLifetime") + .HasColumnType("int"); + + b.Property("BackChannelLogoutSessionRequired") + .HasColumnType("bit"); + + b.Property("BackChannelLogoutUri") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("CibaLifetime") + .HasColumnType("int"); + + b.Property("ClientClaimsPrefix") + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.Property("ClientName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ClientUri") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("ConsentLifetime") + .HasColumnType("int"); + + b.Property("CoordinateLifetimeWithUserSession") + .HasColumnType("bit"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("DeviceCodeLifetime") + .HasColumnType("int"); + + b.Property("EnableLocalLogin") + .HasColumnType("bit"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("FrontChannelLogoutSessionRequired") + .HasColumnType("bit"); + + b.Property("FrontChannelLogoutUri") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("IdentityTokenLifetime") + .HasColumnType("int"); + + b.Property("IncludeJwtId") + .HasColumnType("bit"); + + b.Property("LogoUri") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("NonEditable") + .HasColumnType("bit"); + + b.Property("PairWiseSubjectSalt") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PolicyUri") + .HasColumnType("nvarchar(max)"); + + b.Property("PollingInterval") + .HasColumnType("int"); + + b.Property("ProtocolType") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PushedAuthorizationLifetime") + .HasColumnType("int"); + + b.Property("RefreshTokenExpiration") + .HasColumnType("int"); + + b.Property("RefreshTokenUsage") + .HasColumnType("int"); + + b.Property("RegistrationToken") + .HasColumnType("uniqueidentifier"); + + b.Property("RelyingPartyId") + .HasColumnType("nvarchar(450)"); + + b.Property("RequireClientSecret") + .HasColumnType("bit"); + + b.Property("RequireConsent") + .HasColumnType("bit"); + + b.Property("RequireDPoP") + .HasColumnType("bit"); + + b.Property("RequirePkce") + .HasColumnType("bit"); + + b.Property("RequirePushedAuthorization") + .HasColumnType("bit"); + + b.Property("RequireRequestObject") + .HasColumnType("bit"); + + b.Property("SlidingRefreshTokenLifetime") + .HasColumnType("int"); + + b.Property("TosUri") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdateAccessTokenClaimsOnRefresh") + .HasColumnType("bit"); + + b.Property("UserCodeType") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("UserSsoLifetime") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RelyingPartyId"); + + b.ToTable("Clients"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientAllowedIdentityTokenSigningAlgorithm", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("Algorithm") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("ClientId", "Algorithm") + .IsUnique(); + + b.ToTable("ClientAllowedIdentityTokenSigningAlgorithms"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientClaim", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientClaims"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientGrantType", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("GrantType") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("ClientId", "GrantType") + .IsUnique(); + + b.ToTable("ClientGrantTypes"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientIdpRestriction", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("Provider") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId", "Provider") + .IsUnique(); + + b.ToTable("ClientIdpRestriction"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientLocalizedResource", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CultureId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("ResourceKind") + .HasColumnType("int"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientLocalizedResources"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientProperty", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("Value") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId", "Key") + .IsUnique(); + + b.ToTable("ClientProperties"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientScope", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId", "Scope") + .IsUnique(); + + b.ToTable("ClientScopes"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientSecret", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("Expiration") + .HasColumnType("datetime2"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientSecrets"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientUri", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Kind") + .HasColumnType("int"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("SanetizedCorsUri") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("Uri") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId", "Uri") + .IsUnique(); + + b.ToTable("ClientUris"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.Culture", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("Cultures"); + + b.HasData( + new + { + Id = "en", + CreatedAt = new DateTime(2023, 11, 16, 21, 3, 5, 62, DateTimeKind.Utc).AddTicks(2489) + }); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ExternalClaimTransformation", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AsMultipleValues") + .HasColumnType("bit"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("FromClaimType") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("Scheme") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("ToClaimType") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("Scheme", "FromClaimType") + .IsUnique(); + + b.ToTable("ExternalClaimTransformations"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ExternalProvider", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasColumnName("Scheme"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("MapDefaultOutboundClaimType") + .HasColumnType("bit"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("SerializedHandlerType") + .HasColumnType("nvarchar(max)"); + + b.Property("SerializedOptions") + .HasColumnType("nvarchar(max)"); + + b.Property("StoreClaims") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.ToTable("Providers"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.IdentityClaim", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("IdentityId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("Type") + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.HasKey("Id"); + + b.HasIndex("IdentityId", "Type") + .IsUnique() + .HasFilter("[Type] IS NOT NULL"); + + b.ToTable("IdentityClaims"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.IdentityLocalizedResource", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CultureId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IdentityId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("ResourceKind") + .HasColumnType("int"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("IdentityId"); + + b.ToTable("IdentityLocalizedResources"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.IdentityProperty", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("IdentityId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("Value") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.HasKey("Id"); + + b.HasIndex("IdentityId", "Key") + .IsUnique(); + + b.ToTable("IdentityProperties"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.IdentityResource", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Emphasize") + .HasColumnType("bit"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("NonEditable") + .HasColumnType("bit"); + + b.Property("Required") + .HasColumnType("bit"); + + b.Property("ShowInDiscoveryDocument") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.ToTable("Identities"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.LocalizedResource", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("BaseName") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CultureId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Key") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Location") + .HasColumnType("nvarchar(max)"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CultureId"); + + b.ToTable("LocalizedResources"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ProtectResource", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("NonEditable") + .HasColumnType("bit"); + + b.Property("RequireResourceIndicator") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.ToTable("Apis"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.RelyingParty", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("DigestAlgorithm") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("EncryptionCertificate") + .HasColumnType("varbinary(max)"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("SamlNameIdentifierFormat") + .HasColumnType("nvarchar(max)"); + + b.Property("SignatureAlgorithm") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("TokenType") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("RelyingParties"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.RelyingPartyClaimMapping", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("FromClaimType") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("RelyingPartyId") + .HasColumnType("nvarchar(450)"); + + b.Property("ToClaimType") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("RelyingPartyId"); + + b.ToTable("RelyingPartyClaimMappings"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiApiScope", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.ProtectResource", "Api") + .WithMany("ApiScopes") + .HasForeignKey("ApiId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Aguacongas.IdentityServer.Store.Entity.ApiScope", "ApiScope") + .WithMany("Apis") + .HasForeignKey("ApiScopeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Api"); + + b.Navigation("ApiScope"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiClaim", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.ProtectResource", "Api") + .WithMany("ApiClaims") + .HasForeignKey("ApiId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Api"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiLocalizedResource", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.ProtectResource", "Api") + .WithMany("Resources") + .HasForeignKey("ApiId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Api"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiProperty", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.ProtectResource", "Api") + .WithMany("Properties") + .HasForeignKey("ApiId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Api"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiScopeClaim", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.ApiScope", "ApiScope") + .WithMany("ApiScopeClaims") + .HasForeignKey("ApiScopeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApiScope"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiScopeLocalizedResource", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.ApiScope", "ApiScope") + .WithMany("Resources") + .HasForeignKey("ApiScopeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApiScope"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiScopeProperty", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.ApiScope", "ApiScope") + .WithMany("Properties") + .HasForeignKey("ApiScopeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApiScope"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiSecret", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.ProtectResource", "Api") + .WithMany("Secrets") + .HasForeignKey("ApiId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Api"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.Client", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.RelyingParty", "RelyingParty") + .WithMany("Clients") + .HasForeignKey("RelyingPartyId"); + + b.Navigation("RelyingParty"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientAllowedIdentityTokenSigningAlgorithm", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.Client", "Client") + .WithMany("AllowedIdentityTokenSigningAlgorithms") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientClaim", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.Client", "Client") + .WithMany("ClientClaims") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientGrantType", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.Client", "Client") + .WithMany("AllowedGrantTypes") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientIdpRestriction", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.Client", "Client") + .WithMany("IdentityProviderRestrictions") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientLocalizedResource", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.Client", "Client") + .WithMany("Resources") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientProperty", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.Client", "Client") + .WithMany("Properties") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientScope", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.Client", "Client") + .WithMany("AllowedScopes") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientSecret", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.Client", "Client") + .WithMany("ClientSecrets") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientUri", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.Client", "Client") + .WithMany("RedirectUris") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ExternalClaimTransformation", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.ExternalProvider", null) + .WithMany("ClaimTransformations") + .HasForeignKey("Scheme") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.IdentityClaim", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.IdentityResource", "Identity") + .WithMany("IdentityClaims") + .HasForeignKey("IdentityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Identity"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.IdentityLocalizedResource", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.IdentityResource", "Identity") + .WithMany("Resources") + .HasForeignKey("IdentityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Identity"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.IdentityProperty", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.IdentityResource", "Identity") + .WithMany("Properties") + .HasForeignKey("IdentityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Identity"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.LocalizedResource", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.Culture", "Culture") + .WithMany("Resources") + .HasForeignKey("CultureId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Culture"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.RelyingPartyClaimMapping", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.RelyingParty", "RelyingParty") + .WithMany("ClaimMappings") + .HasForeignKey("RelyingPartyId"); + + b.Navigation("RelyingParty"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiScope", b => + { + b.Navigation("ApiScopeClaims"); + + b.Navigation("Apis"); + + b.Navigation("Properties"); + + b.Navigation("Resources"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.Client", b => + { + b.Navigation("AllowedGrantTypes"); + + b.Navigation("AllowedIdentityTokenSigningAlgorithms"); + + b.Navigation("AllowedScopes"); + + b.Navigation("ClientClaims"); + + b.Navigation("ClientSecrets"); + + b.Navigation("IdentityProviderRestrictions"); + + b.Navigation("Properties"); + + b.Navigation("RedirectUris"); + + b.Navigation("Resources"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.Culture", b => + { + b.Navigation("Resources"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ExternalProvider", b => + { + b.Navigation("ClaimTransformations"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.IdentityResource", b => + { + b.Navigation("IdentityClaims"); + + b.Navigation("Properties"); + + b.Navigation("Resources"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ProtectResource", b => + { + b.Navigation("ApiClaims"); + + b.Navigation("ApiScopes"); + + b.Navigation("Properties"); + + b.Navigation("Resources"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.RelyingParty", b => + { + b.Navigation("ClaimMappings"); + + b.Navigation("Clients"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Migrations/ConfigurationDb/20231116210305_PushAuthorizationRequest.cs b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Migrations/ConfigurationDb/20231116210305_PushAuthorizationRequest.cs new file mode 100644 index 000000000..dcfa03f8a --- /dev/null +++ b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Migrations/ConfigurationDb/20231116210305_PushAuthorizationRequest.cs @@ -0,0 +1,40 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Aguacongas.TheIdServer.SqlServer.Migrations.ConfigurationDb +{ + /// + public partial class PushAuthorizationRequest : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "PushedAuthorizationLifetime", + table: "Clients", + type: "int", + nullable: true); + + migrationBuilder.AddColumn( + name: "RequirePushedAuthorization", + table: "Clients", + type: "bit", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "PushedAuthorizationLifetime", + table: "Clients"); + + migrationBuilder.DropColumn( + name: "RequirePushedAuthorization", + table: "Clients"); + } + } +} diff --git a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Migrations/ConfigurationDb/ConfigurationDbContextModelSnapshot.cs b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Migrations/ConfigurationDb/ConfigurationDbContextModelSnapshot.cs index 0e664b60f..a06ed1684 100644 --- a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Migrations/ConfigurationDb/ConfigurationDbContextModelSnapshot.cs +++ b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Migrations/ConfigurationDb/ConfigurationDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "7.0.5") + .HasAnnotation("ProductVersion", "8.0.0") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -426,6 +426,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(200) .HasColumnType("nvarchar(200)"); + b.Property("PushedAuthorizationLifetime") + .HasColumnType("int"); + b.Property("RefreshTokenExpiration") .HasColumnType("int"); @@ -450,6 +453,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("RequirePkce") .HasColumnType("bit"); + b.Property("RequirePushedAuthorization") + .HasColumnType("bit"); + b.Property("RequireRequestObject") .HasColumnType("bit"); @@ -773,7 +779,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) new { Id = "en", - CreatedAt = new DateTime(2023, 5, 20, 8, 25, 0, 710, DateTimeKind.Utc).AddTicks(6726) + CreatedAt = new DateTime(2023, 11, 16, 21, 3, 5, 62, DateTimeKind.Utc).AddTicks(2489) }); }); diff --git a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Migrations/OperationalDb/20231116204418_PushAuthorizationRequest.Designer.cs b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Migrations/OperationalDb/20231116204418_PushAuthorizationRequest.Designer.cs new file mode 100644 index 000000000..606c5ec9b --- /dev/null +++ b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Migrations/OperationalDb/20231116204418_PushAuthorizationRequest.Designer.cs @@ -0,0 +1,404 @@ +// +using System; +using Aguacongas.IdentityServer.EntityFramework.Store; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Aguacongas.TheIdServer.SqlServer.Migrations.OperationalDb +{ + [DbContext(typeof(OperationalDbContext))] + [Migration("20231116204418_PushAuthorizationRequest")] + partial class PushAuthorizationRequest + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Aguacongas.IdentityServer.KeysRotation.EntityFrameworkCore.KeyRotationKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("nvarchar(max)"); + + b.Property("Xml") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("KeyRotationKeys"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.AuthorizationCode", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Data") + .HasColumnType("nvarchar(max)"); + + b.Property("Expiration") + .HasColumnType("datetime2"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("SessionId") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.ToTable("AuthorizationCodes"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.BackChannelAuthenticationRequest", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Data") + .HasColumnType("nvarchar(max)"); + + b.Property("Expiration") + .HasColumnType("datetime2"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("SessionId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.ToTable("BackChannelAuthenticationRequests"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.DeviceCode", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Code") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Data") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Expiration") + .HasColumnType("datetime2"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("UserCode") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.ToTable("DeviceCodes"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.OneTimeToken", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Data") + .HasColumnType("nvarchar(max)"); + + b.Property("Expiration") + .HasColumnType("datetime2"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("SessionId") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.ToTable("OneTimeTokens"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.PushedAuthorizationRequest", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ExpiresAtUtc") + .HasColumnType("datetime2"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("Parameters") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("PushedAuthorizationRequests"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ReferenceToken", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Data") + .HasColumnType("nvarchar(max)"); + + b.Property("Expiration") + .HasColumnType("datetime2"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("SessionId") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.ToTable("ReferenceTokens"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.RefreshToken", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Data") + .HasColumnType("nvarchar(max)"); + + b.Property("Expiration") + .HasColumnType("datetime2"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("SessionId") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.ToTable("RefreshTokens"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.Saml2PArtifact", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Data") + .HasColumnType("nvarchar(max)"); + + b.Property("Expiration") + .HasColumnType("datetime2"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("SessionId") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.ToTable("Saml2pArtifact"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.UserConsent", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Data") + .HasColumnType("nvarchar(max)"); + + b.Property("Expiration") + .HasColumnType("datetime2"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("SessionId") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.ToTable("UserConstents"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.UserSession", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("Expires") + .HasColumnType("datetime2"); + + b.Property("Renewed") + .HasColumnType("datetime2"); + + b.Property("Scheme") + .HasColumnType("nvarchar(max)"); + + b.Property("SessionId") + .HasColumnType("nvarchar(max)"); + + b.Property("Ticket") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("UserSessions"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("nvarchar(max)"); + + b.Property("Xml") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("DataProtectionKeys"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Migrations/OperationalDb/20231116204418_PushAuthorizationRequest.cs b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Migrations/OperationalDb/20231116204418_PushAuthorizationRequest.cs new file mode 100644 index 000000000..c0d85a10a --- /dev/null +++ b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Migrations/OperationalDb/20231116204418_PushAuthorizationRequest.cs @@ -0,0 +1,37 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Aguacongas.TheIdServer.SqlServer.Migrations.OperationalDb +{ + /// + public partial class PushAuthorizationRequest : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "PushedAuthorizationRequests", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + ExpiresAtUtc = table.Column(type: "datetime2", nullable: false), + Parameters = table.Column(type: "nvarchar(max)", nullable: true), + CreatedAt = table.Column(type: "datetime2", nullable: false), + ModifiedAt = table.Column(type: "datetime2", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PushedAuthorizationRequests", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "PushedAuthorizationRequests"); + } + } +} diff --git a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Migrations/OperationalDb/OperationalDbContextModelSnapshot.cs b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Migrations/OperationalDb/OperationalDbContextModelSnapshot.cs index 9de9d78f3..2c5d3c2c7 100644 --- a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Migrations/OperationalDb/OperationalDbContextModelSnapshot.cs +++ b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Migrations/OperationalDb/OperationalDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "7.0.4") + .HasAnnotation("ProductVersion", "8.0.0") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -187,6 +187,28 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("OneTimeTokens"); }); + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.PushedAuthorizationRequest", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ExpiresAtUtc") + .HasColumnType("datetime2"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("Parameters") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("PushedAuthorizationRequests"); + }); + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ReferenceToken", b => { b.Property("Id") diff --git a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Aguacongas.TheIdServer.Migrations.Sqlite.csproj b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Aguacongas.TheIdServer.Migrations.Sqlite.csproj index a33db0261..e4cf118aa 100644 --- a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Aguacongas.TheIdServer.Migrations.Sqlite.csproj +++ b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Aguacongas.TheIdServer.Migrations.Sqlite.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Aguacongas.TheIdServer.Sqlite Olivier Lefebvre Copyright (c) 2020 @Olivier Lefebvre @@ -16,7 +16,7 @@ - + diff --git a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Migrations/ConfigurationDb/20231116210255_PushAuthorizationRequest.Designer.cs b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Migrations/ConfigurationDb/20231116210255_PushAuthorizationRequest.Designer.cs new file mode 100644 index 000000000..c077016c3 --- /dev/null +++ b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Migrations/ConfigurationDb/20231116210255_PushAuthorizationRequest.Designer.cs @@ -0,0 +1,1454 @@ +// +using System; +using Aguacongas.IdentityServer.EntityFramework.Store; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Aguacongas.TheIdServer.Sqlite.Migrations.ConfigurationDb +{ + [DbContext(typeof(ConfigurationDbContext))] + [Migration("20231116210255_PushAuthorizationRequest")] + partial class PushAuthorizationRequest + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiApiScope", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ApiId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ApiScopeId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ApiId"); + + b.HasIndex("ApiScopeId"); + + b.ToTable("ApiApiScope"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiClaim", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ApiId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ApiId", "Type") + .IsUnique(); + + b.ToTable("ApiClaims"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiLocalizedResource", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ApiId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CultureId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("ResourceKind") + .HasColumnType("INTEGER"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ApiId"); + + b.ToTable("ApiLocalizedResources"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiProperty", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ApiId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ApiId", "Key") + .IsUnique(); + + b.ToTable("ApiProperty"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiScope", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Emphasize") + .HasColumnType("INTEGER"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("Required") + .HasColumnType("INTEGER"); + + b.Property("ShowInDiscoveryDocument") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("ApiScopes"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiScopeClaim", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ApiScopeId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ApiScopeId", "Type") + .IsUnique(); + + b.ToTable("ApiScopeClaims"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiScopeLocalizedResource", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ApiScopeId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CultureId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("ResourceKind") + .HasColumnType("INTEGER"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ApiScopeId"); + + b.ToTable("ApiScopeLocalizedResources"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiScopeProperty", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ApiScopeId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ApiScopeId", "Key") + .IsUnique(); + + b.ToTable("ApiScopeProperty"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiSecret", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ApiId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Expiration") + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ApiId"); + + b.ToTable("ApiSecrets"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.Client", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AbsoluteRefreshTokenLifetime") + .HasColumnType("INTEGER"); + + b.Property("AccessTokenLifetime") + .HasColumnType("INTEGER"); + + b.Property("AccessTokenType") + .HasColumnType("INTEGER"); + + b.Property("AllowAccessTokensViaBrowser") + .HasColumnType("INTEGER"); + + b.Property("AllowOfflineAccess") + .HasColumnType("INTEGER"); + + b.Property("AllowPlainTextPkce") + .HasColumnType("INTEGER"); + + b.Property("AllowRememberConsent") + .HasColumnType("INTEGER"); + + b.Property("AlwaysIncludeUserClaimsInIdToken") + .HasColumnType("INTEGER"); + + b.Property("AlwaysSendClientClaims") + .HasColumnType("INTEGER"); + + b.Property("AuthorizationCodeLifetime") + .HasColumnType("INTEGER"); + + b.Property("BackChannelLogoutSessionRequired") + .HasColumnType("INTEGER"); + + b.Property("BackChannelLogoutUri") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("CibaLifetime") + .HasColumnType("INTEGER"); + + b.Property("ClientClaimsPrefix") + .HasMaxLength(250) + .HasColumnType("TEXT"); + + b.Property("ClientName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ClientUri") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("ConsentLifetime") + .HasColumnType("INTEGER"); + + b.Property("CoordinateLifetimeWithUserSession") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("DeviceCodeLifetime") + .HasColumnType("INTEGER"); + + b.Property("EnableLocalLogin") + .HasColumnType("INTEGER"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("FrontChannelLogoutSessionRequired") + .HasColumnType("INTEGER"); + + b.Property("FrontChannelLogoutUri") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("IdentityTokenLifetime") + .HasColumnType("INTEGER"); + + b.Property("IncludeJwtId") + .HasColumnType("INTEGER"); + + b.Property("LogoUri") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("NonEditable") + .HasColumnType("INTEGER"); + + b.Property("PairWiseSubjectSalt") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("PolicyUri") + .HasColumnType("TEXT"); + + b.Property("PollingInterval") + .HasColumnType("INTEGER"); + + b.Property("ProtocolType") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("PushedAuthorizationLifetime") + .HasColumnType("INTEGER"); + + b.Property("RefreshTokenExpiration") + .HasColumnType("INTEGER"); + + b.Property("RefreshTokenUsage") + .HasColumnType("INTEGER"); + + b.Property("RegistrationToken") + .HasColumnType("TEXT"); + + b.Property("RelyingPartyId") + .HasColumnType("TEXT"); + + b.Property("RequireClientSecret") + .HasColumnType("INTEGER"); + + b.Property("RequireConsent") + .HasColumnType("INTEGER"); + + b.Property("RequireDPoP") + .HasColumnType("INTEGER"); + + b.Property("RequirePkce") + .HasColumnType("INTEGER"); + + b.Property("RequirePushedAuthorization") + .HasColumnType("INTEGER"); + + b.Property("RequireRequestObject") + .HasColumnType("INTEGER"); + + b.Property("SlidingRefreshTokenLifetime") + .HasColumnType("INTEGER"); + + b.Property("TosUri") + .HasColumnType("TEXT"); + + b.Property("UpdateAccessTokenClaimsOnRefresh") + .HasColumnType("INTEGER"); + + b.Property("UserCodeType") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("UserSsoLifetime") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RelyingPartyId"); + + b.ToTable("Clients"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientAllowedIdentityTokenSigningAlgorithm", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Algorithm") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClientId", "Algorithm") + .IsUnique(); + + b.ToTable("ClientAllowedIdentityTokenSigningAlgorithms"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientClaim", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientClaims"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientGrantType", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GrantType") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClientId", "GrantType") + .IsUnique(); + + b.ToTable("ClientGrantTypes"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientIdpRestriction", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("Provider") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClientId", "Provider") + .IsUnique(); + + b.ToTable("ClientIdpRestriction"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientLocalizedResource", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CultureId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("ResourceKind") + .HasColumnType("INTEGER"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientLocalizedResources"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientProperty", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClientId", "Key") + .IsUnique(); + + b.ToTable("ClientProperties"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientScope", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClientId", "Scope") + .IsUnique(); + + b.ToTable("ClientScopes"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientSecret", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("Expiration") + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientSecrets"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientUri", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("SanetizedCorsUri") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("Uri") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClientId", "Uri") + .IsUnique(); + + b.ToTable("ClientUris"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.Culture", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Cultures"); + + b.HasData( + new + { + Id = "en", + CreatedAt = new DateTime(2023, 11, 16, 21, 2, 54, 734, DateTimeKind.Utc).AddTicks(8256) + }); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ExternalClaimTransformation", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AsMultipleValues") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("FromClaimType") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("Scheme") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ToClaimType") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Scheme", "FromClaimType") + .IsUnique(); + + b.ToTable("ExternalClaimTransformations"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ExternalProvider", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasColumnName("Scheme"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasColumnType("TEXT"); + + b.Property("MapDefaultOutboundClaimType") + .HasColumnType("INTEGER"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("SerializedHandlerType") + .HasColumnType("TEXT"); + + b.Property("SerializedOptions") + .HasColumnType("TEXT"); + + b.Property("StoreClaims") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Providers"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.IdentityClaim", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IdentityId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasMaxLength(250) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("IdentityId", "Type") + .IsUnique(); + + b.ToTable("IdentityClaims"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.IdentityLocalizedResource", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CultureId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IdentityId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("ResourceKind") + .HasColumnType("INTEGER"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("IdentityId"); + + b.ToTable("IdentityLocalizedResources"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.IdentityProperty", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IdentityId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("IdentityId", "Key") + .IsUnique(); + + b.ToTable("IdentityProperties"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.IdentityResource", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Emphasize") + .HasColumnType("INTEGER"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("NonEditable") + .HasColumnType("INTEGER"); + + b.Property("Required") + .HasColumnType("INTEGER"); + + b.Property("ShowInDiscoveryDocument") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Identities"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.LocalizedResource", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("BaseName") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CultureId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Location") + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CultureId"); + + b.ToTable("LocalizedResources"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ProtectResource", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("NonEditable") + .HasColumnType("INTEGER"); + + b.Property("RequireResourceIndicator") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Apis"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.RelyingParty", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("DigestAlgorithm") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("EncryptionCertificate") + .HasColumnType("BLOB"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("SamlNameIdentifierFormat") + .HasColumnType("TEXT"); + + b.Property("SignatureAlgorithm") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TokenType") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("RelyingParties"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.RelyingPartyClaimMapping", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("FromClaimType") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("RelyingPartyId") + .HasColumnType("TEXT"); + + b.Property("ToClaimType") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RelyingPartyId"); + + b.ToTable("RelyingPartyClaimMappings"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiApiScope", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.ProtectResource", "Api") + .WithMany("ApiScopes") + .HasForeignKey("ApiId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Aguacongas.IdentityServer.Store.Entity.ApiScope", "ApiScope") + .WithMany("Apis") + .HasForeignKey("ApiScopeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Api"); + + b.Navigation("ApiScope"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiClaim", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.ProtectResource", "Api") + .WithMany("ApiClaims") + .HasForeignKey("ApiId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Api"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiLocalizedResource", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.ProtectResource", "Api") + .WithMany("Resources") + .HasForeignKey("ApiId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Api"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiProperty", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.ProtectResource", "Api") + .WithMany("Properties") + .HasForeignKey("ApiId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Api"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiScopeClaim", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.ApiScope", "ApiScope") + .WithMany("ApiScopeClaims") + .HasForeignKey("ApiScopeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApiScope"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiScopeLocalizedResource", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.ApiScope", "ApiScope") + .WithMany("Resources") + .HasForeignKey("ApiScopeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApiScope"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiScopeProperty", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.ApiScope", "ApiScope") + .WithMany("Properties") + .HasForeignKey("ApiScopeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApiScope"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiSecret", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.ProtectResource", "Api") + .WithMany("Secrets") + .HasForeignKey("ApiId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Api"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.Client", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.RelyingParty", "RelyingParty") + .WithMany("Clients") + .HasForeignKey("RelyingPartyId"); + + b.Navigation("RelyingParty"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientAllowedIdentityTokenSigningAlgorithm", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.Client", "Client") + .WithMany("AllowedIdentityTokenSigningAlgorithms") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientClaim", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.Client", "Client") + .WithMany("ClientClaims") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientGrantType", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.Client", "Client") + .WithMany("AllowedGrantTypes") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientIdpRestriction", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.Client", "Client") + .WithMany("IdentityProviderRestrictions") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientLocalizedResource", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.Client", "Client") + .WithMany("Resources") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientProperty", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.Client", "Client") + .WithMany("Properties") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientScope", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.Client", "Client") + .WithMany("AllowedScopes") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientSecret", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.Client", "Client") + .WithMany("ClientSecrets") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ClientUri", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.Client", "Client") + .WithMany("RedirectUris") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ExternalClaimTransformation", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.ExternalProvider", null) + .WithMany("ClaimTransformations") + .HasForeignKey("Scheme") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.IdentityClaim", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.IdentityResource", "Identity") + .WithMany("IdentityClaims") + .HasForeignKey("IdentityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Identity"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.IdentityLocalizedResource", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.IdentityResource", "Identity") + .WithMany("Resources") + .HasForeignKey("IdentityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Identity"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.IdentityProperty", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.IdentityResource", "Identity") + .WithMany("Properties") + .HasForeignKey("IdentityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Identity"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.LocalizedResource", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.Culture", "Culture") + .WithMany("Resources") + .HasForeignKey("CultureId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Culture"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.RelyingPartyClaimMapping", b => + { + b.HasOne("Aguacongas.IdentityServer.Store.Entity.RelyingParty", "RelyingParty") + .WithMany("ClaimMappings") + .HasForeignKey("RelyingPartyId"); + + b.Navigation("RelyingParty"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiScope", b => + { + b.Navigation("ApiScopeClaims"); + + b.Navigation("Apis"); + + b.Navigation("Properties"); + + b.Navigation("Resources"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.Client", b => + { + b.Navigation("AllowedGrantTypes"); + + b.Navigation("AllowedIdentityTokenSigningAlgorithms"); + + b.Navigation("AllowedScopes"); + + b.Navigation("ClientClaims"); + + b.Navigation("ClientSecrets"); + + b.Navigation("IdentityProviderRestrictions"); + + b.Navigation("Properties"); + + b.Navigation("RedirectUris"); + + b.Navigation("Resources"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.Culture", b => + { + b.Navigation("Resources"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ExternalProvider", b => + { + b.Navigation("ClaimTransformations"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.IdentityResource", b => + { + b.Navigation("IdentityClaims"); + + b.Navigation("Properties"); + + b.Navigation("Resources"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ProtectResource", b => + { + b.Navigation("ApiClaims"); + + b.Navigation("ApiScopes"); + + b.Navigation("Properties"); + + b.Navigation("Resources"); + + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.RelyingParty", b => + { + b.Navigation("ClaimMappings"); + + b.Navigation("Clients"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Migrations/ConfigurationDb/20231116210255_PushAuthorizationRequest.cs b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Migrations/ConfigurationDb/20231116210255_PushAuthorizationRequest.cs new file mode 100644 index 000000000..68e90ca59 --- /dev/null +++ b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Migrations/ConfigurationDb/20231116210255_PushAuthorizationRequest.cs @@ -0,0 +1,40 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Aguacongas.TheIdServer.Sqlite.Migrations.ConfigurationDb +{ + /// + public partial class PushAuthorizationRequest : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "PushedAuthorizationLifetime", + table: "Clients", + type: "INTEGER", + nullable: true); + + migrationBuilder.AddColumn( + name: "RequirePushedAuthorization", + table: "Clients", + type: "INTEGER", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "PushedAuthorizationLifetime", + table: "Clients"); + + migrationBuilder.DropColumn( + name: "RequirePushedAuthorization", + table: "Clients"); + } + } +} diff --git a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Migrations/ConfigurationDb/ConfigurationDbContextModelSnapshot.cs b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Migrations/ConfigurationDb/ConfigurationDbContextModelSnapshot.cs index bd846b065..12befc86f 100644 --- a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Migrations/ConfigurationDb/ConfigurationDbContextModelSnapshot.cs +++ b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Migrations/ConfigurationDb/ConfigurationDbContextModelSnapshot.cs @@ -15,7 +15,7 @@ partial class ConfigurationDbContextModelSnapshot : ModelSnapshot protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "7.0.5"); + modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ApiApiScope", b => { @@ -421,6 +421,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(200) .HasColumnType("TEXT"); + b.Property("PushedAuthorizationLifetime") + .HasColumnType("INTEGER"); + b.Property("RefreshTokenExpiration") .HasColumnType("INTEGER"); @@ -445,6 +448,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("RequirePkce") .HasColumnType("INTEGER"); + b.Property("RequirePushedAuthorization") + .HasColumnType("INTEGER"); + b.Property("RequireRequestObject") .HasColumnType("INTEGER"); @@ -768,7 +774,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) new { Id = "en", - CreatedAt = new DateTime(2023, 5, 20, 8, 24, 50, 130, DateTimeKind.Utc).AddTicks(8146) + CreatedAt = new DateTime(2023, 11, 16, 21, 2, 54, 734, DateTimeKind.Utc).AddTicks(8256) }); }); diff --git a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Migrations/OperationalDb/20231116204408_PushAuthorizationRequest.Designer.cs b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Migrations/OperationalDb/20231116204408_PushAuthorizationRequest.Designer.cs new file mode 100644 index 000000000..148725294 --- /dev/null +++ b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Migrations/OperationalDb/20231116204408_PushAuthorizationRequest.Designer.cs @@ -0,0 +1,395 @@ +// +using System; +using Aguacongas.IdentityServer.EntityFramework.Store; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Aguacongas.TheIdServer.Sqlite.Migrations.OperationalDb +{ + [DbContext(typeof(OperationalDbContext))] + [Migration("20231116204408_PushAuthorizationRequest")] + partial class PushAuthorizationRequest + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); + + modelBuilder.Entity("Aguacongas.IdentityServer.KeysRotation.EntityFrameworkCore.KeyRotationKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FriendlyName") + .HasColumnType("TEXT"); + + b.Property("Xml") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("KeyRotationKeys"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.AuthorizationCode", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Expiration") + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("AuthorizationCodes"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.BackChannelAuthenticationRequest", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Expiration") + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("SessionId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("BackChannelAuthenticationRequests"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.DeviceCode", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Code") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Expiration") + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("UserCode") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("DeviceCodes"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.OneTimeToken", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Expiration") + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("OneTimeTokens"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.PushedAuthorizationRequest", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ExpiresAtUtc") + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("Parameters") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("PushedAuthorizationRequests"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ReferenceToken", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Expiration") + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("ReferenceTokens"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.RefreshToken", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Expiration") + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("RefreshTokens"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.Saml2PArtifact", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Expiration") + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Saml2pArtifact"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.UserConsent", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Expiration") + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("UserConstents"); + }); + + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.UserSession", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasColumnType("TEXT"); + + b.Property("Expires") + .HasColumnType("TEXT"); + + b.Property("Renewed") + .HasColumnType("TEXT"); + + b.Property("Scheme") + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasColumnType("TEXT"); + + b.Property("Ticket") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("UserSessions"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FriendlyName") + .HasColumnType("TEXT"); + + b.Property("Xml") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("DataProtectionKeys"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Migrations/OperationalDb/20231116204408_PushAuthorizationRequest.cs b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Migrations/OperationalDb/20231116204408_PushAuthorizationRequest.cs new file mode 100644 index 000000000..06e735b93 --- /dev/null +++ b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Migrations/OperationalDb/20231116204408_PushAuthorizationRequest.cs @@ -0,0 +1,37 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Aguacongas.TheIdServer.Sqlite.Migrations.OperationalDb +{ + /// + public partial class PushAuthorizationRequest : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "PushedAuthorizationRequests", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + ExpiresAtUtc = table.Column(type: "TEXT", nullable: false), + Parameters = table.Column(type: "TEXT", nullable: true), + CreatedAt = table.Column(type: "TEXT", nullable: false), + ModifiedAt = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PushedAuthorizationRequests", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "PushedAuthorizationRequests"); + } + } +} diff --git a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Migrations/OperationalDb/OperationalDbContextModelSnapshot.cs b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Migrations/OperationalDb/OperationalDbContextModelSnapshot.cs index ef18ebf77..04094a268 100644 --- a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Migrations/OperationalDb/OperationalDbContextModelSnapshot.cs +++ b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Migrations/OperationalDb/OperationalDbContextModelSnapshot.cs @@ -15,7 +15,7 @@ partial class OperationalDbContextModelSnapshot : ModelSnapshot protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "7.0.4"); + modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); modelBuilder.Entity("Aguacongas.IdentityServer.KeysRotation.EntityFrameworkCore.KeyRotationKey", b => { @@ -180,6 +180,28 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("OneTimeTokens"); }); + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.PushedAuthorizationRequest", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ExpiresAtUtc") + .HasColumnType("TEXT"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("Parameters") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("PushedAuthorizationRequests"); + }); + modelBuilder.Entity("Aguacongas.IdentityServer.Store.Entity.ReferenceToken", b => { b.Property("Id") diff --git a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.MySql.Startup/Aguacongas.TheIdServer.MySql.Startup.csproj b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.MySql.Startup/Aguacongas.TheIdServer.MySql.Startup.csproj index 2558c85fe..e6dbbd66b 100644 --- a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.MySql.Startup/Aguacongas.TheIdServer.MySql.Startup.csproj +++ b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.MySql.Startup/Aguacongas.TheIdServer.MySql.Startup.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 Aguacongas.TheIdServer.MySql Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre @@ -16,8 +16,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Oracle.Startup/Aguacongas.TheIdServer.Oracle.Startup.csproj b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Oracle.Startup/Aguacongas.TheIdServer.Oracle.Startup.csproj index ffed5fa60..64eaf5554 100644 --- a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Oracle.Startup/Aguacongas.TheIdServer.Oracle.Startup.csproj +++ b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Oracle.Startup/Aguacongas.TheIdServer.Oracle.Startup.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 Aguacongas.TheIdServer.Oracle Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre @@ -18,8 +18,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.PostgreSQL.Startup/Aguacongas.TheIdServer.PostgreSQL.Startup.csproj b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.PostgreSQL.Startup/Aguacongas.TheIdServer.PostgreSQL.Startup.csproj index 239b64425..2eb9b863b 100644 --- a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.PostgreSQL.Startup/Aguacongas.TheIdServer.PostgreSQL.Startup.csproj +++ b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.PostgreSQL.Startup/Aguacongas.TheIdServer.PostgreSQL.Startup.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 Aguacongas.TheIdServer.PostgreSQL Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre @@ -16,8 +16,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.SqlServer.Startup/Aguacongas.TheIdServer.SqlServer.Startup.csproj b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.SqlServer.Startup/Aguacongas.TheIdServer.SqlServer.Startup.csproj index 8890b2d89..b601a1a0c 100644 --- a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.SqlServer.Startup/Aguacongas.TheIdServer.SqlServer.Startup.csproj +++ b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.SqlServer.Startup/Aguacongas.TheIdServer.SqlServer.Startup.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 Aguacongas.TheIdServer.SqlServer Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre @@ -16,8 +16,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Sqlite.Startup/Aguacongas.TheIdServer.Sqlite.Startup.csproj b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Sqlite.Startup/Aguacongas.TheIdServer.Sqlite.Startup.csproj index f37637f5b..7f47ddf79 100644 --- a/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Sqlite.Startup/Aguacongas.TheIdServer.Sqlite.Startup.csproj +++ b/src/IdentityServer/Migrations/Aguacongas.TheIdServer.Sqlite.Startup/Aguacongas.TheIdServer.Sqlite.Startup.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 Aguacongas.TheIdServer.Sqlite Olivier Lefebvre Copyright (c) 2023 @Olivier Lefebvre @@ -16,8 +16,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Aguacongas.IdentityServer.Admin.Http.Store.Test/Aguacongas.IdentityServer.Admin.Http.Store.Test.csproj b/test/Aguacongas.IdentityServer.Admin.Http.Store.Test/Aguacongas.IdentityServer.Admin.Http.Store.Test.csproj index d0eb9f2f3..67308b15d 100644 --- a/test/Aguacongas.IdentityServer.Admin.Http.Store.Test/Aguacongas.IdentityServer.Admin.Http.Store.Test.csproj +++ b/test/Aguacongas.IdentityServer.Admin.Http.Store.Test/Aguacongas.IdentityServer.Admin.Http.Store.Test.csproj @@ -1,12 +1,12 @@  - net7.0 + net8.0 - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Aguacongas.TheIdServer.BlazorApp.Test/Aguacongas.TheIdServer.BlazorApp.Test.csproj b/test/Aguacongas.TheIdServer.BlazorApp.Test/Aguacongas.TheIdServer.BlazorApp.Test.csproj index 8f1f949fd..28b2e5409 100644 --- a/test/Aguacongas.TheIdServer.BlazorApp.Test/Aguacongas.TheIdServer.BlazorApp.Test.csproj +++ b/test/Aguacongas.TheIdServer.BlazorApp.Test/Aguacongas.TheIdServer.BlazorApp.Test.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 false @@ -15,9 +15,9 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Aguacongas.TheIdServer.Test/Aguacongas.TheIdServer.Test.csproj b/test/Aguacongas.TheIdServer.Test/Aguacongas.TheIdServer.Test.csproj index 1dbdd12d0..1c60bbe3b 100644 --- a/test/Aguacongas.TheIdServer.Test/Aguacongas.TheIdServer.Test.csproj +++ b/test/Aguacongas.TheIdServer.Test/Aguacongas.TheIdServer.Test.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 enable false @@ -13,8 +13,8 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/test/Aguacongas.TheIdServer.Test/Extensions/TracerProviderBuilderExtensionsTest.cs b/test/Aguacongas.TheIdServer.Test/Extensions/TracerProviderBuilderExtensionsTest.cs index 794c86a22..b659aa3dc 100644 --- a/test/Aguacongas.TheIdServer.Test/Extensions/TracerProviderBuilderExtensionsTest.cs +++ b/test/Aguacongas.TheIdServer.Test/Extensions/TracerProviderBuilderExtensionsTest.cs @@ -88,8 +88,7 @@ public void AddTheIdServerTelemetry_should_set_up_instrumentation() }, HttpClient = new HttpClientInstrumentationOptions { - RecordException = true, - SetHttpFlavor = true + RecordException = true }, Redis = new RedisOptions { diff --git a/test/Duende/Aguacongas.IdentityServer.Admin.Duende.Test/Aguacongas.IdentityServer.Admin.Duende.Test.csproj b/test/Duende/Aguacongas.IdentityServer.Admin.Duende.Test/Aguacongas.IdentityServer.Admin.Duende.Test.csproj index 4e983f2ad..642d68068 100644 --- a/test/Duende/Aguacongas.IdentityServer.Admin.Duende.Test/Aguacongas.IdentityServer.Admin.Duende.Test.csproj +++ b/test/Duende/Aguacongas.IdentityServer.Admin.Duende.Test/Aguacongas.IdentityServer.Admin.Duende.Test.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 false True @@ -16,11 +16,11 @@ - - - + + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Duende/Aguacongas.IdentityServer.Admin.Duende.Test/Services/WindowsAuthentication/WindowsHandlerTest.cs b/test/Duende/Aguacongas.IdentityServer.Admin.Duende.Test/Services/WindowsAuthentication/WindowsHandlerTest.cs index 9c1103426..f7fce0fad 100644 --- a/test/Duende/Aguacongas.IdentityServer.Admin.Duende.Test/Services/WindowsAuthentication/WindowsHandlerTest.cs +++ b/test/Duende/Aguacongas.IdentityServer.Admin.Duende.Test/Services/WindowsAuthentication/WindowsHandlerTest.cs @@ -42,12 +42,10 @@ private static async Task InitializeSut() var loggerFactoryMock = new Mock(); var urlEncoderMock = new Mock(); - var systemClockMock = new Mock(); var sut = new WindowsHandler(optionsMonitorMock.Object, loggerFactoryMock.Object, - urlEncoderMock.Object, - systemClockMock.Object); + urlEncoderMock.Object); var httpRequestMock = new Mock(); var httpContextMock = new Mock(); diff --git a/test/Duende/Aguacongas.IdentityServer.Duende.Test/Aguacongas.IdentityServer.Duende.Test.csproj b/test/Duende/Aguacongas.IdentityServer.Duende.Test/Aguacongas.IdentityServer.Duende.Test.csproj index be57eddd1..fcd4b8376 100644 --- a/test/Duende/Aguacongas.IdentityServer.Duende.Test/Aguacongas.IdentityServer.Duende.Test.csproj +++ b/test/Duende/Aguacongas.IdentityServer.Duende.Test/Aguacongas.IdentityServer.Duende.Test.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable True false @@ -16,9 +16,9 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/test/Duende/Aguacongas.IdentityServer.EntityFramework.Store.Duende.Test/Aguacongas.IdentityServer.EntityFramework.Store.Duende.Test.csproj b/test/Duende/Aguacongas.IdentityServer.EntityFramework.Store.Duende.Test/Aguacongas.IdentityServer.EntityFramework.Store.Duende.Test.csproj index 3b506fc69..4ddf85ce1 100644 --- a/test/Duende/Aguacongas.IdentityServer.EntityFramework.Store.Duende.Test/Aguacongas.IdentityServer.EntityFramework.Store.Duende.Test.csproj +++ b/test/Duende/Aguacongas.IdentityServer.EntityFramework.Store.Duende.Test/Aguacongas.IdentityServer.EntityFramework.Store.Duende.Test.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 false @@ -17,13 +17,13 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Duende/Aguacongas.IdentityServer.EntityFramework.Store.Duende.Test/PushedAuthorizationRequestStoreTest.cs b/test/Duende/Aguacongas.IdentityServer.EntityFramework.Store.Duende.Test/PushedAuthorizationRequestStoreTest.cs new file mode 100644 index 000000000..6c191a8ba --- /dev/null +++ b/test/Duende/Aguacongas.IdentityServer.EntityFramework.Store.Duende.Test/PushedAuthorizationRequestStoreTest.cs @@ -0,0 +1,98 @@ +using Aguacongas.IdentityServer.Abstractions; +using Aguacongas.IdentityServer.Store; +using Aguacongas.IdentityServer.Store.Entity; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using System; +using System.Threading.Tasks; +using Xunit; +using ISConfiguration = Duende.IdentityServer.Configuration; +using ISModels = Duende.IdentityServer.Models; + +namespace Aguacongas.IdentityServer.EntityFramework.Store.Duende.Test; +public class PushedAuthorizationRequestStoreTest +{ + [Fact] + public async Task ConsumeByHashAsync_should_delete_row() + { + CreateSut(out var _, out var context, out var sut); + + var id = GenerateId(); + await context.AddAsync(new PushedAuthorizationRequest + { + Id = id, + ExpiresAtUtc = DateTime.Now, + Parameters = GenerateId() + }); + await context.SaveChangesAsync(); + + await sut.ConsumeByHashAsync(id); + + var deleted = await context.PushedAuthorizationRequests.FirstOrDefaultAsync(par => par.Id == id); + + Assert.Null(deleted); + } + + [Fact] + public async Task GetByHashAsync_should_return_the_par() + { + CreateSut(out var _, out var context, out var sut); + + var id = GenerateId(); + await context.AddAsync(new PushedAuthorizationRequest + { + Id = id, + ExpiresAtUtc = DateTime.Now, + Parameters = GenerateId() + }); + await context.SaveChangesAsync(); + + var result = await sut.GetByHashAsync(id); + + Assert.NotNull(result); + + Assert.Null(await sut.GetByHashAsync(GenerateId())); + } + + [Fact] + public async Task StoreAsync_should_store_the_par() + { + CreateSut(out var _, out var context, out var sut); + + var id = GenerateId(); + + await sut.StoreAsync(new ISModels.PushedAuthorizationRequest + { + ExpiresAtUtc = DateTime.UtcNow, + Parameters = GenerateId(), + ReferenceValueHash = id + }); + + var stored = await context.PushedAuthorizationRequests.FirstOrDefaultAsync(par => par.Id == id); + + Assert.NotNull(stored); + } + + private static void CreateSut(out IServiceScope scope, out OperationalDbContext context, out PushedAuthorizationRequestStore sut) + { + var provider = new ServiceCollection() + .AddLogging() + .Configure(options => { }) + .Configure(options => { }) + .AddTransient(p => p.GetRequiredService>().Value) + .AddScoped(typeof(IFlushableCache<>), typeof(FlushableCache<>)) + .AddOperationalStores() + .AddOperationalEntityFrameworkStores(options => + options.UseInMemoryDatabase(GenerateId())) + .BuildServiceProvider(); + scope = provider.CreateScope(); + var serviceProvider = scope.ServiceProvider; + context = serviceProvider.GetRequiredService(); + sut = new PushedAuthorizationRequestStore(serviceProvider.GetRequiredService>()); + } + private static string GenerateId() + => Guid.NewGuid().ToString(); + +} diff --git a/test/Duende/Aguacongas.IdentityServer.Http.Store.Duende.Test/Aguacongas.IdentityServer.Http.Store.Duende.Test.csproj b/test/Duende/Aguacongas.IdentityServer.Http.Store.Duende.Test/Aguacongas.IdentityServer.Http.Store.Duende.Test.csproj index 0aff8547d..f50752f3c 100644 --- a/test/Duende/Aguacongas.IdentityServer.Http.Store.Duende.Test/Aguacongas.IdentityServer.Http.Store.Duende.Test.csproj +++ b/test/Duende/Aguacongas.IdentityServer.Http.Store.Duende.Test/Aguacongas.IdentityServer.Http.Store.Duende.Test.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 True false @@ -15,10 +15,10 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Duende/Aguacongas.IdentityServer.KeysRotation.Duende.Test/Aguacongas.IdentityServer.KeysRotation.Duende.Test.csproj b/test/Duende/Aguacongas.IdentityServer.KeysRotation.Duende.Test/Aguacongas.IdentityServer.KeysRotation.Duende.Test.csproj index 75a5aba75..2c9e9a262 100644 --- a/test/Duende/Aguacongas.IdentityServer.KeysRotation.Duende.Test/Aguacongas.IdentityServer.KeysRotation.Duende.Test.csproj +++ b/test/Duende/Aguacongas.IdentityServer.KeysRotation.Duende.Test/Aguacongas.IdentityServer.KeysRotation.Duende.Test.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 false True @@ -16,10 +16,10 @@ - - + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Duende/Aguacongas.IdentityServer.MongoDb.Store.Duende.Test/Aguacongas.IdentityServer.MongoDb.Store.Duende.Test.csproj b/test/Duende/Aguacongas.IdentityServer.MongoDb.Store.Duende.Test/Aguacongas.IdentityServer.MongoDb.Store.Duende.Test.csproj index 06124e963..4dbe60353 100644 --- a/test/Duende/Aguacongas.IdentityServer.MongoDb.Store.Duende.Test/Aguacongas.IdentityServer.MongoDb.Store.Duende.Test.csproj +++ b/test/Duende/Aguacongas.IdentityServer.MongoDb.Store.Duende.Test/Aguacongas.IdentityServer.MongoDb.Store.Duende.Test.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 True false @@ -15,9 +15,9 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/test/Duende/Aguacongas.IdentityServer.RavenDb.Store.Duende.Test/Aguacongas.IdentityServer.RavenDb.Store.Duende.Test.csproj b/test/Duende/Aguacongas.IdentityServer.RavenDb.Store.Duende.Test/Aguacongas.IdentityServer.RavenDb.Store.Duende.Test.csproj index bdf53bf37..8fce150f9 100644 --- a/test/Duende/Aguacongas.IdentityServer.RavenDb.Store.Duende.Test/Aguacongas.IdentityServer.RavenDb.Store.Duende.Test.csproj +++ b/test/Duende/Aguacongas.IdentityServer.RavenDb.Store.Duende.Test/Aguacongas.IdentityServer.RavenDb.Store.Duende.Test.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 false @@ -17,11 +17,11 @@ - - + + - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/test/Duende/Aguacongas.IdentityServer.Saml2P.Duende.Test/Aguacongas.IdentityServer.Saml2P.Duende.Test.csproj b/test/Duende/Aguacongas.IdentityServer.Saml2P.Duende.Test/Aguacongas.IdentityServer.Saml2P.Duende.Test.csproj index 239578c74..90b6130ba 100644 --- a/test/Duende/Aguacongas.IdentityServer.Saml2P.Duende.Test/Aguacongas.IdentityServer.Saml2P.Duende.Test.csproj +++ b/test/Duende/Aguacongas.IdentityServer.Saml2P.Duende.Test/Aguacongas.IdentityServer.Saml2P.Duende.Test.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 enable enable @@ -11,9 +11,9 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/test/Duende/Aguacongas.TheIdServer.Authentication.Integration.Duende.Test/Aguacongas.TheIdServer.Authentication.Integration.Duende.Test.csproj b/test/Duende/Aguacongas.TheIdServer.Authentication.Integration.Duende.Test/Aguacongas.TheIdServer.Authentication.Integration.Duende.Test.csproj index 227008552..c553f3b20 100644 --- a/test/Duende/Aguacongas.TheIdServer.Authentication.Integration.Duende.Test/Aguacongas.TheIdServer.Authentication.Integration.Duende.Test.csproj +++ b/test/Duende/Aguacongas.TheIdServer.Authentication.Integration.Duende.Test/Aguacongas.TheIdServer.Authentication.Integration.Duende.Test.csproj @@ -1,8 +1,8 @@  - net7.0 - + net8.0 + true false True @@ -16,14 +16,15 @@ - - - - - - - - + + + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Duende/Aguacongas.TheIdServer.Authentication.Integration.Duende.Test/TestUtils.cs b/test/Duende/Aguacongas.TheIdServer.Authentication.Integration.Duende.Test/TestUtils.cs deleted file mode 100644 index 67bed3859..000000000 --- a/test/Duende/Aguacongas.TheIdServer.Authentication.Integration.Duende.Test/TestUtils.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Project: Aguafrommars/TheIdServer -// Copyright (c) 2023 @Olivier Lefebvre -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Serilog; -using System; -using System.Collections.Generic; -using System.IO; - -namespace Aguacongas.TheIdServer.Authentication.IntegrationTest -{ - public static class TestUtils - { - - public static TestServer CreateTestServer( - Action configureServices = null, - IEnumerable> configurationOverrides = null) - { -#pragma warning disable CS0618 // Type or member is obsolete - var webHostBuilder = new WebHostBuilder() - .UseEnvironment("Development") - .UseSerilog((hostingContext, loggerConfiguration) => loggerConfiguration - .ReadFrom.Configuration(hostingContext.Configuration)) - .ConfigureServices((context, services) => - { - var configurationManager = new ConfigurationManager(); - configurationManager.AddJsonFile(Path.Combine(Environment.CurrentDirectory, @"..\..\..\..\..\..\src\Aguacongas.TheIdServer.Duende\appsettings.json")); - configurationManager.AddJsonFile(Path.Combine(Environment.CurrentDirectory, @"appsettings.Test.json"), true); - if (configurationManager != null) - { - configurationManager.AddInMemoryCollection(configurationOverrides); - } - - configureServices?.Invoke(services); - services.AddTheIdServer(configurationManager); - services.AddSingleton() - .AddMvc().AddApplicationPart(typeof(Config).Assembly); - configureServices?.Invoke(services); - }) - .Configure((context, builder) => - { - builder.Use(async (context, next) => - { - var testService = context.RequestServices.GetRequiredService(); - context.User = testService.User; - await next(); - }); - builder.UseTheIdServer(context.HostingEnvironment, context.Configuration); - }); -#pragma warning restore CS0618 // Type or member is obsolete - - var testServer = new TestServer(webHostBuilder); - - return testServer; - } - - } -} diff --git a/test/Duende/Aguacongas.TheIdServer.Authentication.Integration.Duende.Test/TheIdServerTestFixture.cs b/test/Duende/Aguacongas.TheIdServer.Authentication.Integration.Duende.Test/TheIdServerTestFixture.cs index 9c698922a..cc3ca7a37 100644 --- a/test/Duende/Aguacongas.TheIdServer.Authentication.Integration.Duende.Test/TheIdServerTestFixture.cs +++ b/test/Duende/Aguacongas.TheIdServer.Authentication.Integration.Duende.Test/TheIdServerTestFixture.cs @@ -3,15 +3,21 @@ using Aguacongas.AspNetCore.Authentication; using Aguacongas.IdentityServer.EntityFramework.Store; using Aguacongas.TheIdServer.Data; +using Aguacongas.TheIdServer.UI; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.WsFederation; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.TestHost; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using System; using System.Collections.Generic; +using System.IO; namespace Aguacongas.TheIdServer.Authentication.IntegrationTest { @@ -25,8 +31,14 @@ public class TheIdServerTestFixture public TheIdServerTestFixture() { var dbName = Guid.NewGuid().ToString(); - Sut = TestUtils.CreateTestServer( - services => + var factory = new WebApplicationFactory().WithWebHostBuilder(webHostBuilder => + { + webHostBuilder.ConfigureAppConfiguration(configurationManager => + { + configurationManager.AddJsonFile(Path.Combine(Environment.CurrentDirectory, @"..\..\..\..\..\..\src\Aguacongas.TheIdServer.Duende\appsettings.json")); + configurationManager.AddJsonFile(Path.Combine(Environment.CurrentDirectory, @"appsettings.Test.json"), true); + }) + .ConfigureTestServices(services => { services.AddTheIdServerAdminEntityFrameworkStores(options => options.UseInMemoryDatabase(dbName)) @@ -42,7 +54,7 @@ public TheIdServerTestFixture() provider.GetRequiredService>>(), (name, configure) => { - + } ) ) @@ -68,9 +80,24 @@ public TheIdServerTestFixture() } ) ); + + services.AddSingleton() + .AddMvc().AddApplicationPart(typeof(Config).Assembly); + }) + .Configure((context, builder) => + { + builder.Use(async (context, next) => + { + var testService = context.RequestServices.GetRequiredService(); + context.User = testService.User; + await next(); + }); + builder.UseTheIdServer(context.HostingEnvironment, context.Configuration); }); + }); + Sut = factory.Server; - using var scope = Sut.Host.Services.CreateScope(); + using var scope = factory.Services.CreateScope(); using var identityContext = scope.ServiceProvider.GetRequiredService(); identityContext.Database.EnsureCreated(); using var operationalContext = scope.ServiceProvider.GetRequiredService(); diff --git a/test/Duende/Aguacongas.TheIdServer.Duende.Test/Aguacongas.TheIdServer.Duende.Test.csproj b/test/Duende/Aguacongas.TheIdServer.Duende.Test/Aguacongas.TheIdServer.Duende.Test.csproj index 9c0bade4f..78ee33427 100644 --- a/test/Duende/Aguacongas.TheIdServer.Duende.Test/Aguacongas.TheIdServer.Duende.Test.csproj +++ b/test/Duende/Aguacongas.TheIdServer.Duende.Test/Aguacongas.TheIdServer.Duende.Test.csproj @@ -1,9 +1,9 @@  - net7.0 - - false + net8.0 + true + false True @@ -24,11 +24,12 @@ - - + + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Duende/Aguacongas.TheIdServer.Duende.Test/StartupTest.cs b/test/Duende/Aguacongas.TheIdServer.Duende.Test/StartupTest.cs index 828d55f5d..bc2878682 100644 --- a/test/Duende/Aguacongas.TheIdServer.Duende.Test/StartupTest.cs +++ b/test/Duende/Aguacongas.TheIdServer.Duende.Test/StartupTest.cs @@ -4,22 +4,20 @@ using Aguacongas.IdentityServer.Abstractions; using Aguacongas.IdentityServer.Admin.Configuration; using Aguacongas.IdentityServer.Admin.Services; -using Aguacongas.IdentityServer.Store; -using Aguacongas.IdentityServer.Store.Entity; using Aguacongas.TheIdServer.Admin.Hubs; using Aguacongas.TheIdServer.Authentication; using Aguacongas.TheIdServer.Models; -using Microsoft.AspNetCore; +using Aguacongas.TheIdServer.UI; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; using Microsoft.AspNetCore.DataProtection.Repositories; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR.StackExchangeRedis; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Moq; -using Serilog; using System; using System.Collections.Generic; using System.Linq; @@ -107,628 +105,614 @@ public void ConfigureServices_should_configure_signalR() [InlineData(DbTypes.SqlServer)] public void UseDatabaseFromConfiguration_should_configure_context_per_db_type(DbTypes dbTypes) { - var configuration = new ConfigurationManager(); - configuration.AddInMemoryCollection(new Dictionary - { - ["ConnectionStrings:DefaultConnection"] = "invalid", - ["DbType"] = dbTypes.ToString(), - ["Migrate"] = "true", - }); var environementMock = new Mock(); var storeMock = new Mock>(); storeMock.SetupGet(m => m.SchemeDefinitions).Returns(Array.Empty().AsQueryable()).Verifiable(); -#pragma warning disable CS0618 // Type or member is obsolete - using var host = WebHost.CreateDefaultBuilder() - .ConfigureServices(services => + var factory = new WebApplicationFactory() + .WithWebHostBuilder(webHostBuilder => { - services.AddTheIdServer(configuration); - services.AddTransient(p => storeMock.Object); - }) - .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)) - .UseSerilog((hostingContext, configuration) => - configuration.ReadFrom.Configuration(hostingContext.Configuration)) - .Build(); -#pragma warning restore CS0618 // Type or member is obsolete + webHostBuilder.ConfigureAppConfiguration(configuration => + { + configuration.AddInMemoryCollection(new Dictionary + { + ["ConnectionStrings:DefaultConnection"] = "invalid", + ["DbType"] = dbTypes.ToString(), + ["Migrate"] = "true", + }); + }) + .ConfigureServices(services => + { + services.AddTransient(p => storeMock.Object); + }) + .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)); + }); + if (dbTypes != DbTypes.InMemory) { - Assert.ThrowsAny(() => host.Start()); + Assert.ThrowsAny(() => factory.CreateClient()); } } [Fact] public void Configure_should_configure_data_protection_azure_storage() { - var configuration = new ConfigurationManager(); - configuration.AddInMemoryCollection(new Dictionary - { - ["DataProtectionOptions:StorageKind"] = StorageKind.AzureStorage.ToString(), - ["DataProtectionOptions:StorageConnectionString"] = "https://md-3r0d4kzc5jhz.blob.core.windows.net/s3vffgdlczdj/abcd?sv=2017-04-17&sr=b&si=e931bb4b-8a79-4119-b4bb-8b2c1b763369&sig=SIGNATURE_WILL_BE_HERE" - }); var environementMock = new Mock(); var storeMock = new Mock>(); storeMock.SetupGet(m => m.SchemeDefinitions).Returns(Array.Empty().AsQueryable()).Verifiable(); -#pragma warning disable CS0618 // Type or member is obsolete - using var host = WebHost.CreateDefaultBuilder() - .ConfigureServices(services => + var factory = new WebApplicationFactory() + .WithWebHostBuilder(webHostBuilder => { - services.AddTheIdServer(configuration); - services.AddTransient(p => storeMock.Object); - }) - .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)) - .UseSerilog((hostingContext, configuration) => - configuration.ReadFrom.Configuration(hostingContext.Configuration)) - .Build(); -#pragma warning restore CS0618 // Type or member is obsolete - - Assert.Null(host.Services.GetService()); + webHostBuilder.ConfigureAppConfiguration(configuration => + { + configuration.AddInMemoryCollection(new Dictionary + { + ["DataProtectionOptions:StorageKind"] = StorageKind.AzureStorage.ToString(), + ["DataProtectionOptions:StorageConnectionString"] = "https://md-3r0d4kzc5jhz.blob.core.windows.net/s3vffgdlczdj/abcd?sv=2017-04-17&sr=b&si=e931bb4b-8a79-4119-b4bb-8b2c1b763369&sig=SIGNATURE_WILL_BE_HERE" + }); + }) + .ConfigureServices(services => + { + services.AddTransient(p => storeMock.Object); + }) + .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)); + }); + + + Assert.Null(factory.Services.GetService()); } [Fact] public void Configure_should_configure_data_protection_ef_storage() { - var configuration = new ConfigurationManager(); - configuration.AddInMemoryCollection(new Dictionary - { - ["DataProtectionOptions:StorageKind"] = StorageKind.EntityFramework.ToString() - }); var environementMock = new Mock(); var storeMock = new Mock>(); storeMock.SetupGet(m => m.SchemeDefinitions).Returns(Array.Empty().AsQueryable()).Verifiable(); -#pragma warning disable CS0618 // Type or member is obsolete - using var host = WebHost.CreateDefaultBuilder() - .ConfigureServices(services => + var factory = new WebApplicationFactory() + .WithWebHostBuilder(webHostBuilder => { - services.AddTheIdServer(configuration); - services.AddTransient(p => storeMock.Object); - }) - .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)) - .UseSerilog((hostingContext, configuration) => - configuration.ReadFrom.Configuration(hostingContext.Configuration)) - .Build(); -#pragma warning restore CS0618 // Type or member is obsolete - - Assert.Null(host.Services.GetService()); + webHostBuilder.ConfigureAppConfiguration(configuration => + { + configuration.AddInMemoryCollection(new Dictionary + { + ["DataProtectionOptions:StorageKind"] = StorageKind.EntityFramework.ToString() + }); + }) + .ConfigureServices(services => + { + services.AddTransient(p => storeMock.Object); + }) + .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)); + }); + + Assert.Null(factory.Services.GetService()); } [Fact] public void Configure_should_configure_data_protection_fs_storage() { - var configuration = new ConfigurationManager(); - configuration.AddInMemoryCollection(new Dictionary - { - ["DataProtectionOptions:StorageKind"] = StorageKind.FileSystem.ToString(), - ["DataProtectionOptions:StorageConnectionString"] = @"C:\test" - }); var environementMock = new Mock(); var storeMock = new Mock>(); storeMock.SetupGet(m => m.SchemeDefinitions).Returns(Array.Empty().AsQueryable()).Verifiable(); -#pragma warning disable CS0618 // Type or member is obsolete - using var host = WebHost.CreateDefaultBuilder() - .ConfigureServices(services => + var factory = new WebApplicationFactory() + .WithWebHostBuilder(webHostBuilder => { - services.AddTheIdServer(configuration); - services.AddTransient(p => storeMock.Object); - }) - .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)) - .UseSerilog((hostingContext, configuration) => - configuration.ReadFrom.Configuration(hostingContext.Configuration)) - .Build(); -#pragma warning restore CS0618 // Type or member is obsolete - - Assert.Null(host.Services.GetService()); + webHostBuilder.ConfigureAppConfiguration(configuration => + { + configuration.AddInMemoryCollection(new Dictionary + { + ["DataProtectionOptions:StorageKind"] = StorageKind.FileSystem.ToString(), + ["DataProtectionOptions:StorageConnectionString"] = @"C:\test" + }); + }) + .ConfigureServices(services => + { + services.AddTransient(p => storeMock.Object); + }) + .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)); + }); + + Assert.Null(factory.Services.GetService()); } [Fact] public void Configure_should_configure_data_protection_redis_storage() { - var configuration = new ConfigurationManager(); - configuration.AddInMemoryCollection(new Dictionary - { - ["DataProtectionOptions:StorageKind"] = StorageKind.Redis.ToString(), - ["DataProtectionOptions:StorageConnectionString"] = "localhost:6379" - }); var environementMock = new Mock(); var storeMock = new Mock>(); storeMock.SetupGet(m => m.SchemeDefinitions).Returns(Array.Empty().AsQueryable()).Verifiable(); -#pragma warning disable CS0618 // Type or member is obsolete - using var host = WebHost.CreateDefaultBuilder() - .ConfigureServices(services => + var factory = new WebApplicationFactory() + .WithWebHostBuilder(webHostBuilder => { - services.AddTheIdServer(configuration); - services.AddTransient(p => storeMock.Object); - }) - .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)) - .UseSerilog((hostingContext, configuration) => - configuration.ReadFrom.Configuration(hostingContext.Configuration)) - .Build(); -#pragma warning restore CS0618 // Type or member is obsolete - - Assert.Null(host.Services.GetService()); + webHostBuilder.ConfigureAppConfiguration(configuration => + { + configuration.AddInMemoryCollection(new Dictionary + { + ["DataProtectionOptions:StorageKind"] = StorageKind.Redis.ToString(), + ["DataProtectionOptions:StorageConnectionString"] = "localhost:6379" + }); + }) + .ConfigureServices(services => + { + services.AddTransient(p => storeMock.Object); + }) + .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)); + }); + + Assert.Null(factory.Services.GetService()); } [Fact] public void Configure_should_configure_data_protection_registry_storage() { - var configuration = new ConfigurationManager(); - configuration.AddInMemoryCollection(new Dictionary - { - ["DataProtectionOptions:StorageKind"] = StorageKind.Registry.ToString(), - ["DataProtectionOptions:StorageConnectionString"] = @"SOFTWARE\Microsoft" - }); var environementMock = new Mock(); var storeMock = new Mock>(); storeMock.SetupGet(m => m.SchemeDefinitions).Returns(Array.Empty().AsQueryable()).Verifiable(); -#pragma warning disable CS0618 // Type or member is obsolete - using var host = WebHost.CreateDefaultBuilder() - .ConfigureServices(services => + var factory = new WebApplicationFactory() + .WithWebHostBuilder(webHostBuilder => { - services.AddTheIdServer(configuration); - services.AddTransient(p => storeMock.Object); - }) - .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)) - .UseSerilog((hostingContext, configuration) => - configuration.ReadFrom.Configuration(hostingContext.Configuration)) - .Build(); -#pragma warning restore CS0618 // Type or member is obsolete - - Assert.Null(host.Services.GetService()); + webHostBuilder.ConfigureAppConfiguration(configuration => + { + configuration.AddInMemoryCollection(new Dictionary + { + ["DataProtectionOptions:StorageKind"] = StorageKind.Registry.ToString(), + ["DataProtectionOptions:StorageConnectionString"] = @"SOFTWARE\Microsoft" + }); + }) + .ConfigureServices(services => + { + services.AddTransient(p => storeMock.Object); + }) + .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)); + }); + + Assert.Null(factory.Services.GetService()); } [Fact] public void Configure_should_configure_data_protection_storage_azure_protection() { - var configuration = new ConfigurationManager(); - configuration.AddInMemoryCollection(new Dictionary - { - ["DataProtectionOptions:StorageKind"] = StorageKind.None.ToString(), - ["DataProtectionOptions:KeyProtectionOptions:KeyProtectionKind"] = KeyProtectionKind.AzureKeyVault.ToString(), - ["DataProtectionOptions:KeyProtectionOptions:AzureKeyVaultKeyId"] = "http://test", - ["DataProtectionOptions:KeyProtectionOptions:AzureKeyVaultClientId"] = "test", - ["DataProtectionOptions:KeyProtectionOptions:AzureKeyVaultClientSecret"] = "test", - }); var environementMock = new Mock(); var storeMock = new Mock>(); storeMock.SetupGet(m => m.SchemeDefinitions).Returns(Array.Empty().AsQueryable()).Verifiable(); -#pragma warning disable CS0618 // Type or member is obsolete - using var host = WebHost.CreateDefaultBuilder() - .ConfigureServices(services => + var factory = new WebApplicationFactory() + .WithWebHostBuilder(webHostBuilder => { - services.AddTheIdServer(configuration); - services.AddTransient(p => storeMock.Object); - }) - .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)) - .UseSerilog((hostingContext, configuration) => - configuration.ReadFrom.Configuration(hostingContext.Configuration)) - .Build(); -#pragma warning restore CS0618 // Type or member is obsolete - - Assert.Null(host.Services.GetService()); + webHostBuilder.ConfigureAppConfiguration(configuration => + { + configuration.AddInMemoryCollection(new Dictionary + { + ["DataProtectionOptions:StorageKind"] = StorageKind.None.ToString(), + ["DataProtectionOptions:KeyProtectionOptions:KeyProtectionKind"] = KeyProtectionKind.AzureKeyVault.ToString(), + ["DataProtectionOptions:KeyProtectionOptions:AzureKeyVaultKeyId"] = "http://test", + ["DataProtectionOptions:KeyProtectionOptions:AzureKeyVaultClientId"] = "test", + ["DataProtectionOptions:KeyProtectionOptions:AzureKeyVaultClientSecret"] = "test", + }); + }) + .ConfigureServices(services => + { + services.AddTransient(p => storeMock.Object); + }) + .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)); + }); + + Assert.Null(factory.Services.GetService()); } [Fact] public void Configure_should_configure_data_protection_storage_dpapi_protection() { - var configuration = new ConfigurationManager(); - configuration.AddInMemoryCollection(new Dictionary - { - ["DataProtectionOptions:StorageKind"] = StorageKind.None.ToString(), - ["DataProtectionOptions:KeyProtectionOptions:KeyProtectionKind"] = KeyProtectionKind.WindowsDpApi.ToString() - }); var environementMock = new Mock(); var storeMock = new Mock>(); storeMock.SetupGet(m => m.SchemeDefinitions).Returns(Array.Empty().AsQueryable()).Verifiable(); -#pragma warning disable CS0618 // Type or member is obsolete - using var host = WebHost.CreateDefaultBuilder() - .ConfigureServices(services => + var factory = new WebApplicationFactory() + .WithWebHostBuilder(webHostBuilder => { - services.AddTheIdServer(configuration); - services.AddTransient(p => storeMock.Object); - }) - .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)) - .UseSerilog((hostingContext, configuration) => - configuration.ReadFrom.Configuration(hostingContext.Configuration)) - .Build(); -#pragma warning restore CS0618 // Type or member is obsolete - - Assert.Null(host.Services.GetService()); + webHostBuilder.ConfigureAppConfiguration(configuration => + { + configuration.AddInMemoryCollection(new Dictionary + { + ["DataProtectionOptions:StorageKind"] = StorageKind.None.ToString(), + ["DataProtectionOptions:KeyProtectionOptions:KeyProtectionKind"] = KeyProtectionKind.WindowsDpApi.ToString() + }); + }) + .ConfigureServices(services => + { + services.AddTransient(p => storeMock.Object); + }) + .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)); + }); + + Assert.Null(factory.Services.GetService()); } [Fact] public void Configure_should_configure_data_protection_storage_dpaping_protection() { - var configuration = new ConfigurationManager(); - configuration.AddInMemoryCollection(new Dictionary - { - ["DataProtectionOptions:StorageKind"] = StorageKind.None.ToString(), - ["DataProtectionOptions:KeyProtectionOptions:KeyProtectionKind"] = KeyProtectionKind.WindowsDpApiNg.ToString() - }); var environementMock = new Mock(); var storeMock = new Mock>(); storeMock.SetupGet(m => m.SchemeDefinitions).Returns(Array.Empty().AsQueryable()).Verifiable(); -#pragma warning disable CS0618 // Type or member is obsolete - using var host = WebHost.CreateDefaultBuilder() - .ConfigureServices(services => + var factory = new WebApplicationFactory() + .WithWebHostBuilder(webHostBuilder => { - services.AddTheIdServer(configuration); - services.AddTransient(p => storeMock.Object); - }) - .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)) - .UseSerilog((hostingContext, configuration) => - configuration.ReadFrom.Configuration(hostingContext.Configuration)) - .Build(); -#pragma warning restore CS0618 // Type or member is obsolete - - Assert.Null(host.Services.GetService()); + webHostBuilder.ConfigureAppConfiguration(configuration => + { + configuration.AddInMemoryCollection(new Dictionary + { + ["DataProtectionOptions:StorageKind"] = StorageKind.None.ToString(), + ["DataProtectionOptions:KeyProtectionOptions:KeyProtectionKind"] = KeyProtectionKind.WindowsDpApiNg.ToString() + }); + }) + .ConfigureServices(services => + { + services.AddTransient(p => storeMock.Object); + }) + .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)); + }); + + Assert.Null(factory.Services.GetService()); } [Fact] public void Configure_should_configure_data_protection_storage_dpaping_protection_with_cert() { - var configuration = new ConfigurationManager(); - configuration.AddInMemoryCollection(new Dictionary - { - ["DataProtectionOptions:StorageKind"] = StorageKind.None.ToString(), - ["DataProtectionOptions:KeyProtectionOptions:KeyProtectionKind"] = KeyProtectionKind.WindowsDpApiNg.ToString(), - ["DataProtectionOptions:KeyProtectionOptions:WindowsDpApiNgCerticate"] = "test" - }); var environementMock = new Mock(); var storeMock = new Mock>(); storeMock.SetupGet(m => m.SchemeDefinitions).Returns(Array.Empty().AsQueryable()).Verifiable(); -#pragma warning disable CS0618 // Type or member is obsolete - using var host = WebHost.CreateDefaultBuilder() - .ConfigureServices(services => + var factory = new WebApplicationFactory() + .WithWebHostBuilder(webHostBuilder => { - services.AddTheIdServer(configuration); - services.AddTransient(p => storeMock.Object); - }) - .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)) - .UseSerilog((hostingContext, configuration) => - configuration.ReadFrom.Configuration(hostingContext.Configuration)) - .Build(); -#pragma warning restore CS0618 // Type or member is obsolete - - Assert.Null(host.Services.GetService()); + webHostBuilder.ConfigureAppConfiguration(configuration => + { + configuration.AddInMemoryCollection(new Dictionary + { + ["DataProtectionOptions:StorageKind"] = StorageKind.None.ToString(), + ["DataProtectionOptions:KeyProtectionOptions:KeyProtectionKind"] = KeyProtectionKind.WindowsDpApiNg.ToString(), + ["DataProtectionOptions:KeyProtectionOptions:WindowsDpApiNgCerticate"] = "test" + }); + }) + .ConfigureServices(services => + { + services.AddTransient(p => storeMock.Object); + }) + .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)); + }); + + Assert.Null(factory.Services.GetService()); } [Fact] public void Configure_should_configure_data_protection_storage_dpaping_protection_with_sid() { - var configuration = new ConfigurationManager(); - configuration.AddInMemoryCollection(new Dictionary - { - ["DataProtectionOptions:StorageKind"] = StorageKind.None.ToString(), - ["DataProtectionOptions:KeyProtectionOptions:KeyProtectionKind"] = KeyProtectionKind.WindowsDpApiNg.ToString(), - ["DataProtectionOptions:KeyProtectionOptions:WindowsDpApiNgSid"] = "test" - }); var environementMock = new Mock(); var storeMock = new Mock>(); storeMock.SetupGet(m => m.SchemeDefinitions).Returns(Array.Empty().AsQueryable()).Verifiable(); -#pragma warning disable CS0618 // Type or member is obsolete - using var host = WebHost.CreateDefaultBuilder() - .ConfigureServices(services => + var factory = new WebApplicationFactory() + .WithWebHostBuilder(webHostBuilder => { - services.AddTheIdServer(configuration); - services.AddTransient(p => storeMock.Object); - }) - .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)) - .UseSerilog((hostingContext, configuration) => - configuration.ReadFrom.Configuration(hostingContext.Configuration)) - .Build(); -#pragma warning restore CS0618 // Type or member is obsolete - - Assert.Null(host.Services.GetService()); + webHostBuilder.ConfigureAppConfiguration(configuration => + { + configuration.AddInMemoryCollection(new Dictionary + { + ["DataProtectionOptions:StorageKind"] = StorageKind.None.ToString(), + ["DataProtectionOptions:KeyProtectionOptions:KeyProtectionKind"] = KeyProtectionKind.WindowsDpApiNg.ToString(), + ["DataProtectionOptions:KeyProtectionOptions:WindowsDpApiNgSid"] = "test" + }); + }) + .ConfigureServices(services => + { + services.AddTransient(p => storeMock.Object); + }) + .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)); + }); + + Assert.Null(factory.Services.GetService()); } [Fact] public void Configure_should_configure_data_protection_storage_cert_protection() { - var configuration = new ConfigurationManager(); - configuration.AddInMemoryCollection(new Dictionary - { - ["DataProtectionOptions:StorageKind"] = StorageKind.None.ToString(), - ["DataProtectionOptions:KeyProtectionOptions:KeyProtectionKind"] = KeyProtectionKind.X509.ToString(), - ["DataProtectionOptions:KeyProtectionOptions:X509CertificateThumbprint"] = "test" - }); var environementMock = new Mock(); var storeMock = new Mock>(); storeMock.SetupGet(m => m.SchemeDefinitions).Returns(Array.Empty().AsQueryable()).Verifiable(); -#pragma warning disable CS0618 // Type or member is obsolete - Assert.Throws(() => WebHost.CreateDefaultBuilder() - .ConfigureServices(services => + var factory = new WebApplicationFactory() + .WithWebHostBuilder(webHostBuilder => { - services.AddTheIdServer(configuration); - services.AddTransient(p => storeMock.Object); - }) - .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)) - .UseSerilog((hostingContext, configuration) => - configuration.ReadFrom.Configuration(hostingContext.Configuration)) - .Build()); -#pragma warning restore CS0618 // Type or member is obsolete + webHostBuilder.ConfigureAppConfiguration(configuration => + { + configuration.AddInMemoryCollection(new Dictionary + { + ["DataProtectionOptions:StorageKind"] = StorageKind.None.ToString(), + ["DataProtectionOptions:KeyProtectionOptions:KeyProtectionKind"] = KeyProtectionKind.X509.ToString(), + ["DataProtectionOptions:KeyProtectionOptions:X509CertificateThumbprint"] = "test" + }); + }) + .ConfigureServices(services => + { + services.AddTransient(p => storeMock.Object); + }) + .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)); + }); + + var client = factory.CreateClient(); + Assert.NotNull(client); } [Fact] public void Configure_should_configure_data_protection_storage_cert_file_protection() { - var configuration = new ConfigurationManager(); - configuration.AddInMemoryCollection(new Dictionary - { - ["DataProtectionOptions:StorageKind"] = StorageKind.None.ToString(), - ["DataProtectionOptions:KeyProtectionOptions:KeyProtectionKind"] = KeyProtectionKind.X509.ToString(), - ["DataProtectionOptions:KeyProtectionOptions:X509CertificatePath"] = "theidserver.pfx", - ["DataProtectionOptions:KeyProtectionOptions:X509CertificatePassword"] = "YourSecurePassword" - }); var environementMock = new Mock(); var storeMock = new Mock>(); storeMock.SetupGet(m => m.SchemeDefinitions).Returns(Array.Empty().AsQueryable()).Verifiable(); -#pragma warning disable CS0618 // Type or member is obsolete - using var host = WebHost.CreateDefaultBuilder() - .ConfigureServices(services => + var factory = new WebApplicationFactory() + .WithWebHostBuilder(webHostBuilder => { - services.AddTheIdServer(configuration); - services.AddTransient(p => storeMock.Object); - }) - .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)) - .UseSerilog((hostingContext, configuration) => - configuration.ReadFrom.Configuration(hostingContext.Configuration)) - .Build(); -#pragma warning restore CS0618 // Type or member is obsolete - - Assert.Null(host.Services.GetService()); + webHostBuilder.ConfigureAppConfiguration(configuration => + { + configuration.AddInMemoryCollection(new Dictionary + { + ["DataProtectionOptions:StorageKind"] = StorageKind.None.ToString(), + ["DataProtectionOptions:KeyProtectionOptions:KeyProtectionKind"] = KeyProtectionKind.X509.ToString(), + ["DataProtectionOptions:KeyProtectionOptions:X509CertificatePath"] = "theidserver.pfx", + ["DataProtectionOptions:KeyProtectionOptions:X509CertificatePassword"] = "YourSecurePassword" + }); + }) + .ConfigureServices(services => + { + services.AddTransient(p => storeMock.Object); + }) + .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)); + }); + + Assert.Null(factory.Services.GetService()); } [Fact] public void Configure_should_configure_data_protection_algorithms() { - var configuration = new ConfigurationManager(); - configuration.AddInMemoryCollection(new Dictionary - { - ["DataProtectionOptions:AuthenticatedEncryptorConfiguration:EncryptionAlgorithm"] = EncryptionAlgorithm.AES_128_CBC.ToString(), - }); var environementMock = new Mock(); var storeMock = new Mock>(); storeMock.SetupGet(m => m.SchemeDefinitions).Returns(Array.Empty().AsQueryable()).Verifiable(); -#pragma warning disable CS0618 // Type or member is obsolete - using var host = WebHost.CreateDefaultBuilder() - .ConfigureServices(services => + var factory = new WebApplicationFactory() + .WithWebHostBuilder(webHostBuilder => { - services.AddTheIdServer(configuration); - services.AddTransient(p => storeMock.Object); - }) - .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)) - .UseSerilog((hostingContext, configuration) => - configuration.ReadFrom.Configuration(hostingContext.Configuration)) - .Build(); -#pragma warning restore CS0618 // Type or member is obsolete - - Assert.Null(host.Services.GetService()); + webHostBuilder.ConfigureAppConfiguration(configuration => + { + configuration.AddInMemoryCollection(new Dictionary + { + ["DataProtectionOptions:AuthenticatedEncryptorConfiguration:EncryptionAlgorithm"] = EncryptionAlgorithm.AES_128_CBC.ToString(), + }); + }) + .ConfigureServices(services => + { + services.AddTransient(p => storeMock.Object); + }) + .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)); + }); + + Assert.Null(factory.Services.GetService()); } [Fact] public void Configure_should_configure_keys_rotation_azure_storage() { - var configuration = new ConfigurationManager(); - configuration.AddInMemoryCollection(new Dictionary - { - ["IdentityServer:Key:Type"] = KeyKinds.KeysRotation.ToString(), - ["IdentityServer:Key:StorageKind"] = StorageKind.AzureStorage.ToString(), - ["IdentityServer:Key:StorageConnectionString"] = "https://azure.com?sv=test" - }); var environementMock = new Mock(); var storeMock = new Mock>(); storeMock.SetupGet(m => m.SchemeDefinitions).Returns(Array.Empty().AsQueryable()).Verifiable(); -#pragma warning disable CS0618 // Type or member is obsolete - using var host = WebHost.CreateDefaultBuilder() - .ConfigureServices(services => + var factory = new WebApplicationFactory() + .WithWebHostBuilder(webHostBuilder => { - services.AddTheIdServer(configuration); - services.AddTransient(p => storeMock.Object); - }) - .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)) - .UseSerilog((hostingContext, configuration) => - configuration.ReadFrom.Configuration(hostingContext.Configuration)) - .Build(); -#pragma warning restore CS0618 // Type or member is obsolete - - Assert.Null(host.Services.GetService()); + webHostBuilder.ConfigureAppConfiguration(configuration => + { + configuration.AddInMemoryCollection(new Dictionary + { + ["IdentityServer:Key:Type"] = KeyKinds.KeysRotation.ToString(), + ["IdentityServer:Key:StorageKind"] = StorageKind.AzureStorage.ToString(), + ["IdentityServer:Key:StorageConnectionString"] = "https://azure.com?sv=test" + }); + }) + .ConfigureServices(services => + { + services.AddTransient(p => storeMock.Object); + }) + .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)); + }); + + Assert.Null(factory.Services.GetService()); } [Fact] public void Configure_should_configure_keys_rotation_ef_storage() { - var configuration = new ConfigurationManager(); - configuration.AddInMemoryCollection(new Dictionary - { - ["IdentityServer:Key:Type"] = KeyKinds.KeysRotation.ToString(), - ["IdentityServer:Key:StorageKind"] = StorageKind.EntityFramework.ToString() - }); var environementMock = new Mock(); var storeMock = new Mock>(); storeMock.SetupGet(m => m.SchemeDefinitions).Returns(Array.Empty().AsQueryable()).Verifiable(); -#pragma warning disable CS0618 // Type or member is obsolete - using var host = WebHost.CreateDefaultBuilder() - .ConfigureServices(services => + var factory = new WebApplicationFactory() + .WithWebHostBuilder(webHostBuilder => { - services.AddTheIdServer(configuration); - services.AddTransient(p => storeMock.Object); - }) - .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)) - .UseSerilog((hostingContext, configuration) => - configuration.ReadFrom.Configuration(hostingContext.Configuration)) - .Build(); -#pragma warning restore CS0618 // Type or member is obsolete - - Assert.Null(host.Services.GetService()); + webHostBuilder.ConfigureAppConfiguration(configuration => + { + configuration.AddInMemoryCollection(new Dictionary + { + ["IdentityServer:Key:Type"] = KeyKinds.KeysRotation.ToString(), + ["IdentityServer:Key:StorageKind"] = StorageKind.EntityFramework.ToString() + }); + }) + .ConfigureServices(services => + { + services.AddTransient(p => storeMock.Object); + }) + .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)); + }); + + Assert.Null(factory.Services.GetService()); } [Fact] public void Configure_should_configure_keys_rotation_fs_storage() { - var configuration = new ConfigurationManager(); - configuration.AddInMemoryCollection(new Dictionary - { - ["IdentityServer:Key:Type"] = KeyKinds.KeysRotation.ToString(), - ["IdentityServer:Key:StorageKind"] = StorageKind.FileSystem.ToString(), - ["IdentityServer:Key:StorageConnectionString"] = @"C:\test" - }); var environementMock = new Mock(); var storeMock = new Mock>(); storeMock.SetupGet(m => m.SchemeDefinitions).Returns(Array.Empty().AsQueryable()).Verifiable(); -#pragma warning disable CS0618 // Type or member is obsolete - using var host = WebHost.CreateDefaultBuilder() - .ConfigureServices(services => + var factory = new WebApplicationFactory() + .WithWebHostBuilder(webHostBuilder => { - services.AddTheIdServer(configuration); - services.AddTransient(p => storeMock.Object); - }) - .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)) - .UseSerilog((hostingContext, configuration) => - configuration.ReadFrom.Configuration(hostingContext.Configuration)) - .Build(); -#pragma warning restore CS0618 // Type or member is obsolete - - Assert.Null(host.Services.GetService()); + webHostBuilder.ConfigureAppConfiguration(configuration => + { + configuration.AddInMemoryCollection(new Dictionary + { + ["IdentityServer:Key:Type"] = KeyKinds.KeysRotation.ToString(), + ["IdentityServer:Key:StorageKind"] = StorageKind.FileSystem.ToString(), + ["IdentityServer:Key:StorageConnectionString"] = @"C:\test" + }); + }) + .ConfigureServices(services => + { + services.AddTransient(p => storeMock.Object); + }) + .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)); + }); + + Assert.Null(factory.Services.GetService()); } [Fact] public void Configure_should_configure_keys_rotation_redis_storage() { - var configuration = new ConfigurationManager(); - configuration.AddInMemoryCollection(new Dictionary - { - ["IdentityServer:Key:Type"] = KeyKinds.KeysRotation.ToString(), - ["IdentityServer:Key:StorageKind"] = StorageKind.Redis.ToString(), - ["IdentityServer:Key:StorageConnectionString"] = "localhost:6379" - }); var environementMock = new Mock(); var storeMock = new Mock>(); storeMock.SetupGet(m => m.SchemeDefinitions).Returns(Array.Empty().AsQueryable()).Verifiable(); -#pragma warning disable CS0618 // Type or member is obsolete - using var host = WebHost.CreateDefaultBuilder() - .ConfigureServices(services => + var factory = new WebApplicationFactory() + .WithWebHostBuilder(webHostBuilder => { - services.AddTheIdServer(configuration); - services.AddTransient(p => storeMock.Object); - }) - .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)) - .UseSerilog((hostingContext, configuration) => - configuration.ReadFrom.Configuration(hostingContext.Configuration)) - .Build(); -#pragma warning restore CS0618 // Type or member is obsolete - - Assert.Null(host.Services.GetService()); + webHostBuilder.ConfigureAppConfiguration(configuration => + { + configuration.AddInMemoryCollection(new Dictionary + { + ["IdentityServer:Key:Type"] = KeyKinds.KeysRotation.ToString(), + ["IdentityServer:Key:StorageKind"] = StorageKind.Redis.ToString(), + ["IdentityServer:Key:StorageConnectionString"] = "localhost:6379" + }); + }) + .ConfigureServices(services => + { + services.AddTransient(p => storeMock.Object); + }) + .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)); + }); + + Assert.Null(factory.Services.GetService()); } [Fact] public void Configure_should_configure_keys_rotation_storage_azure_protection() { - var configuration = new ConfigurationManager(); - configuration.AddInMemoryCollection(new Dictionary - { - ["IdentityServer:Key:Type"] = KeyKinds.KeysRotation.ToString(), - ["IdentityServer:Key:StorageKind"] = StorageKind.None.ToString(), - ["IdentityServer:Key:KeyProtectionOptions:KeyProtectionKind"] = KeyProtectionKind.AzureKeyVault.ToString(), - ["IdentityServer:Key:KeyProtectionOptions:AzureKeyVaultKeyId"] = "test", - ["IdentityServer:Key:KeyProtectionOptions:AzureKeyVaultClientId"] = "test", - ["IdentityServer:Key:KeyProtectionOptions:AzureKeyVaultClientSecret"] = "test", - }); var environementMock = new Mock(); var storeMock = new Mock>(); storeMock.SetupGet(m => m.SchemeDefinitions).Returns(Array.Empty().AsQueryable()).Verifiable(); -#pragma warning disable CS0618 // Type or member is obsolete - using var host = WebHost.CreateDefaultBuilder() - .ConfigureServices(services => + var factory = new WebApplicationFactory() + .WithWebHostBuilder(webHostBuilder => { - services.AddTheIdServer(configuration); - services.AddTransient(p => storeMock.Object); - }) - .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)) - .UseSerilog((hostingContext, configuration) => - configuration.ReadFrom.Configuration(hostingContext.Configuration)) - .Build(); -#pragma warning restore CS0618 // Type or member is obsolete - - Assert.Null(host.Services.GetService()); + webHostBuilder.ConfigureAppConfiguration(configuration => + { + configuration.AddInMemoryCollection(new Dictionary + { + ["IdentityServer:Key:Type"] = KeyKinds.KeysRotation.ToString(), + ["IdentityServer:Key:StorageKind"] = StorageKind.None.ToString(), + ["IdentityServer:Key:KeyProtectionOptions:KeyProtectionKind"] = KeyProtectionKind.AzureKeyVault.ToString(), + ["IdentityServer:Key:KeyProtectionOptions:AzureKeyVaultKeyId"] = "test", + ["IdentityServer:Key:KeyProtectionOptions:AzureKeyVaultClientId"] = "test", + ["IdentityServer:Key:KeyProtectionOptions:AzureKeyVaultClientSecret"] = "test", + }); + }) + .ConfigureServices(services => + { + services.AddTransient(p => storeMock.Object); + }) + .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)); + }); + + Assert.Null(factory.Services.GetService()); } [Fact] public void Configure_should_configure_keys_rotation_storage_cert_protection() { - var configuration = new ConfigurationManager(); - configuration.AddInMemoryCollection(new Dictionary - { - ["IdentityServer:Key:Type"] = KeyKinds.KeysRotation.ToString(), - ["IdentityServer:Key:StorageKind"] = StorageKind.None.ToString(), - ["IdentityServer:Key:KeyProtectionOptions:KeyProtectionKind"] = KeyProtectionKind.X509.ToString(), - ["IdentityServer:Key:KeyProtectionOptions:X509CertificateThumbprint"] = "test" - }); var environementMock = new Mock(); var storeMock = new Mock>(); storeMock.SetupGet(m => m.SchemeDefinitions).Returns(Array.Empty().AsQueryable()).Verifiable(); -#pragma warning disable CS0618 // Type or member is obsolete - Assert.Throws(() => WebHost.CreateDefaultBuilder() - .ConfigureServices(services => + var factory = new WebApplicationFactory() + .WithWebHostBuilder(webHostBuilder => { - services.AddTheIdServer(configuration); - services.AddTransient(p => storeMock.Object); - }) - .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)) - .UseSerilog((hostingContext, configuration) => - configuration.ReadFrom.Configuration(hostingContext.Configuration)) - .Build()); -#pragma warning restore CS0618 // Type or member is obsolete + webHostBuilder.ConfigureAppConfiguration(configuration => + { + configuration.AddInMemoryCollection(new Dictionary + { + ["IdentityServer:Key:Type"] = KeyKinds.KeysRotation.ToString(), + ["IdentityServer:Key:StorageKind"] = StorageKind.None.ToString(), + ["IdentityServer:Key:KeyProtectionOptions:KeyProtectionKind"] = KeyProtectionKind.X509.ToString(), + ["IdentityServer:Key:KeyProtectionOptions:X509CertificateThumbprint"] = "test" + }); + }) + .ConfigureServices(services => + { + services.AddTransient(p => storeMock.Object); + }) + .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)); + }); + + Assert.NotNull(factory.CreateClient()); } [Fact] public void Configure_should_configure_keys_rotation_storage_cert_file_protection() { - var configuration = new ConfigurationManager(); - configuration.AddInMemoryCollection(new Dictionary - { - ["IdentityServer:Key:Type"] = KeyKinds.KeysRotation.ToString(), - ["IdentityServer:Key:StorageKind"] = StorageKind.None.ToString(), - ["IdentityServer:Key:KeyProtectionOptions:KeyProtectionKind"] = KeyProtectionKind.X509.ToString(), - ["IdentityServer:Key:KeyProtectionOptions:X509CertificatePath"] = "theidserver.pfx", - ["IdentityServer:Key:KeyProtectionOptions:X509CertificatePassword"] = "YourSecurePassword" - }); var environementMock = new Mock(); var storeMock = new Mock>(); storeMock.SetupGet(m => m.SchemeDefinitions).Returns(Array.Empty().AsQueryable()).Verifiable(); -#pragma warning disable CS0618 // Type or member is obsolete - using var host = WebHost.CreateDefaultBuilder() - .ConfigureServices(services => + var factory = new WebApplicationFactory() + .WithWebHostBuilder(webHostBuilder => { - services.AddTheIdServer(configuration); - services.AddTransient(p => storeMock.Object); - }) - .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)) - .UseSerilog((hostingContext, configuration) => - configuration.ReadFrom.Configuration(hostingContext.Configuration)) - .Build(); -#pragma warning restore CS0618 // Type or member is obsolete - - Assert.Null(host.Services.GetService()); + webHostBuilder.ConfigureAppConfiguration(configuration => + { + configuration.AddInMemoryCollection(new Dictionary + { + ["IdentityServer:Key:Type"] = KeyKinds.KeysRotation.ToString(), + ["IdentityServer:Key:StorageKind"] = StorageKind.None.ToString(), + ["IdentityServer:Key:KeyProtectionOptions:KeyProtectionKind"] = KeyProtectionKind.X509.ToString(), + ["IdentityServer:Key:KeyProtectionOptions:X509CertificatePath"] = "theidserver.pfx", + ["IdentityServer:Key:KeyProtectionOptions:X509CertificatePassword"] = "YourSecurePassword" + }); + }) + .ConfigureServices(services => + { + services.AddTransient(p => storeMock.Object); + }) + .Configure((context, builder) => builder.UseTheIdServer(context.HostingEnvironment, context.Configuration)); + }); + + Assert.Null(factory.Services.GetService()); } } diff --git a/test/Duende/Aguacongas.TheIdServer.Identity.Duende.Test/Aguacongas.TheIdServer.Identity.Duende.Test.csproj b/test/Duende/Aguacongas.TheIdServer.Identity.Duende.Test/Aguacongas.TheIdServer.Identity.Duende.Test.csproj index 3ab9c01e2..089adb5ac 100644 --- a/test/Duende/Aguacongas.TheIdServer.Identity.Duende.Test/Aguacongas.TheIdServer.Identity.Duende.Test.csproj +++ b/test/Duende/Aguacongas.TheIdServer.Identity.Duende.Test/Aguacongas.TheIdServer.Identity.Duende.Test.csproj @@ -1,14 +1,14 @@  - net7.0 + net8.0 True false - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Duende/Aguacongas.TheIdServer.Identity.Integration.Duende.Test/Aguacongas.TheIdServer.Identity.Integration.Duende.Test.csproj b/test/Duende/Aguacongas.TheIdServer.Identity.Integration.Duende.Test/Aguacongas.TheIdServer.Identity.Integration.Duende.Test.csproj index 3e5cc737f..5ff6064d3 100644 --- a/test/Duende/Aguacongas.TheIdServer.Identity.Integration.Duende.Test/Aguacongas.TheIdServer.Identity.Integration.Duende.Test.csproj +++ b/test/Duende/Aguacongas.TheIdServer.Identity.Integration.Duende.Test/Aguacongas.TheIdServer.Identity.Integration.Duende.Test.csproj @@ -1,9 +1,9 @@  - net7.0 + net8.0 false - + true True @@ -17,12 +17,13 @@ - - - - + + + + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Duende/Aguacongas.TheIdServer.Identity.Integration.Duende.Test/TestUtils.cs b/test/Duende/Aguacongas.TheIdServer.Identity.Integration.Duende.Test/TestUtils.cs deleted file mode 100644 index ef13f2156..000000000 --- a/test/Duende/Aguacongas.TheIdServer.Identity.Integration.Duende.Test/TestUtils.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Project: Aguafrommars/TheIdServer -// Copyright (c) 2023 @Olivier Lefebvre -using Aguacongas.TheIdServer.Models; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Serilog; -using System; -using System.Collections.Generic; -using System.IO; - -namespace Aguacongas.TheIdServer.Identity.IntegrationTest -{ - public static class TestUtils - { - - public static TestServer CreateTestServer( - Action configureServices = null, - IEnumerable> configurationOverrides = null) - { -#pragma warning disable CS0618 // Type or member is obsolete - var webHostBuilder = new WebHostBuilder() - .UseEnvironment("Development") - .UseSerilog((hostingContext, loggerConfiguration) => loggerConfiguration - .ReadFrom.Configuration(hostingContext.Configuration)) - .ConfigureServices((context, services) => - { - configureServices?.Invoke(services); - - var configurationManager = new ConfigurationManager(); - configurationManager.AddJsonFile(Path.Combine(Environment.CurrentDirectory, @"..\..\..\..\..\..\src\Aguacongas.TheIdServer.Duende\appsettings.json")); - configurationManager.AddJsonFile(Path.Combine(Environment.CurrentDirectory, @"appsettings.Test.json"), true); - if (configurationOverrides != null) - { - configurationManager.AddInMemoryCollection(configurationOverrides); - } - - var isProxy = configurationManager.GetValue("Proxy"); - var dbType = configurationManager.GetValue("DbType"); - - services.AddTheIdServer(configurationManager); - services.AddSingleton() - .AddMvc().AddApplicationPart(typeof(Config).Assembly); - configureServices?.Invoke(services); - }) - .Configure((context, builder) => - { - builder.Use(async (context, next) => - { - var testService = context.RequestServices.GetRequiredService(); - context.User = testService.User; - await next(); - }); - - builder.UseTheIdServer(context.HostingEnvironment, context.Configuration); - }); -#pragma warning restore CS0618 // Type or member is obsolete - - var testServer = new TestServer(webHostBuilder); - - return testServer; - } - - } -} diff --git a/test/Duende/Aguacongas.TheIdServer.Identity.Integration.Duende.Test/TheIdServerTestFixture.cs b/test/Duende/Aguacongas.TheIdServer.Identity.Integration.Duende.Test/TheIdServerTestFixture.cs index 04100d231..5738da1dc 100644 --- a/test/Duende/Aguacongas.TheIdServer.Identity.Integration.Duende.Test/TheIdServerTestFixture.cs +++ b/test/Duende/Aguacongas.TheIdServer.Identity.Integration.Duende.Test/TheIdServerTestFixture.cs @@ -4,11 +4,17 @@ using Aguacongas.IdentityServer.Admin.Services; using Aguacongas.IdentityServer.EntityFramework.Store; using Aguacongas.TheIdServer.Data; +using Aguacongas.TheIdServer.UI; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.TestHost; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using System; +using System.IO; using Xunit.Abstractions; namespace Aguacongas.TheIdServer.Identity.IntegrationTest @@ -38,21 +44,42 @@ public ILoggerProvider LoggerProvider public TheIdServerTestFixture() { var dbName = Guid.NewGuid().ToString(); - Sut = TestUtils.CreateTestServer( - services => + var factory = new WebApplicationFactory() + .WithWebHostBuilder(webHostBuilder => { - services.AddSingleton() - .AddTransient() - .AddTheIdServerAdminEntityFrameworkStores(options => - options.UseInMemoryDatabase(dbName)) - .AddIdentityProviderStore() - .AddConfigurationEntityFrameworkStores(options => - options.UseInMemoryDatabase(dbName)) - .AddOperationalEntityFrameworkStores(options => - options.UseInMemoryDatabase(dbName)); + webHostBuilder.ConfigureAppConfiguration(configuration => + { + configuration.AddJsonFile(Path.Combine(Environment.CurrentDirectory, @"appsettings.Test.json"), true); + }) + .ConfigureServices(services => + { + services.AddSingleton() + .AddMvc().AddApplicationPart(typeof(Config).Assembly); + services.AddSingleton() + .AddTransient() + .AddTheIdServerAdminEntityFrameworkStores(options => + options.UseInMemoryDatabase(dbName)) + .AddIdentityProviderStore() + .AddConfigurationEntityFrameworkStores(options => + options.UseInMemoryDatabase(dbName)) + .AddOperationalEntityFrameworkStores(options => + options.UseInMemoryDatabase(dbName)); + }) + .Configure((context, builder) => + { + builder.Use(async (context, next) => + { + var testService = context.RequestServices.GetRequiredService(); + context.User = testService.User; + await next(); + }); + + builder.UseTheIdServer(context.HostingEnvironment, context.Configuration); + }); }); + Sut = factory.Server; - using var scope = Sut.Host.Services.CreateScope(); + using var scope = factory.Services.CreateScope(); using var identityContext = scope.ServiceProvider.GetRequiredService(); identityContext.Database.EnsureCreated(); using var operationalContext = scope.ServiceProvider.GetRequiredService(); diff --git a/test/Duende/Aguacongas.TheIdServer.Identity.Integration.Duende.Test/UserStoreTest.cs b/test/Duende/Aguacongas.TheIdServer.Identity.Integration.Duende.Test/UserStoreTest.cs index f2c77350d..e5ece84da 100644 --- a/test/Duende/Aguacongas.TheIdServer.Identity.Integration.Duende.Test/UserStoreTest.cs +++ b/test/Duende/Aguacongas.TheIdServer.Identity.Integration.Duende.Test/UserStoreTest.cs @@ -17,7 +17,6 @@ namespace Aguacongas.TheIdServer.Identity.IntegrationTest { - [SuppressMessage("Usage", "xUnit1033:Test classes decorated with 'Xunit.IClassFixture' or 'Xunit.ICollectionFixture' should add a constructor argument of type TFixture", Justification = "false positive")] public class UserStoreTest : IdentitySpecificationTestBase, IClassFixture { private readonly TheIdServerTestFixture _fixture; diff --git a/test/Duende/Aguacongas.TheIdServer.Integration.Duende.Test/Aguacongas.TheIdServer.Integration.Duende.Test.csproj b/test/Duende/Aguacongas.TheIdServer.Integration.Duende.Test/Aguacongas.TheIdServer.Integration.Duende.Test.csproj index bbcf534f7..0d3cb5fb1 100644 --- a/test/Duende/Aguacongas.TheIdServer.Integration.Duende.Test/Aguacongas.TheIdServer.Integration.Duende.Test.csproj +++ b/test/Duende/Aguacongas.TheIdServer.Integration.Duende.Test/Aguacongas.TheIdServer.Integration.Duende.Test.csproj @@ -1,11 +1,11 @@  - net7.0 + net8.0 enable false 51a7f0be-96e4-42d5-ad09-37e9adabfff6 - + true True @@ -18,15 +18,15 @@ - + - - - - - + + + + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Duende/Aguacongas.TheIdServer.Integration.Duende.Test/BlazorApp/Pages/RelyingPartyTest.cs b/test/Duende/Aguacongas.TheIdServer.Integration.Duende.Test/BlazorApp/Pages/RelyingPartyTest.cs index 56fed8a5a..2ee5d7985 100644 --- a/test/Duende/Aguacongas.TheIdServer.Integration.Duende.Test/BlazorApp/Pages/RelyingPartyTest.cs +++ b/test/Duende/Aguacongas.TheIdServer.Integration.Duende.Test/BlazorApp/Pages/RelyingPartyTest.cs @@ -3,12 +3,10 @@ using Aguacongas.IdentityServer.EntityFramework.Store; using Aguacongas.IdentityServer.Store; using Aguacongas.IdentityServer.Store.Entity; -using Aguacongas.TheIdServer.BlazorApp; using Bunit; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Forms; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; using Microsoft.JSInterop.Infrastructure; using System; @@ -17,7 +15,6 @@ using System.Threading; using System.Threading.Tasks; using Xunit; -using Xunit.Abstractions; using RelyingPartyPage = Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParty.RelyingParty; namespace Aguacongas.TheIdServer.IntegrationTest.BlazorApp.Pages diff --git a/test/Duende/Aguacongas.TheIdServer.Integration.Duende.Test/Controllers/RegisterControllerTest.cs b/test/Duende/Aguacongas.TheIdServer.Integration.Duende.Test/Controllers/RegisterControllerTest.cs index 23527cc80..c609fd533 100644 --- a/test/Duende/Aguacongas.TheIdServer.Integration.Duende.Test/Controllers/RegisterControllerTest.cs +++ b/test/Duende/Aguacongas.TheIdServer.Integration.Duende.Test/Controllers/RegisterControllerTest.cs @@ -26,7 +26,7 @@ namespace Aguacongas.TheIdServer.Integration.Duende.Test.Controllers [Collection(BlazorAppCollection.Name)] public class RegisterControllerTest { - private WebApplicationFactory _factory; + private readonly WebApplicationFactory _factory; public RegisterControllerTest(TheIdServerFactory factory) { _factory = factory; diff --git a/test/Duende/Aguacongas.TheIdServer.Integration.Duende.Test/TestUtils.cs b/test/Duende/Aguacongas.TheIdServer.Integration.Duende.Test/TestUtils.cs index 63db906ef..28616a6a2 100644 --- a/test/Duende/Aguacongas.TheIdServer.Integration.Duende.Test/TestUtils.cs +++ b/test/Duende/Aguacongas.TheIdServer.Integration.Duende.Test/TestUtils.cs @@ -36,7 +36,7 @@ public static IConfigurationRoot CreateApplicationConfiguration(HttpClient httpC ["ProviderOptions:ResponseType"] = "code", ["WelcomeContenUrl"] = "/welcome-fragment.html", ["SettingsOptions:TypeName"] = "Aguacongas.TheIdServer.BlazorApp.Models.ServerConfig, Aguacongas.TheIdServer.BlazorApp.Infrastructure", - ["SettingsOptions:ApiUrl"] = new Uri(httpClient.BaseAddress, "api/api/configuration").ToString(), + ["SettingsOptions:ApiUrl"] = new Uri(httpClient.BaseAddress, "api/configuration").ToString(), ["MenuOptions:ShowSettings"] = "true" }; var appConfiguration = new ConfigurationBuilder().AddInMemoryCollection(appConfigurationDictionary).Build(); @@ -46,6 +46,7 @@ public static IConfigurationRoot CreateApplicationConfiguration(HttpClient httpC public class FakeAuthenticationStateProvider : AuthenticationStateProvider, IAccessTokenProvider { private readonly AuthenticationState _state; + private static readonly string[] scopes = ["openid", "profile", "theidseveradminaoi"]; public FakeAuthenticationStateProvider(string userName, IEnumerable claims) { @@ -69,7 +70,7 @@ public ValueTask RequestAccessToken() new AccessToken { Expires = DateTimeOffset.Now.AddDays(1), - GrantedScopes = new string[] { "openid", "profile", "theidseveradminaoi" }, + GrantedScopes = scopes, }, "http://exemple.com", new InteractiveRequestOptions { diff --git a/test/Duende/Aguacongas.TheIdServer.Integration.Duende.Test/appsettings.json b/test/Duende/Aguacongas.TheIdServer.Integration.Duende.Test/appsettings.json index ef3e2bb81..3ebeb9313 100644 --- a/test/Duende/Aguacongas.TheIdServer.Integration.Duende.Test/appsettings.json +++ b/test/Duende/Aguacongas.TheIdServer.Integration.Duende.Test/appsettings.json @@ -471,5 +471,30 @@ "Description": "The wep page of the user" } ] + }, + "PasswordHasherOptions": { + "IterationCount": 600000 + }, + "Argon2PasswordHasherOptions": { + "Interations": 2, + "Memory": 67108864 + }, + "BcryptPasswordHasherOptions": { + "WorkFactor": 11 + }, + "ScryptPasswordHasherOptions": { + "IterationCount": 131072, + "BlockSize": 8, + "ThreadCount": 1 + }, + "UpgradePasswordHasherOptions": { + "HashPrefixMaps": { + "0": "Microsoft.AspNetCore.Identity.PasswordHasher", + "1": "Microsoft.AspNetCore.Identity.PasswordHasher", + "162": "Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Argon2PasswordHasher", + "12": "Aguacongas.TheIdServer.Identity.ScryptPasswordHasher.ScryptPasswordHasher", + "188": "Aguacongas.TheIdServer.Identity.BcryptPasswordHasher.BcryptPasswordHasher" + }, + "UsePasswordHasherTypeName": "Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Argon2PasswordHasher" } } \ No newline at end of file diff --git a/test/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Test/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Test.csproj b/test/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Test/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Test.csproj new file mode 100644 index 000000000..1f5393502 --- /dev/null +++ b/test/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Test/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Test.csproj @@ -0,0 +1,30 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/test/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Test/Argon2PasswordHasherTest.cs b/test/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Test/Argon2PasswordHasherTest.cs new file mode 100644 index 000000000..ae66a29a7 --- /dev/null +++ b/test/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Test/Argon2PasswordHasherTest.cs @@ -0,0 +1,75 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; + +namespace Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Test; + +public class Argon2PasswordHasherTest +{ + [Fact] + public void HashPassword_should_hash_password_according_to_options() + { + var settings = new Argon2PasswordHasherOptions(); + + var result = CreateHash(settings, Guid.NewGuid().ToString()); + + Assert.NotNull(result); + var binaryResult = Convert.FromBase64String(result); + + Assert.Equal(settings.HashPrefix, binaryResult[0]); + } + + [Fact] + public void VerifyHashedPassword_should_return_success_on_password_valid() + { + var settings = new Argon2PasswordHasherOptions(); + var options = Options.Create(settings); + var sut = new Argon2PasswordHasher(new Argon2Id(options), options); + var password = Guid.NewGuid().ToString(); + var hash = CreateHash(settings, password); + + var result = sut.VerifyHashedPassword(Guid.NewGuid().ToString(), hash, password); + + Assert.Equal(PasswordVerificationResult.Success, result); + } + + [Fact] + public void VerifyHashedPassword_should_return_rehash_needed_on_settings_change() + { + var settings = new Argon2PasswordHasherOptions + { + Interations = 3 + }; + var password = Guid.NewGuid().ToString(); + var hash = CreateHash(settings, password); + + settings.Interations = 2; + var options = Options.Create(settings); + var sut = new Argon2PasswordHasher(new Argon2Id(options), options); + + var result = sut.VerifyHashedPassword(Guid.NewGuid().ToString(), hash, password); + + Assert.Equal(PasswordVerificationResult.SuccessRehashNeeded, result); + } + + [Fact] + public void VerifyHashedPassword_should_return_fail_invalid_hash() + { + var settings = new Argon2PasswordHasherOptions(); + var hash = CreateHash(settings, Guid.NewGuid().ToString()); + + var options = Options.Create(settings); + var sut = new Argon2PasswordHasher(new Argon2Id(options), options); + + var result = sut.VerifyHashedPassword(Guid.NewGuid().ToString(), hash, Guid.NewGuid().ToString()); + + Assert.Equal(PasswordVerificationResult.Failed, result); + } + + private static string CreateHash(Argon2PasswordHasherOptions settings, string password) + { + var options = Options.Create(settings); + var sut = new Argon2PasswordHasher(new Argon2Id(options), options); + + return sut.HashPassword(Guid.NewGuid().ToString(), password); + } +} \ No newline at end of file diff --git a/test/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Test/Extensions/IdentityBuilderExtensionsTest.cs b/test/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Test/Extensions/IdentityBuilderExtensionsTest.cs new file mode 100644 index 000000000..33fbb0a68 --- /dev/null +++ b/test/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Test/Extensions/IdentityBuilderExtensionsTest.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; + +namespace Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Test.Extensions; +public class IdentityBuilderExtensionsTest +{ + [Fact] + public void AddArgon2PasswordHasher_should_add_argon2_password_hasher_services() + { + var builder = new ServiceCollection() + .AddIdentityCore() + .AddArgon2PasswordHasher(); + var provider = builder.Services.BuildServiceProvider(); + + var hasher = provider.GetService>() as Argon2PasswordHasher; + + Assert.NotNull(hasher); + } +} diff --git a/test/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Test/Extensions/ServiceCollectionExtentsionsTest.cs b/test/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Test/Extensions/ServiceCollectionExtentsionsTest.cs new file mode 100644 index 000000000..a0c2201b4 --- /dev/null +++ b/test/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Test/Extensions/ServiceCollectionExtentsionsTest.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; + +namespace Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Test.Extensions; +public class ServiceCollectionExtentsionsTest +{ + [Fact] + public void AddArgon2PasswordHasher_should_add_argon2_password_hasher_services() + { + var provider = new ServiceCollection().AddArgon2PasswordHasher().BuildServiceProvider(); + + var hasher = provider.GetService>() as Argon2PasswordHasher; + + Assert.NotNull(hasher); + + // test options validation + var hash = hasher.HashPassword(Guid.NewGuid().ToString(), Guid.NewGuid().ToString()); + Assert.NotNull(hash); + } +} diff --git a/test/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Test/GlobalUsings.cs b/test/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Test/GlobalUsings.cs new file mode 100644 index 000000000..8c927eb74 --- /dev/null +++ b/test/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Test/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/test/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasherTest/Aguacongas.TheIdServer.Identity.BcryptPasswordHasherTest.csproj b/test/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasherTest/Aguacongas.TheIdServer.Identity.BcryptPasswordHasherTest.csproj new file mode 100644 index 000000000..149168298 --- /dev/null +++ b/test/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasherTest/Aguacongas.TheIdServer.Identity.BcryptPasswordHasherTest.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/test/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasherTest/BcryptPasswordHasherTest.cs b/test/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasherTest/BcryptPasswordHasherTest.cs new file mode 100644 index 000000000..b1b284f29 --- /dev/null +++ b/test/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasherTest/BcryptPasswordHasherTest.cs @@ -0,0 +1,76 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using System.Diagnostics.CodeAnalysis; + +namespace Aguacongas.TheIdServer.Identity.BcryptPasswordHasher.Test; + +public class BcryptPasswordHasherTest +{ + [Fact] + public void HashPassword_should_hash_password_according_to_options() + { + var settings = new BcryptPasswordHasherOptions(); + + var result = CreateHash(settings, Guid.NewGuid().ToString()); + + Assert.NotNull(result); + var binaryResult = Convert.FromBase64String(result); + + Assert.Equal(settings.HashPrefix, binaryResult[0]); + } + + [Fact] + public void VerifyHashedPassword_should_return_success_on_password_valid() + { + var settings = new BcryptPasswordHasherOptions(); + var options = Options.Create(settings); + var sut = new BcryptPasswordHasher(options); + var password = Guid.NewGuid().ToString(); + var hash = CreateHash(settings, password); + + var result = sut.VerifyHashedPassword(Guid.NewGuid().ToString(), hash, password); + + Assert.Equal(PasswordVerificationResult.Success, result); + } + + [Fact] + public void VerifyHashedPassword_should_return_rehash_needed_on_settings_change() + { + var settings = new BcryptPasswordHasherOptions + { + WorkFactor = 11 + }; + var password = Guid.NewGuid().ToString(); + var hash = CreateHash(settings, password); + + settings.WorkFactor = 12; + var options = Options.Create(settings); + var sut = new BcryptPasswordHasher(options); + + var result = sut.VerifyHashedPassword(Guid.NewGuid().ToString(), hash, password); + + Assert.Equal(PasswordVerificationResult.SuccessRehashNeeded, result); + } + + [Fact] + public void VerifyHashedPassword_should_return_fail_invalid_hash() + { + var settings = new BcryptPasswordHasherOptions(); + var hash = CreateHash(settings, Guid.NewGuid().ToString()); + + var options = Options.Create(settings); + var sut = new BcryptPasswordHasher(options); + + var result = sut.VerifyHashedPassword(Guid.NewGuid().ToString(), hash, Guid.NewGuid().ToString()); + + Assert.Equal(PasswordVerificationResult.Failed, result); + } + + private static string CreateHash(BcryptPasswordHasherOptions settings, string password) + { + var options = Options.Create(settings); + var sut = new BcryptPasswordHasher(options); + + return sut.HashPassword(Guid.NewGuid().ToString(), password); + } +} \ No newline at end of file diff --git a/test/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasherTest/Extensions/IdentityBuilderExtensionsTest.cs b/test/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasherTest/Extensions/IdentityBuilderExtensionsTest.cs new file mode 100644 index 000000000..ceca856e8 --- /dev/null +++ b/test/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasherTest/Extensions/IdentityBuilderExtensionsTest.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; + +namespace Aguacongas.TheIdServer.Identity.BcryptPasswordHasher.Test.Extensions; +public class IdentityBuilderExtensionsTest +{ + [Fact] + public void AddBcryptPasswordHasher_should_add_bcrypt_password_hasher_services() + { + var builder = new ServiceCollection() + .AddIdentityCore() + .AddBcryptPasswordHasher(); + var provider = builder.Services.BuildServiceProvider(); + + var hasher = provider.GetService>() as BcryptPasswordHasher; + + Assert.NotNull(hasher); + } +} diff --git a/test/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasherTest/Extensions/ServiceCollectionExtentsionsTest.cs b/test/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasherTest/Extensions/ServiceCollectionExtentsionsTest.cs new file mode 100644 index 000000000..af8adcecb --- /dev/null +++ b/test/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasherTest/Extensions/ServiceCollectionExtentsionsTest.cs @@ -0,0 +1,21 @@ +using Aguacongas.TheIdServer.Identity.BcryptPasswordHasher; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; + +namespace Aguacongas.TheIdServer.Identity.BCryptPasswordHasher.Test.Extensions; +public class ServiceCollectionExtentsionsTest +{ + [Fact] + public void AddBCryptPasswordHasher_should_add_BCrypt_password_hasher_services() + { + var provider = new ServiceCollection().AddBcryptPasswordHasher().BuildServiceProvider(); + + var hasher = provider.GetService>() as BcryptPasswordHasher; + + Assert.NotNull(hasher); + + // test options validation + var hash = hasher.HashPassword(Guid.NewGuid().ToString(), Guid.NewGuid().ToString()); + Assert.NotNull(hash); + } +} diff --git a/test/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasherTest/GlobalUsings.cs b/test/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasherTest/GlobalUsings.cs new file mode 100644 index 000000000..8c927eb74 --- /dev/null +++ b/test/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasherTest/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/test/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasherTest/Aguacongas.TheIdServer.Identity.ScryptPasswordHasherTest.csproj b/test/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasherTest/Aguacongas.TheIdServer.Identity.ScryptPasswordHasherTest.csproj new file mode 100644 index 000000000..fec5eed90 --- /dev/null +++ b/test/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasherTest/Aguacongas.TheIdServer.Identity.ScryptPasswordHasherTest.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/test/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasherTest/Extensions/IdentityBuilderExtensionsTest.cs b/test/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasherTest/Extensions/IdentityBuilderExtensionsTest.cs new file mode 100644 index 000000000..e26de22f0 --- /dev/null +++ b/test/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasherTest/Extensions/IdentityBuilderExtensionsTest.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; + +namespace Aguacongas.TheIdServer.Identity.ScryptPasswordHasher.Test.Extensions; +public class IdentityBuilderExtensionsTest +{ + [Fact] + public void AddScryptPasswordHasher_should_add_scrypt_password_hasher_services() + { + var builder = new ServiceCollection() + .AddIdentityCore() + .AddScryptPasswordHasher(); + var provider = builder.Services.BuildServiceProvider(); + + var hasher = provider.GetService>() as ScryptPasswordHasher; + + Assert.NotNull(hasher); + } +} diff --git a/test/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasherTest/Extensions/ServiceCollectionExtentsionsTest.cs b/test/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasherTest/Extensions/ServiceCollectionExtentsionsTest.cs new file mode 100644 index 000000000..e414f62ac --- /dev/null +++ b/test/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasherTest/Extensions/ServiceCollectionExtentsionsTest.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; + +namespace Aguacongas.TheIdServer.Identity.ScryptPasswordHasher.Test.Extensions; +public class ServiceCollectionExtentsionsTest +{ + [Fact] + public void AddScryptPasswordHasher_should_add_Scrypt_password_hasher_services() + { + var provider = new ServiceCollection().AddScryptPasswordHasher().BuildServiceProvider(); + + var hasher = provider.GetService>() as ScryptPasswordHasher; + + Assert.NotNull(hasher); + + // test options validation + var hash = hasher.HashPassword(Guid.NewGuid().ToString(), Guid.NewGuid().ToString()); + Assert.NotNull(hash); + } +} diff --git a/test/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasherTest/GlobalUsings.cs b/test/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasherTest/GlobalUsings.cs new file mode 100644 index 000000000..8c927eb74 --- /dev/null +++ b/test/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasherTest/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/test/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasherTest/ScryptPasswordHasherTest.cs b/test/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasherTest/ScryptPasswordHasherTest.cs new file mode 100644 index 000000000..799b0cf7a --- /dev/null +++ b/test/Identity/Aguacongas.TheIdServer.Identity.ScryptPasswordHasherTest/ScryptPasswordHasherTest.cs @@ -0,0 +1,77 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using Scrypt; + +namespace Aguacongas.TheIdServer.Identity.ScryptPasswordHasher.Test; + +public class ScryptPasswordHasherTest +{ + [Fact] + public void HashPassword_should_hash_password_according_to_options() + { + var settings = new ScryptPasswordHasherOptions(); + + var result = CreateHash(settings, Guid.NewGuid().ToString()); + + Assert.NotNull(result); + var binaryResult = Convert.FromBase64String(result); + + Assert.Equal(settings.HashPrefix, binaryResult[0]); + } + + [Fact] + public void VerifyHashedPassword_should_return_success_on_password_valid() + { + var settings = new ScryptPasswordHasherOptions(); + var options = Options.Create(settings); + var sut = new ScryptPasswordHasher(new ScryptEncoder(settings.IterationCount, settings.BlockSize, settings.ThreadCount), + options); + var password = Guid.NewGuid().ToString(); + var hash = CreateHash(settings, password); + + var result = sut.VerifyHashedPassword(Guid.NewGuid().ToString(), hash, password); + + Assert.Equal(PasswordVerificationResult.Success, result); + } + + [Fact] + public void VerifyHashedPassword_should_return_rehash_needed_on_settings_change() + { + var settings = new ScryptPasswordHasherOptions + { + IterationCount = 4 + }; + var password = Guid.NewGuid().ToString(); + var hash = CreateHash(settings, password); + + settings.IterationCount = 2; + var options = Options.Create(settings); + var sut = new ScryptPasswordHasher(new ScryptEncoder(settings.IterationCount, settings.BlockSize, settings.ThreadCount), options); + + var result = sut.VerifyHashedPassword(Guid.NewGuid().ToString(), hash, password); + + Assert.Equal(PasswordVerificationResult.SuccessRehashNeeded, result); + } + + [Fact] + public void VerifyHashedPassword_should_return_fail_invalid_hash() + { + var settings = new ScryptPasswordHasherOptions(); + var hash = CreateHash(settings, Guid.NewGuid().ToString()); + + var options = Options.Create(settings); + var sut = new ScryptPasswordHasher(new ScryptEncoder(settings.IterationCount, settings.BlockSize, settings.ThreadCount), options); + + var result = sut.VerifyHashedPassword(Guid.NewGuid().ToString(), hash, Guid.NewGuid().ToString()); + + Assert.Equal(PasswordVerificationResult.Failed, result); + } + + private static string CreateHash(ScryptPasswordHasherOptions settings, string password) + { + var options = Options.Create(settings); + var sut = new ScryptPasswordHasher(new ScryptEncoder(settings.IterationCount, settings.BlockSize, settings.ThreadCount), options); + + return sut.HashPassword(Guid.NewGuid().ToString(), password); + } +} \ No newline at end of file diff --git a/test/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasherTest/Aguacongas.TheIdServer.Identity.UpgradePasswordHasherTest.csproj b/test/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasherTest/Aguacongas.TheIdServer.Identity.UpgradePasswordHasherTest.csproj new file mode 100644 index 000000000..0e66a3513 --- /dev/null +++ b/test/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasherTest/Aguacongas.TheIdServer.Identity.UpgradePasswordHasherTest.csproj @@ -0,0 +1,32 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + diff --git a/test/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasherTest/Extensions/IdentityBuilderExtensionsTest.cs b/test/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasherTest/Extensions/IdentityBuilderExtensionsTest.cs new file mode 100644 index 000000000..3151a2bf9 --- /dev/null +++ b/test/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasherTest/Extensions/IdentityBuilderExtensionsTest.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; + +namespace Aguacongas.TheIdServer.Identity.UpgradePasswordHasher.Test.Extensions; +public class IdentityBuilderExtensionsTest +{ + [Fact] + public void AddUpgradePasswordHasher_should_add_upgrade_password_hasher_services() + { + var builder = new ServiceCollection() + .AddIdentityCore() + .AddUpgradePasswordHasher(); + var provider = builder.Services.BuildServiceProvider(); + + var hasher = provider.GetService>() as UpgradePasswordHasher; + + Assert.NotNull(hasher); + } +} diff --git a/test/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasherTest/Extensions/ServiceCollectionExtensionsTest.cs b/test/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasherTest/Extensions/ServiceCollectionExtensionsTest.cs new file mode 100644 index 000000000..e01d8bdb3 --- /dev/null +++ b/test/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasherTest/Extensions/ServiceCollectionExtensionsTest.cs @@ -0,0 +1,41 @@ +using Aguacongas.TheIdServer.Identity.UpgradePasswordHasher; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; + +namespace Aguacongas.TheIdServer.Identity.UpgradePasswordHasherTest.Extensions; +public class ServiceCollectionExtensionsTest +{ + [Fact] + public void AddUpgradePasswordHasher_should_add_upgrade_password_hasher_services() + { + var services = new ServiceCollection(); + services.AddIdentityCore(); + + var provider = services.AddArgon2PasswordHasher() + .AddBcryptPasswordHasher() + .AddScryptPasswordHasher() + .AddUpgradePasswordHasher(options => + { + options.HashPrefixMaps = new Dictionary + { + [0x00] = "Microsoft.AspNetCore.Identity.PasswordHasher", + [0x01] = "Microsoft.AspNetCore.Identity.PasswordHasher", + [0xA2] = "Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Argon2PasswordHasher", + [0x0C] = "Aguacongas.TheIdServer.Identity.ScryptPasswordHasher.ScryptPasswordHasher", + [0xBC] = "Aguacongas.TheIdServer.Identity.BcryptPasswordHasher.BcryptPasswordHasher" + }; + options.UsePasswordHasherTypeName = "Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Argon2PasswordHasher"; + }) + .BuildServiceProvider(); + + using var scope = provider.CreateScope(); + var hasher = scope.ServiceProvider.GetService>() as UpgradePasswordHasher; + + Assert.NotNull(hasher); + + // test options validation + var hash = hasher.HashPassword(Guid.NewGuid().ToString(), Guid.NewGuid().ToString()); + Assert.NotNull(hash); + } + +} diff --git a/test/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasherTest/GlobalUsings.cs b/test/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasherTest/GlobalUsings.cs new file mode 100644 index 000000000..8c927eb74 --- /dev/null +++ b/test/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasherTest/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/test/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasherTest/UpgradePasswordHasherTest.cs b/test/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasherTest/UpgradePasswordHasherTest.cs new file mode 100644 index 000000000..252d85bdd --- /dev/null +++ b/test/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasherTest/UpgradePasswordHasherTest.cs @@ -0,0 +1,120 @@ +using Aguacongas.TheIdServer.Identity.UpgradePasswordHasher; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using System.Text.Json; + +namespace Aguacongas.TheIdServer.Identity.UpgradePasswordHasherTest; + +public class UpgradePasswordHasherTest +{ + [Fact] + public void HashPassword_should_hash_password_with_configured_hascher() + { + var settings = new UpgradePasswordHasherOptions + { + HashPrefixMaps = new Dictionary + { + [0x0C] = "Aguacongas.TheIdServer.Identity.ScryptPasswordHasher.ScryptPasswordHasher" + }, + UsePasswordHasherTypeName = "Aguacongas.TheIdServer.Identity.ScryptPasswordHasher.ScryptPasswordHasher" + }; + var provider = new ServiceCollection().AddScryptPasswordHasher() + .BuildServiceProvider(); + + var sut = new UpgradePasswordHasher(provider, Options.Create(settings!)); + + var hash = sut.HashPassword(Guid.NewGuid().ToString(), Guid.NewGuid().ToString()); + Assert.NotNull(hash); + + var decodedHash = Convert.FromBase64String(hash); + Assert.Equal(0x0C, decodedHash[0]); + } + + [Fact] + public void VerifyHashedPassword_should_return_success_on_password_valid() + { + var settings = new UpgradePasswordHasherOptions + { + HashPrefixMaps = new Dictionary + { + [0x0C] = "Aguacongas.TheIdServer.Identity.ScryptPasswordHasher.ScryptPasswordHasher" + }, + UsePasswordHasherTypeName = "Aguacongas.TheIdServer.Identity.ScryptPasswordHasher.ScryptPasswordHasher" + }; + var provider = new ServiceCollection().AddScryptPasswordHasher() + .BuildServiceProvider(); + + var sut = new UpgradePasswordHasher(provider, Options.Create(settings!)); + + var user = Guid.NewGuid().ToString(); + var password = Guid.NewGuid().ToString(); + var hash = sut.HashPassword(user, password); + var result = sut.VerifyHashedPassword(user, hash, password); + + Assert.Equal(PasswordVerificationResult.Success, result); + } + + [Fact] + public void VerifyHashedPassword_should_return_rehash_needed_on_password_valid_and_configuration_changed() + { + var settings = new UpgradePasswordHasherOptions + { + HashPrefixMaps = new Dictionary + { + [0x0C] = "Aguacongas.TheIdServer.Identity.ScryptPasswordHasher.ScryptPasswordHasher", + [0xBC] = "Aguacongas.TheIdServer.Identity.BcryptPasswordHasher.BcryptPasswordHasher" + }, + UsePasswordHasherTypeName = "Aguacongas.TheIdServer.Identity.BcryptPasswordHasher.BcryptPasswordHasher" + }; + + + var serialized = JsonSerializer.Serialize(settings); + + var provider = new ServiceCollection() + .AddBcryptPasswordHasher() + .AddScryptPasswordHasher() + .BuildServiceProvider(); + + var scryptHasher = provider.GetRequiredService>(); + var user = Guid.NewGuid().ToString(); + var password = Guid.NewGuid().ToString(); + var hash = scryptHasher.HashPassword(user, password); + + var sut = new UpgradePasswordHasher(provider, Options.Create(settings!)); + + var result = sut.VerifyHashedPassword(user, hash, password); + + Assert.Equal(PasswordVerificationResult.SuccessRehashNeeded, result); + } + + [Fact] + public void VerifyHashedPassword_should_return_failed_on_dead_line_expired_and_rehash_needed() + { + var settings = new UpgradePasswordHasherOptions + { + HashPrefixMaps = new Dictionary + { + [0xA2] = "Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Argon2PasswordHasher", + [0xBC] = "Aguacongas.TheIdServer.Identity.BcryptPasswordHasher.BcryptPasswordHasher" + }, + UsePasswordHasherTypeName = "Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Argon2PasswordHasher", + DeadLineUtc = DateTime.UtcNow.AddDays(-1) + }; + var provider = new ServiceCollection() + .AddArgon2PasswordHasher() + .AddBcryptPasswordHasher() + .BuildServiceProvider(); + + var bcryptHasher = provider.GetRequiredService>(); + var user = Guid.NewGuid().ToString(); + var password = Guid.NewGuid().ToString(); + var hash = bcryptHasher.HashPassword(user, password); + + var sut = new UpgradePasswordHasher(provider, Options.Create(settings!)); + + var result = sut.VerifyHashedPassword(user, hash, password); + + Assert.Equal(PasswordVerificationResult.Failed, result); + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Components.Testing/ContainerComponent.cs b/test/Microsoft.AspNetCore.Components.Testing/ContainerComponent.cs deleted file mode 100644 index 330a6c245..000000000 --- a/test/Microsoft.AspNetCore.Components.Testing/ContainerComponent.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Project: Aguafrommars/TheIdServer -// Copyright (c) 2021 @Olivier Lefebvre -using Microsoft.AspNetCore.Components.RenderTree; -using System; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Threading.Tasks; - -namespace Microsoft.AspNetCore.Components.Testing -{ - // This provides the ability for test code to trigger rendering at arbitrary times, - // and to supply arbitrary parameters to the component being tested (including ones - // flagged as 'cascading'). - // - // This also avoids the use of Renderer's RenderRootComponentAsync APIs, which are - // not a good entrypoint for unit tests, because their asynchrony is all about waiting - // for quiescence. We don't want that in tests because we want to assert about all - // possible states, including loading states. - - [SuppressMessage("Usage", "BL0006:Do not use RenderTree types", Justification = "")] - internal class ContainerComponent : IComponent - { - private readonly TestRenderer _renderer; - private RenderHandle _renderHandle; - private readonly int _componentId; - - public ContainerComponent(TestRenderer renderer) - { - _renderer = renderer; - _componentId = renderer.AttachTestRootComponent(this); - } - - public void Attach(RenderHandle renderHandle) - { - _renderHandle = renderHandle; - } - - public Task SetParametersAsync(ParameterView parameters) - { - throw new NotImplementedException($"{nameof(ContainerComponent)} shouldn't receive any parameters"); - } - - public (int, object) FindComponentUnderTest() - { - var ownFrames = _renderer.GetCurrentRenderTreeFrames(_componentId); - if (ownFrames.Count == 0) - { - throw new InvalidOperationException($"{nameof(ContainerComponent)} hasn't yet rendered"); - } - - ref var childComponentFrame = ref ownFrames.Array[0]; - Debug.Assert(childComponentFrame.FrameType == RenderTreeFrameType.Component); - Debug.Assert(childComponentFrame.Component != null); - return (childComponentFrame.ComponentId, childComponentFrame.Component); - } - - public void RenderComponentUnderTest(Type componentType, ParameterView parameters) - { - _renderer.DispatchAndAssertNoSynchronousErrors(() => - { - _renderHandle.Render(builder => - { - builder.OpenComponent(0, componentType); - - foreach (var parameterValue in parameters) - { - builder.AddAttribute(1, parameterValue.Name, parameterValue.Value); - } - - builder.CloseComponent(); - }); - }); - } - } -} diff --git a/test/Microsoft.AspNetCore.Components.Testing/EventDispatchExtensions.cs b/test/Microsoft.AspNetCore.Components.Testing/EventDispatchExtensions.cs deleted file mode 100644 index facc71009..000000000 --- a/test/Microsoft.AspNetCore.Components.Testing/EventDispatchExtensions.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Project: Aguafrommars/TheIdServer -// Copyright (c) 2021 @Olivier Lefebvre -using HtmlAgilityPack; -using Microsoft.AspNetCore.Components.RenderTree; -using Microsoft.AspNetCore.Components.Web; -using System; -using System.Diagnostics.CodeAnalysis; -using System.Threading.Tasks; - -namespace Microsoft.AspNetCore.Components.Testing -{ - public static class EventDispatchExtensions - { - public static Task ClickAsync(this HtmlNode element) - { - return element.TriggerEventAsync("onclick", new MouseEventArgs()); - } - - public static Task SubmitAsync(this HtmlNode element) - { - return element.TriggerEventAsync("onsubmit", new EventArgs()); - } - - public static Task ChangeAsync(this HtmlNode element, string newValue) - { - return element.TriggerEventAsync("onchange", new ChangeEventArgs { Value = newValue }); - } - - public static Task ChangeAsync(this HtmlNode element, bool newValue) - { - return element.TriggerEventAsync("onchange", new ChangeEventArgs { Value = newValue }); - } - - [SuppressMessage("Usage", "BL0006:Do not use RenderTree types", Justification = "")] - public static Task TriggerEventAsync(this HtmlNode element, string attributeName, EventArgs eventArgs) - { - var eventHandlerIdString = element.GetAttributeValue(attributeName, string.Empty); - if (string.IsNullOrEmpty(eventHandlerIdString)) - { - throw new ArgumentException($"The element does not have an event handler for the event '{attributeName}'."); - } - var eventHandlerId = ulong.Parse(eventHandlerIdString); - - var renderer = ((TestHtmlDocument)element.OwnerDocument).Renderer; - return renderer.DispatchEventAsync(eventHandlerId, - new EventFieldInfo(), - eventArgs); - } - } -} diff --git a/test/Microsoft.AspNetCore.Components.Testing/Htmlizer.cs b/test/Microsoft.AspNetCore.Components.Testing/Htmlizer.cs deleted file mode 100644 index e827de324..000000000 --- a/test/Microsoft.AspNetCore.Components.Testing/Htmlizer.cs +++ /dev/null @@ -1,231 +0,0 @@ -// Project: Aguafrommars/TheIdServer -// Copyright (c) 2021 @Olivier Lefebvre -using Microsoft.AspNetCore.Components.RenderTree; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Text.Encodings.Web; - -namespace Microsoft.AspNetCore.Components.Testing -{ - [SuppressMessage("Major Bug", "S4143:Collection elements should not be replaced unconditionally", Justification = "")] - [SuppressMessage("Usage", "BL0006:Do not use RenderTree types", Justification = "")] - internal class Htmlizer - { - private static readonly HtmlEncoder _htmlEncoder = HtmlEncoder.Default; - - private static readonly HashSet _selfClosingElements = new HashSet(StringComparer.OrdinalIgnoreCase) - { - "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr" - }; - - public static string GetHtml(TestRenderer renderer, int componentId) - { - var frames = renderer.GetCurrentRenderTreeFrames(componentId); - var context = new HtmlRenderingContext(renderer); - var newPosition = RenderFrames(context, frames, 0, frames.Count); - Debug.Assert(newPosition == frames.Count); - return string.Join(string.Empty, context.Result); - } - - private static int RenderFrames(HtmlRenderingContext context, ArrayRange frames, int position, int maxElements) - { - var nextPosition = position; - var endPosition = position + maxElements; - while (position < endPosition) - { - nextPosition = RenderCore(context, frames, position); - if (position == nextPosition) - { - break; - } - position = nextPosition; - } - - return nextPosition; - } - - private static int RenderCore( - HtmlRenderingContext context, - ArrayRange frames, - int position) - { - ref var frame = ref frames.Array[position]; - switch (frame.FrameType) - { - case RenderTreeFrameType.Element: - return RenderElement(context, frames, position); - case RenderTreeFrameType.Attribute: - throw new InvalidOperationException($"Attributes should only be encountered within {nameof(RenderElement)}"); - case RenderTreeFrameType.Text: - context.Result.Add(_htmlEncoder.Encode(frame.TextContent)); - return ++position; - case RenderTreeFrameType.Markup: - context.Result.Add(frame.MarkupContent); - return ++position; - case RenderTreeFrameType.Component: - return RenderChildComponent(context, frames, position); - case RenderTreeFrameType.Region: - return RenderFrames(context, frames, position + 1, frame.RegionSubtreeLength - 1); - default: - return ++position; - } - } - - private static int RenderChildComponent( - HtmlRenderingContext context, - ArrayRange frames, - int position) - { - ref var frame = ref frames.Array[position]; - var childFrames = context.Renderer.GetCurrentRenderTreeFrames(frame.ComponentId); - RenderFrames(context, childFrames, 0, childFrames.Count); - return position + frame.ComponentSubtreeLength; - } - - private static int RenderElement( - HtmlRenderingContext context, - ArrayRange frames, - int position) - { - ref var frame = ref frames.Array[position]; - var result = context.Result; - result.Add("<"); - result.Add(frame.ElementName); - var afterAttributes = RenderAttributes(context, frames, position + 1, frame.ElementSubtreeLength - 1, out var capturedValueAttribute); - - // When we see an