diff --git a/.github/workflows/catalog-api.yml b/.github/workflows/catalog-api.yml index e3e8e9afe..6def653d0 100644 --- a/.github/workflows/catalog-api.yml +++ b/.github/workflows/catalog-api.yml @@ -22,7 +22,7 @@ on: env: SERVICE: catalog-api IMAGE: catalog.api - DOTNET_VERSION: 6.0.x + DOTNET_VERSION: 7.0.x PROJECT_PATH: Services/Catalog/Catalog.API TESTS_PATH: Services/Catalog/Catalog.UnitTests diff --git a/.github/workflows/ordering-api.yml b/.github/workflows/ordering-api.yml index 7f6c3fff5..617fba8d7 100644 --- a/.github/workflows/ordering-api.yml +++ b/.github/workflows/ordering-api.yml @@ -22,7 +22,7 @@ on: env: SERVICE: ordering-api IMAGE: ordering.api - DOTNET_VERSION: 6.0.x + DOTNET_VERSION: 7.0.x PROJECT_PATH: Services/Ordering/Ordering.API TESTS_PATH: Services/Ordering/Ordering.UnitTests diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile index aab4164b7..294e04375 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile @@ -1,8 +1,8 @@ -FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base WORKDIR /app EXPOSE 80 -FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build WORKDIR /src # It's important to keep lines from here down to "COPY . ." identical in all Dockerfiles diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile.develop b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile.develop index 9a907ee69..41361d3a1 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile.develop +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile.develop @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:6.0 +FROM mcr.microsoft.com/dotnet/sdk:7.0 ARG BUILD_CONFIGURATION=Debug ENV ASPNETCORE_ENVIRONMENT=Development ENV DOTNET_USE_POLLING_FILE_WATCHER=true diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/GlobalUsings.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/GlobalUsings.cs index 881670f5e..ecae40e62 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/GlobalUsings.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/GlobalUsings.cs @@ -39,3 +39,4 @@ global using System.Text.Json; global using System.Threading.Tasks; global using System.Threading; global using System; +global using Microsoft.IdentityModel.Tokens; \ No newline at end of file diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj index 13c443025..707bd266a 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 Mobile.Shopping.HttpAggregator Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator ..\..\..\docker-compose.dcproj @@ -14,19 +14,20 @@ - - - - - - - - + + + + + + + + + - - - - + + + + diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Startup.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Startup.cs index 3f988395a..1945cebaa 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Startup.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Startup.cs @@ -1,4 +1,7 @@ -namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; + +namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator; public class Startup { @@ -86,7 +89,7 @@ public static class ServiceCollectionExtensions services.AddSwaggerGen(options => { - options.DescribeAllEnumsAsStrings(); + //options.DescribeAllEnumsAsStrings(); options.SwaggerDoc("v1", new OpenApiInfo { Title = "Shopping Aggregator for Mobile Clients", @@ -143,10 +146,26 @@ public static class ServiceCollectionExtensions options.Authority = identityUrl; options.RequireHttpsMetadata = false; options.Audience = "mobileshoppingagg"; + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateAudience = false + }; }); return services; } + public static IServiceCollection AddCustomAuthorization(this IServiceCollection services, IConfiguration configuration) + { + services.AddAuthorization(options => + { + options.AddPolicy("ApiScope", policy => + { + policy.RequireAuthenticatedUser(); + policy.RequireClaim("scope", "mobileshoppingagg"); + }); + }); + return services; + } public static IServiceCollection AddHttpServices(this IServiceCollection services) { diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile b/src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile index 8761763a1..5cf1c7332 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile @@ -1,8 +1,8 @@ -FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base WORKDIR /app EXPOSE 80 -FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build WORKDIR /src # It's important to keep lines from here down to "COPY . ." identical in all Dockerfiles diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile.develop b/src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile.develop index 268a9c3ba..f3a93760d 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile.develop +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile.develop @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:6.0 +FROM mcr.microsoft.com/dotnet/sdk:7.0 ARG BUILD_CONFIGURATION=Debug ENV ASPNETCORE_ENVIRONMENT=Development ENV DOTNET_USE_POLLING_FILE_WATCHER=true diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/GlobalUsings.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/GlobalUsings.cs index 6162c557e..58765400a 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/GlobalUsings.cs +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/GlobalUsings.cs @@ -39,3 +39,4 @@ global using System.Text.Json; global using System.Threading.Tasks; global using System.Threading; global using System; +global using Microsoft.IdentityModel.Tokens; diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Startup.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Startup.cs index 6e8e66931..272ab3587 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Startup.cs +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Startup.cs @@ -1,4 +1,7 @@ -namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; + +namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator; public class Startup { @@ -22,6 +25,7 @@ public class Startup services.AddCustomMvc(Configuration) .AddCustomAuthentication(Configuration) + //.AddCustomAuthorization(Configuration) .AddDevspaces() .AddApplicationServices() .AddGrpcServices(); @@ -83,22 +87,20 @@ public static class ServiceCollectionExtensions JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); var identityUrl = configuration.GetValue("urls:identity"); - services.AddAuthentication(options => - { - options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; - - }) + services.AddAuthentication("Bearer") .AddJwtBearer(options => { options.Authority = identityUrl; options.RequireHttpsMetadata = false; options.Audience = "webshoppingagg"; + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateAudience = false + }; }); return services; } - public static IServiceCollection AddCustomMvc(this IServiceCollection services, IConfiguration configuration) { services.AddOptions(); @@ -109,7 +111,7 @@ public static class ServiceCollectionExtensions services.AddSwaggerGen(options => { - options.DescribeAllEnumsAsStrings(); + //options.DescribeAllEnumsAsStrings(); options.SwaggerDoc("v1", new OpenApiInfo { diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj b/src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj index 921d5b709..d8104f2d1 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 Web.Shopping.HttpAggregator Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator ..\..\..\docker-compose.dcproj @@ -14,20 +14,21 @@ - - - - - - - - - + + + + + + + + + + - - - - + + + + diff --git a/src/BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj b/src/BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj index 3398f17e6..d3b1f9098 100644 --- a/src/BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj +++ b/src/BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj @@ -1,11 +1,11 @@  - net6.0 + net7.0 - - + + diff --git a/src/BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj b/src/BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj index 7b75841ec..c1c2ae2db 100644 --- a/src/BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj +++ b/src/BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj @@ -1,16 +1,16 @@  - net6.0 + net7.0 - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/BuildingBlocks/EventBus/EventBus/EventBus.csproj b/src/BuildingBlocks/EventBus/EventBus/EventBus.csproj index 37396d3ec..6d33cff34 100644 --- a/src/BuildingBlocks/EventBus/EventBus/EventBus.csproj +++ b/src/BuildingBlocks/EventBus/EventBus/EventBus.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 Microsoft.eShopOnContainers.BuildingBlocks.EventBus diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj index b6b23483c..6a1b9a477 100644 --- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj +++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj @@ -1,16 +1,16 @@  - net6.0 + net7.0 Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ - + - - - + + + diff --git a/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.csproj b/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.csproj index e725de1c7..4b02b0d90 100644 --- a/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.csproj +++ b/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.csproj @@ -1,15 +1,15 @@  - net6.0 + net7.0 Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus - - + + - + diff --git a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEF.csproj b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEF.csproj index c68db458e..0343bb20c 100644 --- a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEF.csproj +++ b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEF.csproj @@ -1,18 +1,18 @@  - net6.0 + net7.0 Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + diff --git a/src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHost.Customization.csproj b/src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHost.Customization.csproj index 5c3ba6721..c3bad0c69 100644 --- a/src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHost.Customization.csproj +++ b/src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHost.Customization.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 false @@ -10,16 +10,16 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - + + + + + + diff --git a/src/Services/Basket/Basket.API/Basket.API.csproj b/src/Services/Basket/Basket.API/Basket.API.csproj index ebb224824..1cf1b7dfc 100644 --- a/src/Services/Basket/Basket.API/Basket.API.csproj +++ b/src/Services/Basket/Basket.API/Basket.API.csproj @@ -1,6 +1,6 @@  - net6.0 + net7.0 $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; ..\..\..\..\docker-compose.dcproj false @@ -14,36 +14,37 @@ - + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - + + + + + - - - - - - + + + + + + diff --git a/src/Services/Basket/Basket.API/Dockerfile b/src/Services/Basket/Basket.API/Dockerfile index 9cd4abba8..078257cc0 100644 --- a/src/Services/Basket/Basket.API/Dockerfile +++ b/src/Services/Basket/Basket.API/Dockerfile @@ -1,8 +1,8 @@ -FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base WORKDIR /app EXPOSE 80 -FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build WORKDIR /src # It's important to keep lines from here down to "COPY . ." identical in all Dockerfiles diff --git a/src/Services/Basket/Basket.API/Dockerfile.develop b/src/Services/Basket/Basket.API/Dockerfile.develop index 8f8312673..b29a1aa8a 100644 --- a/src/Services/Basket/Basket.API/Dockerfile.develop +++ b/src/Services/Basket/Basket.API/Dockerfile.develop @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:6.0 +FROM mcr.microsoft.com/dotnet/sdk:7.0 ARG BUILD_CONFIGURATION=Debug ENV ASPNETCORE_ENVIRONMENT=Development ENV DOTNET_USE_POLLING_FILE_WATCHER=true diff --git a/src/Services/Basket/Basket.API/GlobalUsings.cs b/src/Services/Basket/Basket.API/GlobalUsings.cs index 75f7a878e..b2e13ab17 100644 --- a/src/Services/Basket/Basket.API/GlobalUsings.cs +++ b/src/Services/Basket/Basket.API/GlobalUsings.cs @@ -58,4 +58,5 @@ global using System.Net; global using System.Security.Claims; global using System.Text.Json; global using System.Threading.Tasks; -global using System; \ No newline at end of file +global using System; +global using Microsoft.IdentityModel.Tokens; \ No newline at end of file diff --git a/src/Services/Basket/Basket.API/Program.cs b/src/Services/Basket/Basket.API/Program.cs index fd57afc82..e7208476d 100644 --- a/src/Services/Basket/Basket.API/Program.cs +++ b/src/Services/Basket/Basket.API/Program.cs @@ -60,7 +60,7 @@ Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) .Enrich.FromLogContext() .WriteTo.Console() .WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) - .WriteTo.Http(string.IsNullOrWhiteSpace(logstashUrl) ? "http://logstash:8080" : logstashUrl) + .WriteTo.Http(string.IsNullOrWhiteSpace(logstashUrl) ? "http://logstash:8080" : logstashUrl, null) .ReadFrom.Configuration(configuration) .CreateLogger(); } diff --git a/src/Services/Basket/Basket.API/Startup.cs b/src/Services/Basket/Basket.API/Startup.cs index 7e0142c2c..89b8934e9 100644 --- a/src/Services/Basket/Basket.API/Startup.cs +++ b/src/Services/Basket/Basket.API/Startup.cs @@ -1,3 +1,6 @@ +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; + namespace Microsoft.eShopOnContainers.Services.Basket.API; public class Startup { @@ -214,16 +217,20 @@ public class Startup var identityUrl = Configuration.GetValue("IdentityUrl"); - services.AddAuthentication(options => - { - options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; - - }).AddJwtBearer(options => + services.AddAuthentication("Bearer").AddJwtBearer(options => { options.Authority = identityUrl; options.RequireHttpsMetadata = false; options.Audience = "basket"; + options.TokenValidationParameters.ValidateAudience = false; + }); + services.AddAuthorization(options => + { + options.AddPolicy("ApiScope", policy => + { + policy.RequireAuthenticatedUser(); + policy.RequireClaim("scope", "basket"); + }); }); } diff --git a/src/Services/Basket/Basket.FunctionalTests/Basket.FunctionalTests.csproj b/src/Services/Basket/Basket.FunctionalTests/Basket.FunctionalTests.csproj index db0b48fdb..002bd16c4 100644 --- a/src/Services/Basket/Basket.FunctionalTests/Basket.FunctionalTests.csproj +++ b/src/Services/Basket/Basket.FunctionalTests/Basket.FunctionalTests.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 false @@ -16,16 +16,16 @@ - - - - - - + + + + + + all runtime; build; native; contentfiles; analyzers - + diff --git a/src/Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj b/src/Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj index 039258c2f..73a4a773f 100644 --- a/src/Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj +++ b/src/Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj @@ -1,23 +1,23 @@  - net6.0 + net7.0 false false - - - - - - - + + + + + + + all runtime; build; native; contentfiles; analyzers - + diff --git a/src/Services/Catalog/Catalog.API/Catalog.API.csproj b/src/Services/Catalog/Catalog.API/Catalog.API.csproj index b78ce2af3..362401a4d 100644 --- a/src/Services/Catalog/Catalog.API/Catalog.API.csproj +++ b/src/Services/Catalog/Catalog.API/Catalog.API.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 portable true Catalog.API @@ -41,37 +41,40 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/Services/Catalog/Catalog.API/Dockerfile b/src/Services/Catalog/Catalog.API/Dockerfile index e491c2110..f7ab4232c 100644 --- a/src/Services/Catalog/Catalog.API/Dockerfile +++ b/src/Services/Catalog/Catalog.API/Dockerfile @@ -1,9 +1,9 @@ -FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base WORKDIR /app EXPOSE 80 EXPOSE 443 -FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build WORKDIR /src # It's important to keep lines from here down to "COPY . ." identical in all Dockerfiles diff --git a/src/Services/Catalog/Catalog.API/Dockerfile.develop b/src/Services/Catalog/Catalog.API/Dockerfile.develop index 513396298..9aefa6eff 100644 --- a/src/Services/Catalog/Catalog.API/Dockerfile.develop +++ b/src/Services/Catalog/Catalog.API/Dockerfile.develop @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:6.0 +FROM mcr.microsoft.com/dotnet/sdk:7.0 ARG BUILD_CONFIGURATION=Debug ENV ASPNETCORE_ENVIRONMENT=Development ENV DOTNET_USE_POLLING_FILE_WATCHER=true diff --git a/src/Services/Catalog/Catalog.API/Program.cs b/src/Services/Catalog/Catalog.API/Program.cs index e5ef82aff..0e51b9a25 100644 --- a/src/Services/Catalog/Catalog.API/Program.cs +++ b/src/Services/Catalog/Catalog.API/Program.cs @@ -66,7 +66,7 @@ Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) .Enrich.FromLogContext() .WriteTo.Console() .WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) - .WriteTo.Http(string.IsNullOrWhiteSpace(logstashUrl) ? "http://logstash:8080" : logstashUrl) + .WriteTo.Http(string.IsNullOrWhiteSpace(logstashUrl) ? "http://logstash:8080" : logstashUrl,null) .ReadFrom.Configuration(configuration) .CreateLogger(); } diff --git a/src/Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj b/src/Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj index cb8ec5630..9597df839 100644 --- a/src/Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj +++ b/src/Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 false @@ -33,14 +33,14 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers - + diff --git a/src/Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj b/src/Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj index 513174e1d..b2d5f0b77 100644 --- a/src/Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj +++ b/src/Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj @@ -1,21 +1,21 @@  - net6.0 + net7.0 false false - - - - - + + + + + all runtime; build; native; contentfiles; analyzers - + diff --git a/src/Services/Identity/Identity.API/Certificate/Certificate.cs b/src/Services/Identity/Identity.API/Certificate/Certificate.cs deleted file mode 100644 index 91aa74a6c..000000000 --- a/src/Services/Identity/Identity.API/Certificate/Certificate.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Identity.API.Certificates -{ - static class Certificate - { - public static X509Certificate2 Get() - { - var assembly = typeof(Certificate).GetTypeInfo().Assembly; - var names = assembly.GetManifestResourceNames(); - - /*********************************************************************************************** - * Please note that here we are using a local certificate only for testing purposes. In a - * real environment the certificate should be created and stored in a secure way, which is out - * of the scope of this project. - **********************************************************************************************/ - using var stream = assembly.GetManifestResourceStream("Identity.API.Certificate.idsrv3test.pfx"); - return new X509Certificate2(ReadStream(stream), "idsrv3test"); - } - - private static byte[] ReadStream(Stream input) - { - byte[] buffer = new byte[16 * 1024]; - using MemoryStream ms = new MemoryStream(); - int read; - while ((read = input.Read(buffer, 0, buffer.Length)) > 0) - { - ms.Write(buffer, 0, read); - } - return ms.ToArray(); - } - } -} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Certificate/idsrv3test.pfx b/src/Services/Identity/Identity.API/Certificate/idsrv3test.pfx deleted file mode 100644 index 0247dea03..000000000 Binary files a/src/Services/Identity/Identity.API/Certificate/idsrv3test.pfx and /dev/null differ diff --git a/src/Services/Identity/Identity.API/Configuration/Config.cs b/src/Services/Identity/Identity.API/Configuration/Config.cs index 6d5393bde..a8f3b1a40 100644 --- a/src/Services/Identity/Identity.API/Configuration/Config.cs +++ b/src/Services/Identity/Identity.API/Configuration/Config.cs @@ -1,4 +1,4 @@ -using IdentityServer4.Models; +using Duende.IdentityServer.Models; namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration { @@ -6,7 +6,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration { // ApiResources define the apis in your system public static IEnumerable GetApis() - { + { return new List { new ApiResource("orders", "Orders Service"), @@ -18,6 +18,21 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration }; } + // ApiScope is used to protect the API + //The effect is the same as that of API resources in IdentityServer 3.x + public static IEnumerable GetApiScopes() + { + return new List + { + new ApiScope("orders", "Orders Service"), + new ApiScope("basket", "Basket Service"), + new ApiScope("mobileshoppingagg", "Mobile Shopping Aggregator"), + new ApiScope("webshoppingagg", "Web Shopping Aggregator"), + new ApiScope("orders.signalrhub", "Ordering Signalr Hub"), + new ApiScope("webhooks", "Webhooks registration Service"), + }; + } + // Identity resources are data like user ID, name, or email address of a user // see: http://docs.identityserver.io/en/release/configuration/resources.html public static IEnumerable GetResources() @@ -30,7 +45,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration } // client want to access resources (aka scopes) - public static IEnumerable GetClients(Dictionary clientsUrl) + public static IEnumerable GetClients(IConfiguration configuration) { return new List { @@ -41,10 +56,10 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration ClientName = "eShop SPA OpenId Client", AllowedGrantTypes = GrantTypes.Implicit, AllowAccessTokensViaBrowser = true, - RedirectUris = { $"{clientsUrl["Spa"]}/" }, + RedirectUris = { $"{configuration["SpaClient"]}/" }, RequireConsent = false, - PostLogoutRedirectUris = { $"{clientsUrl["Spa"]}/" }, - AllowedCorsOrigins = { $"{clientsUrl["Spa"]}" }, + PostLogoutRedirectUris = { $"{configuration["SpaClient"]}/" }, + AllowedCorsOrigins = { $"{configuration["SpaClient"]}" }, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, @@ -63,13 +78,13 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration AllowedGrantTypes = GrantTypes.Hybrid, //Used to retrieve the access token on the back channel. ClientSecrets = - { + { new Secret("secret".Sha256()) }, - RedirectUris = { clientsUrl["Xamarin"] }, + RedirectUris = { configuration["XamarinCallback"] }, RequireConsent = false, RequirePkce = true, - PostLogoutRedirectUris = { $"{clientsUrl["Xamarin"]}/Account/Redirecting" }, + PostLogoutRedirectUris = { $"{configuration["XamarinCallback"]}/Account/Redirecting" }, //AllowedCorsOrigins = { "http://eshopxamarin" }, AllowedScopes = new List { @@ -91,22 +106,23 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration ClientName = "MVC Client", ClientSecrets = new List { - + new Secret("secret".Sha256()) }, - ClientUri = $"{clientsUrl["Mvc"]}", // public uri of the client - AllowedGrantTypes = GrantTypes.Hybrid, + ClientUri = $"{configuration["MvcClient"]}", // public uri of the client + AllowedGrantTypes = GrantTypes.Code, AllowAccessTokensViaBrowser = false, RequireConsent = false, AllowOfflineAccess = true, AlwaysIncludeUserClaimsInIdToken = true, + RequirePkce = false, RedirectUris = new List { - $"{clientsUrl["Mvc"]}/signin-oidc" + $"{configuration["MvcClient"]}/signin-oidc" }, PostLogoutRedirectUris = new List { - $"{clientsUrl["Mvc"]}/signout-callback-oidc" + $"{configuration["MvcClient"]}/signout-callback-oidc" }, AllowedScopes = new List { @@ -130,19 +146,19 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration { new Secret("secret".Sha256()) }, - ClientUri = $"{clientsUrl["WebhooksWeb"]}", // public uri of the client - AllowedGrantTypes = GrantTypes.Hybrid, + ClientUri = $"{configuration["WebhooksWebClient"]}", // public uri of the client + AllowedGrantTypes = GrantTypes.Code, AllowAccessTokensViaBrowser = false, RequireConsent = false, AllowOfflineAccess = true, AlwaysIncludeUserClaimsInIdToken = true, RedirectUris = new List { - $"{clientsUrl["WebhooksWeb"]}/signin-oidc" + $"{configuration["WebhooksWebClient"]}/signin-oidc" }, PostLogoutRedirectUris = new List { - $"{clientsUrl["WebhooksWeb"]}/signout-callback-oidc" + $"{configuration["WebhooksWebClient"]}/signout-callback-oidc" }, AllowedScopes = new List { @@ -162,18 +178,18 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration { new Secret("secret".Sha256()) }, - ClientUri = $"{clientsUrl["Mvc"]}", // public uri of the client - AllowedGrantTypes = GrantTypes.Hybrid, + ClientUri = $"{configuration["Mvc"]}", // public uri of the client + AllowedGrantTypes = GrantTypes.Code, AllowAccessTokensViaBrowser = true, RequireConsent = false, AllowOfflineAccess = true, RedirectUris = new List { - $"{clientsUrl["Mvc"]}/signin-oidc" + $"{configuration["MvcClient"]}/signin-oidc" }, PostLogoutRedirectUris = new List { - $"{clientsUrl["Mvc"]}/signout-callback-oidc" + $"{configuration["MvcClient"]}/signout-callback-oidc" }, AllowedScopes = new List { @@ -193,8 +209,8 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration AllowedGrantTypes = GrantTypes.Implicit, AllowAccessTokensViaBrowser = true, - RedirectUris = { $"{clientsUrl["BasketApi"]}/swagger/oauth2-redirect.html" }, - PostLogoutRedirectUris = { $"{clientsUrl["BasketApi"]}/swagger/" }, + RedirectUris = { $"{configuration["BasketApiClient"]}/swagger/oauth2-redirect.html" }, + PostLogoutRedirectUris = { $"{configuration["BasketApiClient"]}/swagger/" }, AllowedScopes = { @@ -208,8 +224,8 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration AllowedGrantTypes = GrantTypes.Implicit, AllowAccessTokensViaBrowser = true, - RedirectUris = { $"{clientsUrl["OrderingApi"]}/swagger/oauth2-redirect.html" }, - PostLogoutRedirectUris = { $"{clientsUrl["OrderingApi"]}/swagger/" }, + RedirectUris = { $"{configuration["OrderingApiClient"]}/swagger/oauth2-redirect.html" }, + PostLogoutRedirectUris = { $"{configuration["OrderingApiClient"]}/swagger/" }, AllowedScopes = { @@ -223,8 +239,8 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration AllowedGrantTypes = GrantTypes.Implicit, AllowAccessTokensViaBrowser = true, - RedirectUris = { $"{clientsUrl["MobileShoppingAgg"]}/swagger/oauth2-redirect.html" }, - PostLogoutRedirectUris = { $"{clientsUrl["MobileShoppingAgg"]}/swagger/" }, + RedirectUris = { $"{configuration["MobileShoppingAggClient"]}/swagger/oauth2-redirect.html" }, + PostLogoutRedirectUris = { $"{configuration["MobileShoppingAggClient"]}/swagger/" }, AllowedScopes = { @@ -238,8 +254,8 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration AllowedGrantTypes = GrantTypes.Implicit, AllowAccessTokensViaBrowser = true, - RedirectUris = { $"{clientsUrl["WebShoppingAgg"]}/swagger/oauth2-redirect.html" }, - PostLogoutRedirectUris = { $"{clientsUrl["WebShoppingAgg"]}/swagger/" }, + RedirectUris = { $"{configuration["WebShoppingAggClient"]}/swagger/oauth2-redirect.html" }, + PostLogoutRedirectUris = { $"{configuration["WebShoppingAggClient"]}/swagger/" }, AllowedScopes = { @@ -254,8 +270,8 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration AllowedGrantTypes = GrantTypes.Implicit, AllowAccessTokensViaBrowser = true, - RedirectUris = { $"{clientsUrl["WebhooksApi"]}/swagger/oauth2-redirect.html" }, - PostLogoutRedirectUris = { $"{clientsUrl["WebhooksApi"]}/swagger/" }, + RedirectUris = { $"{configuration["WebhooksApiClient"]}/swagger/oauth2-redirect.html" }, + PostLogoutRedirectUris = { $"{configuration["WebhooksApiClient"]}/swagger/" }, AllowedScopes = { diff --git a/src/Services/Identity/Identity.API/Controllers/AccountController.cs b/src/Services/Identity/Identity.API/Controllers/AccountController.cs deleted file mode 100644 index 22e347fb9..000000000 --- a/src/Services/Identity/Identity.API/Controllers/AccountController.cs +++ /dev/null @@ -1,299 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers -{ - /// - /// This sample controller implements a typical login/logout/provision workflow for local accounts. - /// The login service encapsulates the interactions with the user data store. This data store is in-memory only and cannot be used for production! - /// The interaction service provides a way for the UI to communicate with identityserver for validation and context retrieval - /// - public class AccountController : Controller - { - //private readonly InMemoryUserLoginService _loginService; - private readonly ILoginService _loginService; - private readonly IIdentityServerInteractionService _interaction; - private readonly IClientStore _clientStore; - private readonly ILogger _logger; - private readonly UserManager _userManager; - private readonly IConfiguration _configuration; - - public AccountController( - - //InMemoryUserLoginService loginService, - ILoginService loginService, - IIdentityServerInteractionService interaction, - IClientStore clientStore, - ILogger logger, - UserManager userManager, - IConfiguration configuration) - { - _loginService = loginService; - _interaction = interaction; - _clientStore = clientStore; - _logger = logger; - _userManager = userManager; - _configuration = configuration; - } - - /// - /// Show login page - /// - [HttpGet] - public async Task Login(string returnUrl) - { - var context = await _interaction.GetAuthorizationContextAsync(returnUrl); - if (context?.IdP != null) - { - throw new NotImplementedException("External login is not implemented!"); - } - - var vm = await BuildLoginViewModelAsync(returnUrl, context); - - ViewData["ReturnUrl"] = returnUrl; - - return View(vm); - } - - /// - /// Handle postback from username/password login - /// - [HttpPost] - [ValidateAntiForgeryToken] - public async Task Login(LoginViewModel model) - { - if (ModelState.IsValid) - { - var user = await _loginService.FindByUsername(model.Email); - - if (await _loginService.ValidateCredentials(user, model.Password)) - { - var tokenLifetime = _configuration.GetValue("TokenLifetimeMinutes", 120); - - var props = new AuthenticationProperties - { - ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(tokenLifetime), - AllowRefresh = true, - RedirectUri = model.ReturnUrl - }; - - if (model.RememberMe) - { - var permanentTokenLifetime = _configuration.GetValue("PermanentTokenLifetimeDays", 365); - - props.ExpiresUtc = DateTimeOffset.UtcNow.AddDays(permanentTokenLifetime); - props.IsPersistent = true; - }; - - await _loginService.SignInAsync(user, props); - - // make sure the returnUrl is still valid, and if yes - redirect back to authorize endpoint - if (_interaction.IsValidReturnUrl(model.ReturnUrl)) - { - return Redirect(model.ReturnUrl); - } - - return Redirect("~/"); - } - - ModelState.AddModelError("", "Invalid username or password."); - } - - // something went wrong, show form with error - var vm = await BuildLoginViewModelAsync(model); - - ViewData["ReturnUrl"] = model.ReturnUrl; - - return View(vm); - } - - private async Task BuildLoginViewModelAsync(string returnUrl, AuthorizationRequest context) - { - var allowLocal = true; - if (context?.ClientId != null) - { - var client = await _clientStore.FindEnabledClientByIdAsync(context.ClientId); - if (client != null) - { - allowLocal = client.EnableLocalLogin; - } - } - - return new LoginViewModel - { - ReturnUrl = returnUrl, - Email = context?.LoginHint, - }; - } - - private async Task BuildLoginViewModelAsync(LoginViewModel model) - { - var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); - var vm = await BuildLoginViewModelAsync(model.ReturnUrl, context); - vm.Email = model.Email; - vm.RememberMe = model.RememberMe; - return vm; - } - - /// - /// Show logout page - /// - [HttpGet] - public async Task Logout(string logoutId) - { - if (User.Identity.IsAuthenticated == false) - { - // if the user is not authenticated, then just show logged out page - return await Logout(new LogoutViewModel { LogoutId = logoutId }); - } - - //Test for Xamarin. - var context = await _interaction.GetLogoutContextAsync(logoutId); - if (context?.ShowSignoutPrompt == false) - { - //it's safe to automatically sign-out - return await Logout(new LogoutViewModel { LogoutId = logoutId }); - } - - // show the logout prompt. this prevents attacks where the user - // is automatically signed out by another malicious web page. - var vm = new LogoutViewModel - { - LogoutId = logoutId - }; - return View(vm); - } - - /// - /// Handle logout page postback - /// - [HttpPost] - [ValidateAntiForgeryToken] - public async Task Logout(LogoutViewModel model) - { - var idp = User?.FindFirst(JwtClaimTypes.IdentityProvider)?.Value; - - if (idp != null && idp != IdentityServerConstants.LocalIdentityProvider) - { - if (model.LogoutId == null) - { - // if there's no current logout context, we need to create one - // this captures necessary info from the current logged in user - // before we signout and redirect away to the external IdP for signout - model.LogoutId = await _interaction.CreateLogoutContextAsync(); - } - - string url = "/Account/Logout?logoutId=" + model.LogoutId; - - try - { - - // hack: try/catch to handle social providers that throw - await HttpContext.SignOutAsync(idp, new AuthenticationProperties - { - RedirectUri = url - }); - } - catch (Exception ex) - { - _logger.LogError(ex, "LOGOUT ERROR: {ExceptionMessage}", ex.Message); - } - } - - // delete authentication cookie - await HttpContext.SignOutAsync(); - - await HttpContext.SignOutAsync(IdentityConstants.ApplicationScheme); - - // set this so UI rendering sees an anonymous user - HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity()); - - // get context information (client name, post logout redirect URI and iframe for federated signout) - var logout = await _interaction.GetLogoutContextAsync(model.LogoutId); - - return Redirect(logout?.PostLogoutRedirectUri); - } - - public async Task DeviceLogOut(string redirectUrl) - { - // delete authentication cookie - await HttpContext.SignOutAsync(); - - // set this so UI rendering sees an anonymous user - HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity()); - - return Redirect(redirectUrl); - } - - // GET: /Account/Register - [HttpGet] - [AllowAnonymous] - public IActionResult Register(string returnUrl = null) - { - ViewData["ReturnUrl"] = returnUrl; - return View(); - } - - // - // POST: /Account/Register - [HttpPost] - [AllowAnonymous] - [ValidateAntiForgeryToken] - public async Task Register(RegisterViewModel model, string returnUrl = null) - { - ViewData["ReturnUrl"] = returnUrl; - if (ModelState.IsValid) - { - var user = new ApplicationUser - { - UserName = model.Email, - Email = model.Email, - CardHolderName = model.User.CardHolderName, - CardNumber = model.User.CardNumber, - CardType = model.User.CardType, - City = model.User.City, - Country = model.User.Country, - Expiration = model.User.Expiration, - LastName = model.User.LastName, - Name = model.User.Name, - Street = model.User.Street, - State = model.User.State, - ZipCode = model.User.ZipCode, - PhoneNumber = model.User.PhoneNumber, - SecurityNumber = model.User.SecurityNumber - }; - var result = await _userManager.CreateAsync(user, model.Password); - if (result.Errors.Count() > 0) - { - AddErrors(result); - // If we got this far, something failed, redisplay form - return View(model); - } - } - - if (returnUrl != null) - { - if (HttpContext.User.Identity.IsAuthenticated) - return Redirect(returnUrl); - else - if (ModelState.IsValid) - return RedirectToAction("login", "account", new { returnUrl = returnUrl }); - else - return View(model); - } - - return RedirectToAction("index", "home"); - } - - [HttpGet] - public IActionResult Redirecting() - { - return View(); - } - - private void AddErrors(IdentityResult result) - { - foreach (var error in result.Errors) - { - ModelState.AddModelError(string.Empty, error.Description); - } - } - } -} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Controllers/ConsentController.cs b/src/Services/Identity/Identity.API/Controllers/ConsentController.cs deleted file mode 100644 index 76c27cd23..000000000 --- a/src/Services/Identity/Identity.API/Controllers/ConsentController.cs +++ /dev/null @@ -1,131 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers -{ - /// - /// This controller implements the consent logic - /// - public class ConsentController : Controller - { - private readonly ILogger _logger; - private readonly IClientStore _clientStore; - private readonly IResourceStore _resourceStore; - private readonly IIdentityServerInteractionService _interaction; - - - public ConsentController( - ILogger logger, - IIdentityServerInteractionService interaction, - IClientStore clientStore, - IResourceStore resourceStore) - { - _logger = logger; - _interaction = interaction; - _clientStore = clientStore; - _resourceStore = resourceStore; - } - - /// - /// Shows the consent screen - /// - /// - /// - [HttpGet] - public async Task Index(string returnUrl) - { - var vm = await BuildViewModelAsync(returnUrl); - ViewData["ReturnUrl"] = returnUrl; - if (vm != null) - { - return View("Index", vm); - } - - return View("Error"); - } - - /// - /// Handles the consent screen postback - /// - [HttpPost] - [ValidateAntiForgeryToken] - public async Task Index(ConsentInputModel model) - { - // parse the return URL back to an AuthorizeRequest object - var request = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); - ConsentResponse response = null; - - // user clicked 'no' - send back the standard 'access_denied' response - if (model.Button == "no") - { - response = ConsentResponse.Denied; - } - // user clicked 'yes' - validate the data - else if (model.Button == "yes") - { - // if the user consented to some scope, build the response model - if (model.ScopesConsented != null && model.ScopesConsented.Any()) - { - response = new ConsentResponse - { - RememberConsent = model.RememberConsent, - ScopesConsented = model.ScopesConsented - }; - } - else - { - ModelState.AddModelError("", "You must pick at least one permission."); - } - } - else - { - ModelState.AddModelError("", "Invalid Selection"); - } - - if (response != null) - { - // communicate outcome of consent back to identityserver - await _interaction.GrantConsentAsync(request, response); - - // redirect back to authorization endpoint - return Redirect(model.ReturnUrl); - } - - var vm = await BuildViewModelAsync(model.ReturnUrl, model); - if (vm != null) - { - return View("Index", vm); - } - - return View("Error"); - } - - async Task BuildViewModelAsync(string returnUrl, ConsentInputModel model = null) - { - var request = await _interaction.GetAuthorizationContextAsync(returnUrl); - if (request != null) - { - var client = await _clientStore.FindEnabledClientByIdAsync(request.ClientId); - if (client != null) - { - var resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested); - if (resources != null && (resources.IdentityResources.Any() || resources.ApiResources.Any())) - { - return new ConsentViewModel(model, returnUrl, request, client, resources); - } - else - { - _logger.LogError("No scopes matching: {0}", request.ScopesRequested.Aggregate((x, y) => x + ", " + y)); - } - } - else - { - _logger.LogError("Invalid client id: {0}", request.ClientId); - } - } - else - { - _logger.LogError("No consent request matching request: {0}", returnUrl); - } - - return null; - } - } -} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Controllers/HomeController.cs b/src/Services/Identity/Identity.API/Controllers/HomeController.cs deleted file mode 100644 index 3c31c4ab1..000000000 --- a/src/Services/Identity/Identity.API/Controllers/HomeController.cs +++ /dev/null @@ -1,46 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers -{ - public class HomeController : Controller - { - private readonly IIdentityServerInteractionService _interaction; - private readonly IOptionsSnapshot _settings; - private readonly IRedirectService _redirectSvc; - - public HomeController(IIdentityServerInteractionService interaction, IOptionsSnapshot settings, IRedirectService redirectSvc) - { - _interaction = interaction; - _settings = settings; - _redirectSvc = redirectSvc; - } - - public IActionResult Index(string returnUrl) - { - return View(); - } - - public IActionResult ReturnToOriginalApplication(string returnUrl) - { - if (returnUrl != null) - return Redirect(_redirectSvc.ExtractRedirectUriFromReturnUrl(returnUrl)); - else - return RedirectToAction("Index", "Home"); - } - - /// - /// Shows the error page - /// - public async Task Error(string errorId) - { - var vm = new ErrorViewModel(); - - // retrieve error details from identityserver - var message = await _interaction.GetErrorContextAsync(errorId); - if (message != null) - { - vm.Error = message; - } - - return View("Error", vm); - } - } -} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Data/ApplicationDbContext.cs b/src/Services/Identity/Identity.API/Data/ApplicationDbContext.cs index 227406008..2d04400d7 100644 --- a/src/Services/Identity/Identity.API/Data/ApplicationDbContext.cs +++ b/src/Services/Identity/Identity.API/Data/ApplicationDbContext.cs @@ -1,18 +1,17 @@ -namespace Microsoft.eShopOnContainers.Services.Identity.API.Data +namespace Microsoft.eShopOnContainers.Services.Identity.API.Data; + +public class ApplicationDbContext : IdentityDbContext { - public class ApplicationDbContext : IdentityDbContext + public ApplicationDbContext(DbContextOptions options) + : base(options) { - public ApplicationDbContext(DbContextOptions options) - : base(options) - { - } + } - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - // Customize the ASP.NET Identity model and override the defaults if needed. - // For example, you can rename the ASP.NET Identity table names and more. - // Add your customizations after calling base.OnModelCreating(builder); - } + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); } } diff --git a/src/Services/Identity/Identity.API/Data/ApplicationDbContextSeed.cs b/src/Services/Identity/Identity.API/Data/ApplicationDbContextSeed.cs deleted file mode 100644 index 6ee9fd499..000000000 --- a/src/Services/Identity/Identity.API/Data/ApplicationDbContextSeed.cs +++ /dev/null @@ -1,218 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Identity.API.Data -{ - using Microsoft.Extensions.Logging; - public class ApplicationDbContextSeed - { - private readonly IPasswordHasher _passwordHasher = new PasswordHasher(); - - public async Task SeedAsync(ApplicationDbContext context, IWebHostEnvironment env, - ILogger logger, IOptions settings, int? retry = 0) - { - int retryForAvaiability = retry.Value; - - try - { - var useCustomizationData = settings.Value.UseCustomizationData; - var contentRootPath = env.ContentRootPath; - var webroot = env.WebRootPath; - - if (!context.Users.Any()) - { - context.Users.AddRange(useCustomizationData - ? GetUsersFromFile(contentRootPath, logger) - : GetDefaultUser()); - - await context.SaveChangesAsync(); - } - - if (useCustomizationData) - { - GetPreconfiguredImages(contentRootPath, webroot, logger); - } - } - catch (Exception ex) - { - if (retryForAvaiability < 10) - { - retryForAvaiability++; - - logger.LogError(ex, "EXCEPTION ERROR while migrating {DbContextName}", nameof(ApplicationDbContext)); - - await SeedAsync(context, env, logger, settings, retryForAvaiability); - } - } - } - - private IEnumerable GetUsersFromFile(string contentRootPath, ILogger logger) - { - string csvFileUsers = Path.Combine(contentRootPath, "Setup", "Users.csv"); - - if (!File.Exists(csvFileUsers)) - { - return GetDefaultUser(); - } - - string[] csvheaders; - try - { - string[] requiredHeaders = { - "cardholdername", "cardnumber", "cardtype", "city", "country", - "email", "expiration", "lastname", "name", "phonenumber", - "username", "zipcode", "state", "street", "securitynumber", - "normalizedemail", "normalizedusername", "password" - }; - csvheaders = GetHeaders(requiredHeaders, csvFileUsers); - } - catch (Exception ex) - { - logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); - - return GetDefaultUser(); - } - - List users = File.ReadAllLines(csvFileUsers) - .Skip(1) // skip header column - .Select(row => Regex.Split(row, ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)")) - .SelectTry(column => CreateApplicationUser(column, csvheaders)) - .OnCaughtException(ex => { logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return null; }) - .Where(x => x != null) - .ToList(); - - return users; - } - - private ApplicationUser CreateApplicationUser(string[] column, string[] headers) - { - if (column.Count() != headers.Count()) - { - throw new Exception($"column count '{column.Count()}' not the same as headers count'{headers.Count()}'"); - } - - string cardtypeString = column[Array.IndexOf(headers, "cardtype")].Trim('"').Trim(); - if (!int.TryParse(cardtypeString, out int cardtype)) - { - throw new Exception($"cardtype='{cardtypeString}' is not a number"); - } - - var user = new ApplicationUser - { - CardHolderName = column[Array.IndexOf(headers, "cardholdername")].Trim('"').Trim(), - CardNumber = column[Array.IndexOf(headers, "cardnumber")].Trim('"').Trim(), - CardType = cardtype, - City = column[Array.IndexOf(headers, "city")].Trim('"').Trim(), - Country = column[Array.IndexOf(headers, "country")].Trim('"').Trim(), - Email = column[Array.IndexOf(headers, "email")].Trim('"').Trim(), - Expiration = column[Array.IndexOf(headers, "expiration")].Trim('"').Trim(), - Id = Guid.NewGuid().ToString(), - LastName = column[Array.IndexOf(headers, "lastname")].Trim('"').Trim(), - Name = column[Array.IndexOf(headers, "name")].Trim('"').Trim(), - PhoneNumber = column[Array.IndexOf(headers, "phonenumber")].Trim('"').Trim(), - UserName = column[Array.IndexOf(headers, "username")].Trim('"').Trim(), - ZipCode = column[Array.IndexOf(headers, "zipcode")].Trim('"').Trim(), - State = column[Array.IndexOf(headers, "state")].Trim('"').Trim(), - Street = column[Array.IndexOf(headers, "street")].Trim('"').Trim(), - SecurityNumber = column[Array.IndexOf(headers, "securitynumber")].Trim('"').Trim(), - NormalizedEmail = column[Array.IndexOf(headers, "normalizedemail")].Trim('"').Trim(), - NormalizedUserName = column[Array.IndexOf(headers, "normalizedusername")].Trim('"').Trim(), - SecurityStamp = Guid.NewGuid().ToString("D"), - PasswordHash = column[Array.IndexOf(headers, "password")].Trim('"').Trim(), // Note: This is the password - }; - - user.PasswordHash = _passwordHasher.HashPassword(user, user.PasswordHash); - - return user; - } - - private IEnumerable GetDefaultUser() - { - var user = - new ApplicationUser() - { - CardHolderName = "DemoUser", - CardNumber = "4012888888881881", - CardType = 1, - City = "Redmond", - Country = "U.S.", - Email = "demouser@microsoft.com", - Expiration = "12/25", - Id = Guid.NewGuid().ToString(), - LastName = "DemoLastName", - Name = "DemoUser", - PhoneNumber = "1234567890", - UserName = "demouser@microsoft.com", - ZipCode = "98052", - State = "WA", - Street = "15703 NE 61st Ct", - SecurityNumber = "535", - NormalizedEmail = "DEMOUSER@MICROSOFT.COM", - NormalizedUserName = "DEMOUSER@MICROSOFT.COM", - SecurityStamp = Guid.NewGuid().ToString("D"), - }; - - user.PasswordHash = _passwordHasher.HashPassword(user, "Pass@word1"); - - return new List() - { - user - }; - } - - static string[] GetHeaders(string[] requiredHeaders, string csvfile) - { - string[] csvheaders = File.ReadLines(csvfile).First().ToLowerInvariant().Split(','); - - if (csvheaders.Count() != requiredHeaders.Count()) - { - throw new Exception($"requiredHeader count '{ requiredHeaders.Count()}' is different then read header '{csvheaders.Count()}'"); - } - - foreach (var requiredHeader in requiredHeaders) - { - if (!csvheaders.Contains(requiredHeader)) - { - throw new Exception($"does not contain required header '{requiredHeader}'"); - } - } - - return csvheaders; - } - - static void GetPreconfiguredImages(string contentRootPath, string webroot, ILogger logger) - { - try - { - string imagesZipFile = Path.Combine(contentRootPath, "Setup", "images.zip"); - if (!File.Exists(imagesZipFile)) - { - logger.LogError("Zip file '{ZipFileName}' does not exists.", imagesZipFile); - return; - } - - string imagePath = Path.Combine(webroot, "images"); - string[] imageFiles = Directory.GetFiles(imagePath).Select(file => Path.GetFileName(file)).ToArray(); - - using ZipArchive zip = ZipFile.Open(imagesZipFile, ZipArchiveMode.Read); - foreach (ZipArchiveEntry entry in zip.Entries) - { - if (imageFiles.Contains(entry.Name)) - { - string destinationFilename = Path.Combine(imagePath, entry.Name); - if (File.Exists(destinationFilename)) - { - File.Delete(destinationFilename); - } - entry.ExtractToFile(destinationFilename); - } - else - { - logger.LogWarning("Skipped file '{FileName}' in zipfile '{ZipFileName}'", entry.Name, imagesZipFile); - } - } - } - catch (Exception ex) - { - logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); ; - } - } - } -} diff --git a/src/Services/Identity/Identity.API/Data/ConfigurationDbContextSeed.cs b/src/Services/Identity/Identity.API/Data/ConfigurationDbContextSeed.cs deleted file mode 100644 index e3df90fdd..000000000 --- a/src/Services/Identity/Identity.API/Data/ConfigurationDbContextSeed.cs +++ /dev/null @@ -1,73 +0,0 @@ -using IdentityServer4.EntityFramework.Entities; - -namespace Microsoft.eShopOnContainers.Services.Identity.API.Data -{ - public class ConfigurationDbContextSeed - { - public async Task SeedAsync(ConfigurationDbContext context, IConfiguration configuration) - { - - //callbacks urls from config: - var clientUrls = new Dictionary(); - - clientUrls.Add("Mvc", configuration.GetValue("MvcClient")); - clientUrls.Add("Spa", configuration.GetValue("SpaClient")); - clientUrls.Add("Xamarin", configuration.GetValue("XamarinCallback")); - clientUrls.Add("BasketApi", configuration.GetValue("BasketApiClient")); - clientUrls.Add("OrderingApi", configuration.GetValue("OrderingApiClient")); - clientUrls.Add("MobileShoppingAgg", configuration.GetValue("MobileShoppingAggClient")); - clientUrls.Add("WebShoppingAgg", configuration.GetValue("WebShoppingAggClient")); - clientUrls.Add("WebhooksApi", configuration.GetValue("WebhooksApiClient")); - clientUrls.Add("WebhooksWeb", configuration.GetValue("WebhooksWebClient")); - - if (!context.Clients.Any()) - { - foreach (var client in Config.GetClients(clientUrls)) - { - context.Clients.Add(client.ToEntity()); - } - await context.SaveChangesAsync(); - } - // Checking always for old redirects to fix existing deployments - // to use new swagger-ui redirect uri as of v3.0.0 - // There should be no problem for new ones - // ref: https://github.com/dotnet-architecture/eShopOnContainers/issues/586 - else - { - List oldRedirects = (await context.Clients.Include(c => c.RedirectUris).ToListAsync()) - .SelectMany(c => c.RedirectUris) - .Where(ru => ru.RedirectUri.EndsWith("/o2c.html")) - .ToList(); - - if (oldRedirects.Any()) - { - foreach (var ru in oldRedirects) - { - ru.RedirectUri = ru.RedirectUri.Replace("/o2c.html", "/oauth2-redirect.html"); - context.Update(ru.Client); - } - await context.SaveChangesAsync(); - } - } - - if (!context.IdentityResources.Any()) - { - foreach (var resource in Config.GetResources()) - { - context.IdentityResources.Add(resource.ToEntity()); - } - await context.SaveChangesAsync(); - } - - if (!context.ApiResources.Any()) - { - foreach (var api in Config.GetApis()) - { - context.ApiResources.Add(api.ToEntity()); - } - - await context.SaveChangesAsync(); - } - } - } -} diff --git a/src/Services/Identity/Identity.API/Migrations/20210813072445_InitialMigration.Designer.cs b/src/Services/Identity/Identity.API/Data/Migrations/20210914100206_InitialMigration.Designer.cs similarity index 95% rename from src/Services/Identity/Identity.API/Migrations/20210813072445_InitialMigration.Designer.cs rename to src/Services/Identity/Identity.API/Data/Migrations/20210914100206_InitialMigration.Designer.cs index b6c43abf5..d8c737ae4 100644 --- a/src/Services/Identity/Identity.API/Migrations/20210813072445_InitialMigration.Designer.cs +++ b/src/Services/Identity/Identity.API/Data/Migrations/20210914100206_InitialMigration.Designer.cs @@ -7,10 +7,10 @@ using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.eShopOnContainers.Services.Identity.API.Data; -namespace Identity.API.Migrations +namespace Microsoft.eShopOnContainers.Services.Identity.API.Data.Migrations { [DbContext(typeof(ApplicationDbContext))] - [Migration("20210813072445_InitialMigration")] + [Migration("20210914100206_InitialMigration")] partial class InitialMigration { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -18,7 +18,7 @@ namespace Identity.API.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "6.0.0") + .HasAnnotation("ProductVersion", "5.0.9") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => @@ -45,7 +45,7 @@ namespace Identity.API.Migrations .HasDatabaseName("RoleNameIndex") .HasFilter("[NormalizedName] IS NOT NULL"); - b.ToTable("AspNetRoles", (string)null); + b.ToTable("AspNetRoles"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => @@ -69,7 +69,7 @@ namespace Identity.API.Migrations b.HasIndex("RoleId"); - b.ToTable("AspNetRoleClaims", (string)null); + b.ToTable("AspNetRoleClaims"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => @@ -93,7 +93,7 @@ namespace Identity.API.Migrations b.HasIndex("UserId"); - b.ToTable("AspNetUserClaims", (string)null); + b.ToTable("AspNetUserClaims"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => @@ -115,7 +115,7 @@ namespace Identity.API.Migrations b.HasIndex("UserId"); - b.ToTable("AspNetUserLogins", (string)null); + b.ToTable("AspNetUserLogins"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => @@ -130,7 +130,7 @@ namespace Identity.API.Migrations b.HasIndex("RoleId"); - b.ToTable("AspNetUserRoles", (string)null); + b.ToTable("AspNetUserRoles"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => @@ -149,7 +149,7 @@ namespace Identity.API.Migrations b.HasKey("UserId", "LoginProvider", "Name"); - b.ToTable("AspNetUserTokens", (string)null); + b.ToTable("AspNetUserTokens"); }); modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Identity.API.Models.ApplicationUser", b => @@ -261,7 +261,7 @@ namespace Identity.API.Migrations .HasDatabaseName("UserNameIndex") .HasFilter("[NormalizedUserName] IS NOT NULL"); - b.ToTable("AspNetUsers", (string)null); + b.ToTable("AspNetUsers"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => diff --git a/src/Services/Identity/Identity.API/Migrations/20210813072445_InitialMigration.cs b/src/Services/Identity/Identity.API/Data/Migrations/20210914100206_InitialMigration.cs similarity index 99% rename from src/Services/Identity/Identity.API/Migrations/20210813072445_InitialMigration.cs rename to src/Services/Identity/Identity.API/Data/Migrations/20210914100206_InitialMigration.cs index 88587a677..e0933a52e 100644 --- a/src/Services/Identity/Identity.API/Migrations/20210813072445_InitialMigration.cs +++ b/src/Services/Identity/Identity.API/Data/Migrations/20210914100206_InitialMigration.cs @@ -1,7 +1,7 @@ using System; using Microsoft.EntityFrameworkCore.Migrations; -namespace Identity.API.Migrations +namespace Microsoft.eShopOnContainers.Services.Identity.API.Data.Migrations { public partial class InitialMigration : Migration { diff --git a/src/Services/Identity/Identity.API/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Services/Identity/Identity.API/Data/Migrations/ApplicationDbContextModelSnapshot.cs similarity index 95% rename from src/Services/Identity/Identity.API/Migrations/ApplicationDbContextModelSnapshot.cs rename to src/Services/Identity/Identity.API/Data/Migrations/ApplicationDbContextModelSnapshot.cs index 0076eb5bf..8216173a7 100644 --- a/src/Services/Identity/Identity.API/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/Services/Identity/Identity.API/Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -6,7 +6,7 @@ using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.eShopOnContainers.Services.Identity.API.Data; -namespace Identity.API.Migrations +namespace Microsoft.eShopOnContainers.Services.Identity.API.Data.Migrations { [DbContext(typeof(ApplicationDbContext))] partial class ApplicationDbContextModelSnapshot : ModelSnapshot @@ -16,7 +16,7 @@ namespace Identity.API.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "6.0.0") + .HasAnnotation("ProductVersion", "5.0.9") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => @@ -43,7 +43,7 @@ namespace Identity.API.Migrations .HasDatabaseName("RoleNameIndex") .HasFilter("[NormalizedName] IS NOT NULL"); - b.ToTable("AspNetRoles", (string)null); + b.ToTable("AspNetRoles"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => @@ -67,7 +67,7 @@ namespace Identity.API.Migrations b.HasIndex("RoleId"); - b.ToTable("AspNetRoleClaims", (string)null); + b.ToTable("AspNetRoleClaims"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => @@ -91,7 +91,7 @@ namespace Identity.API.Migrations b.HasIndex("UserId"); - b.ToTable("AspNetUserClaims", (string)null); + b.ToTable("AspNetUserClaims"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => @@ -113,7 +113,7 @@ namespace Identity.API.Migrations b.HasIndex("UserId"); - b.ToTable("AspNetUserLogins", (string)null); + b.ToTable("AspNetUserLogins"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => @@ -128,7 +128,7 @@ namespace Identity.API.Migrations b.HasIndex("RoleId"); - b.ToTable("AspNetUserRoles", (string)null); + b.ToTable("AspNetUserRoles"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => @@ -147,7 +147,7 @@ namespace Identity.API.Migrations b.HasKey("UserId", "LoginProvider", "Name"); - b.ToTable("AspNetUserTokens", (string)null); + b.ToTable("AspNetUserTokens"); }); modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Identity.API.Models.ApplicationUser", b => @@ -259,7 +259,7 @@ namespace Identity.API.Migrations .HasDatabaseName("UserNameIndex") .HasFilter("[NormalizedUserName] IS NOT NULL"); - b.ToTable("AspNetUsers", (string)null); + b.ToTable("AspNetUsers"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => diff --git a/src/Services/Identity/Identity.API/Devspaces/DevspacesRedirectUriValidator.cs b/src/Services/Identity/Identity.API/Devspaces/DevspacesRedirectUriValidator.cs index 8912882f4..e366a7a65 100644 --- a/src/Services/Identity/Identity.API/Devspaces/DevspacesRedirectUriValidator.cs +++ b/src/Services/Identity/Identity.API/Devspaces/DevspacesRedirectUriValidator.cs @@ -10,14 +10,14 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Devspaces _logger = logger; } - public Task IsPostLogoutRedirectUriValidAsync(string requestedUri, IdentityServer4.Models.Client client) + public Task IsPostLogoutRedirectUriValidAsync(string requestedUri, Duende.IdentityServer.Models.Client client) { _logger.LogInformation("Client {ClientName} used post logout uri {RequestedUri}.", client.ClientName, requestedUri); return Task.FromResult(true); } - public Task IsRedirectUriValidAsync(string requestedUri, IdentityServer4.Models.Client client) + public Task IsRedirectUriValidAsync(string requestedUri, Duende.IdentityServer.Models.Client client) { _logger.LogInformation("Client {ClientName} used post logout uri {RequestedUri}.", client.ClientName, requestedUri); return Task.FromResult(true); diff --git a/src/Services/Identity/Identity.API/Dockerfile b/src/Services/Identity/Identity.API/Dockerfile index 674cc4ec1..aca2e1e81 100644 --- a/src/Services/Identity/Identity.API/Dockerfile +++ b/src/Services/Identity/Identity.API/Dockerfile @@ -1,8 +1,8 @@ -FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base WORKDIR /app EXPOSE 80 -FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build WORKDIR /src # It's important to keep lines from here down to "COPY . ." identical in all Dockerfiles diff --git a/src/Services/Identity/Identity.API/Dockerfile.develop b/src/Services/Identity/Identity.API/Dockerfile.develop index 9afc17772..cb38e5b4d 100644 --- a/src/Services/Identity/Identity.API/Dockerfile.develop +++ b/src/Services/Identity/Identity.API/Dockerfile.develop @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:6.0 +FROM mcr.microsoft.com/dotnet/sdk:7.0 ARG BUILD_CONFIGURATION=Debug ENV ASPNETCORE_ENVIRONMENT=Development ENV DOTNET_USE_POLLING_FILE_WATCHER=true diff --git a/src/Services/Identity/Identity.API/Extensions/LinqSelectExtensions.cs b/src/Services/Identity/Identity.API/Extensions/LinqSelectExtensions.cs deleted file mode 100644 index 0e2c4c72b..000000000 --- a/src/Services/Identity/Identity.API/Extensions/LinqSelectExtensions.cs +++ /dev/null @@ -1,46 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Identity.API.Extensions -{ - public static class LinqSelectExtensions - { - public static IEnumerable> SelectTry(this IEnumerable enumerable, Func selector) - { - foreach (TSource element in enumerable) - { - SelectTryResult returnedValue; - try - { - returnedValue = new SelectTryResult(element, selector(element), null); - } - catch (Exception ex) - { - returnedValue = new SelectTryResult(element, default(TResult), ex); - } - yield return returnedValue; - } - } - - public static IEnumerable OnCaughtException(this IEnumerable> enumerable, Func exceptionHandler) - { - return enumerable.Select(x => x.CaughtException == null ? x.Result : exceptionHandler(x.CaughtException)); - } - - public static IEnumerable OnCaughtException(this IEnumerable> enumerable, Func exceptionHandler) - { - return enumerable.Select(x => x.CaughtException == null ? x.Result : exceptionHandler(x.Source, x.CaughtException)); - } - - public class SelectTryResult - { - internal SelectTryResult(TSource source, TResult result, Exception exception) - { - Source = source; - Result = result; - CaughtException = exception; - } - - public TSource Source { get; private set; } - public TResult Result { get; private set; } - public Exception CaughtException { get; private set; } - } - } -} diff --git a/src/Services/Identity/Identity.API/Factories/ApplicationDbContextFactory.cs b/src/Services/Identity/Identity.API/Factories/ApplicationDbContextFactory.cs deleted file mode 100644 index c1fb0198c..000000000 --- a/src/Services/Identity/Identity.API/Factories/ApplicationDbContextFactory.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Identity.API.Factories -{ - public class ApplicationDbContextFactory : IDesignTimeDbContextFactory - { - public ApplicationDbContext CreateDbContext(string[] args) - { - var config = new ConfigurationBuilder() - .SetBasePath(Path.Combine(Directory.GetCurrentDirectory())) - .AddJsonFile("appsettings.json") - .AddEnvironmentVariables() - .Build(); - - var optionsBuilder = new DbContextOptionsBuilder(); - - optionsBuilder.UseSqlServer(config["ConnectionString"], sqlServerOptionsAction: o => o.MigrationsAssembly("Identity.API")); - - return new ApplicationDbContext(optionsBuilder.Options); - } - } -} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Factories/ConfigurationDbContextFactory.cs b/src/Services/Identity/Identity.API/Factories/ConfigurationDbContextFactory.cs deleted file mode 100644 index 0fdbe8598..000000000 --- a/src/Services/Identity/Identity.API/Factories/ConfigurationDbContextFactory.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Identity.API.Factories -{ - public class ConfigurationDbContextFactory : IDesignTimeDbContextFactory - { - public ConfigurationDbContext CreateDbContext(string[] args) - { - var config = new ConfigurationBuilder() - .SetBasePath(Path.Combine(Directory.GetCurrentDirectory())) - .AddJsonFile("appsettings.json") - .AddEnvironmentVariables() - .Build(); - - var optionsBuilder = new DbContextOptionsBuilder(); - var storeOptions = new ConfigurationStoreOptions(); - - optionsBuilder.UseSqlServer(config["ConnectionString"], sqlServerOptionsAction: o => o.MigrationsAssembly("Identity.API")); - - return new ConfigurationDbContext(optionsBuilder.Options, storeOptions); - } - } -} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Factories/PersistedGrantDbContextFactory.cs b/src/Services/Identity/Identity.API/Factories/PersistedGrantDbContextFactory.cs deleted file mode 100644 index 83380dfd0..000000000 --- a/src/Services/Identity/Identity.API/Factories/PersistedGrantDbContextFactory.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Identity.API.Factories -{ - public class PersistedGrantDbContextFactory : IDesignTimeDbContextFactory - { - public PersistedGrantDbContext CreateDbContext(string[] args) - { - var config = new ConfigurationBuilder() - .SetBasePath(Path.Combine(Directory.GetCurrentDirectory())) - .AddJsonFile("appsettings.json") - .AddEnvironmentVariables() - .Build(); - - var optionsBuilder = new DbContextOptionsBuilder(); - var operationOptions = new OperationalStoreOptions(); - - optionsBuilder.UseSqlServer(config["ConnectionString"], sqlServerOptionsAction: o => o.MigrationsAssembly("Identity.API")); - - return new PersistedGrantDbContext(optionsBuilder.Options, operationOptions); - } - } -} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/GlobalUsings.cs b/src/Services/Identity/Identity.API/GlobalUsings.cs index bfbb354db..c21ec0a02 100644 --- a/src/Services/Identity/Identity.API/GlobalUsings.cs +++ b/src/Services/Identity/Identity.API/GlobalUsings.cs @@ -1,19 +1,18 @@ -global using Microsoft.eShopOnContainers.Services.Identity.API.Extensions; + global using System.IO.Compression; global using Autofac.Extensions.DependencyInjection; -global using Autofac; global using Azure.Core; global using Azure.Identity; global using HealthChecks.UI.Client; global using IdentityModel; -global using IdentityServer4.EntityFramework.DbContexts; -global using IdentityServer4.EntityFramework.Mappers; -global using IdentityServer4.EntityFramework.Options; -global using IdentityServer4.Models; -global using IdentityServer4.Services; -global using IdentityServer4.Stores; -global using IdentityServer4.Validation; -global using IdentityServer4; +global using Duende.IdentityServer; +global using Duende.IdentityServer.Configuration; +global using Duende.IdentityServer.Events; +global using Duende.IdentityServer.Extensions; +global using Duende.IdentityServer.Models; +global using Duende.IdentityServer.Services; +global using Duende.IdentityServer.Stores; +global using Duende.IdentityServer.Validation; global using Microsoft.AspNetCore.Authentication; global using Microsoft.AspNetCore.Authorization; global using Microsoft.AspNetCore.Builder; @@ -23,20 +22,21 @@ global using Microsoft.AspNetCore.Identity.EntityFrameworkCore; global using Microsoft.AspNetCore.Identity; global using Microsoft.AspNetCore.Mvc.Rendering; global using Microsoft.AspNetCore.Mvc; +global using Microsoft.AspNetCore.Mvc.Filters; global using Microsoft.AspNetCore; global using Microsoft.EntityFrameworkCore.Design; global using Microsoft.EntityFrameworkCore.Infrastructure; global using Microsoft.EntityFrameworkCore.Metadata; global using Microsoft.EntityFrameworkCore.Migrations; global using Microsoft.EntityFrameworkCore; -global using Microsoft.eShopOnContainers.Services.Identity.API.Certificates; -global using Microsoft.eShopOnContainers.Services.Identity.API.Configuration; + +global using Microsoft.eShopOnContainers.Services.Identity.API; global using Microsoft.eShopOnContainers.Services.Identity.API.Data; +global using Microsoft.eShopOnContainers.Services.Identity.API.Configuration; global using Microsoft.eShopOnContainers.Services.Identity.API.Devspaces; global using Microsoft.eShopOnContainers.Services.Identity.API.Models.AccountViewModels; global using Microsoft.eShopOnContainers.Services.Identity.API.Models; global using Microsoft.eShopOnContainers.Services.Identity.API.Services; -global using Microsoft.eShopOnContainers.Services.Identity.API; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.Diagnostics.HealthChecks; @@ -44,7 +44,6 @@ global using Microsoft.Extensions.Hosting; global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.Options; global using Polly; -global using Serilog; global using StackExchange.Redis; global using System.Collections.Generic; global using System.ComponentModel.DataAnnotations; @@ -58,6 +57,7 @@ global using System.Security.Cryptography.X509Certificates; global using System.Text.RegularExpressions; global using System.Threading.Tasks; global using System; +global using Microsoft.AspNetCore.Http; diff --git a/src/Services/Identity/Identity.API/Identity.API.csproj b/src/Services/Identity/Identity.API/Identity.API.csproj index 67d46a95f..54c6f2a0e 100644 --- a/src/Services/Identity/Identity.API/Identity.API.csproj +++ b/src/Services/Identity/Identity.API/Identity.API.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 aspnet-eShopOnContainers.Identity-90487118-103c-4ff0-b9da-e5e26f7ab0c5 ..\..\..\..\docker-compose.dcproj false @@ -9,64 +9,72 @@ - - PreserveNewest - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - + + + + + + + - - + + - - - - - - - - - - - - - - - - - - PreserveNewest - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Services/Identity/Identity.API/Migrations/ConfigurationDb/20210813072543_InitialMigration.Designer.cs b/src/Services/Identity/Identity.API/Migrations/ConfigurationDb/20210813072543_InitialMigration.Designer.cs deleted file mode 100644 index c5e7b3457..000000000 --- a/src/Services/Identity/Identity.API/Migrations/ConfigurationDb/20210813072543_InitialMigration.Designer.cs +++ /dev/null @@ -1,911 +0,0 @@ -// -using System; -using IdentityServer4.EntityFramework.DbContexts; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -namespace Identity.API.Migrations.ConfigurationDb -{ - [DbContext(typeof(ConfigurationDbContext))] - [Migration("20210813072543_InitialMigration")] - partial class InitialMigration - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "6.0.0") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("Created") - .HasColumnType("datetime2"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("DisplayName") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("Enabled") - .HasColumnType("bit"); - - b.Property("LastAccessed") - .HasColumnType("datetime2"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("NonEditable") - .HasColumnType("bit"); - - b.Property("Updated") - .HasColumnType("datetime2"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("ApiResources", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ApiResourceId") - .HasColumnType("int"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.ToTable("ApiClaims", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ApiResourceId") - .HasColumnType("int"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.ToTable("ApiProperties", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ApiResourceId") - .HasColumnType("int"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("DisplayName") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("Emphasize") - .HasColumnType("bit"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("Required") - .HasColumnType("bit"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("bit"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("ApiScopes", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ApiScopeId") - .HasColumnType("int"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.HasKey("Id"); - - b.HasIndex("ApiScopeId"); - - b.ToTable("ApiScopeClaims", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ApiResourceId") - .HasColumnType("int"); - - b.Property("Created") - .HasColumnType("datetime2"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("Expiration") - .HasColumnType("datetime2"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(4000) - .HasColumnType("nvarchar(4000)"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.ToTable("ApiSecrets", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - 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("ClientClaimsPrefix") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("ClientId") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("ClientName") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("ClientUri") - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.Property("ConsentLifetime") - .HasColumnType("int"); - - b.Property("Created") - .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("LastAccessed") - .HasColumnType("datetime2"); - - b.Property("LogoUri") - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.Property("NonEditable") - .HasColumnType("bit"); - - b.Property("PairWiseSubjectSalt") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("ProtocolType") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("RefreshTokenExpiration") - .HasColumnType("int"); - - b.Property("RefreshTokenUsage") - .HasColumnType("int"); - - b.Property("RequireClientSecret") - .HasColumnType("bit"); - - b.Property("RequireConsent") - .HasColumnType("bit"); - - b.Property("RequirePkce") - .HasColumnType("bit"); - - b.Property("SlidingRefreshTokenLifetime") - .HasColumnType("int"); - - b.Property("UpdateAccessTokenClaimsOnRefresh") - .HasColumnType("bit"); - - b.Property("Updated") - .HasColumnType("datetime2"); - - b.Property("UserCodeType") - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("UserSsoLifetime") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("ClientId") - .IsUnique(); - - b.ToTable("Clients", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientClaims", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("Origin") - .IsRequired() - .HasMaxLength(150) - .HasColumnType("nvarchar(150)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientCorsOrigins", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("GrantType") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientGrantTypes", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientIdPRestrictions", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("PostLogoutRedirectUri") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientPostLogoutRedirectUris", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientProperties", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("RedirectUri") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientRedirectUris", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("Scope") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientScopes", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("Created") - .HasColumnType("datetime2"); - - b.Property("Description") - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.Property("Expiration") - .HasColumnType("datetime2"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(4000) - .HasColumnType("nvarchar(4000)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientSecrets", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("IdentityResourceId") - .HasColumnType("int"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.HasKey("Id"); - - b.HasIndex("IdentityResourceId"); - - b.ToTable("IdentityClaims", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("Created") - .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("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("NonEditable") - .HasColumnType("bit"); - - b.Property("Required") - .HasColumnType("bit"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("bit"); - - b.Property("Updated") - .HasColumnType("datetime2"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("IdentityResources", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("IdentityResourceId") - .HasColumnType("int"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.HasKey("Id"); - - b.HasIndex("IdentityResourceId"); - - b.ToTable("IdentityProperties", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany("UserClaims") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany("Properties") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany("Scopes") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "ApiScope") - .WithMany("UserClaims") - .HasForeignKey("ApiScopeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiScope"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiSecret", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany("Secrets") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("Claims") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedCorsOrigins") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedGrantTypes") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("IdentityProviderRestrictions") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("PostLogoutRedirectUris") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("Properties") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("RedirectUris") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedScopes") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("ClientSecrets") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityClaim", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource") - .WithMany("UserClaims") - .HasForeignKey("IdentityResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("IdentityResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource") - .WithMany("Properties") - .HasForeignKey("IdentityResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("IdentityResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b => - { - b.Navigation("Properties"); - - b.Navigation("Scopes"); - - b.Navigation("Secrets"); - - b.Navigation("UserClaims"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => - { - b.Navigation("UserClaims"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b => - { - b.Navigation("AllowedCorsOrigins"); - - b.Navigation("AllowedGrantTypes"); - - b.Navigation("AllowedScopes"); - - b.Navigation("Claims"); - - b.Navigation("ClientSecrets"); - - b.Navigation("IdentityProviderRestrictions"); - - b.Navigation("PostLogoutRedirectUris"); - - b.Navigation("Properties"); - - b.Navigation("RedirectUris"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b => - { - b.Navigation("Properties"); - - b.Navigation("UserClaims"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Services/Identity/Identity.API/Migrations/ConfigurationDb/20210813072543_InitialMigration.cs b/src/Services/Identity/Identity.API/Migrations/ConfigurationDb/20210813072543_InitialMigration.cs deleted file mode 100644 index 57f508711..000000000 --- a/src/Services/Identity/Identity.API/Migrations/ConfigurationDb/20210813072543_InitialMigration.cs +++ /dev/null @@ -1,607 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Identity.API.Migrations.ConfigurationDb -{ - public partial class InitialMigration : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "ApiResources", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - Enabled = table.Column(type: "bit", nullable: false), - Name = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), - DisplayName = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), - Description = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: true), - Created = table.Column(type: "datetime2", nullable: false), - Updated = table.Column(type: "datetime2", nullable: true), - LastAccessed = table.Column(type: "datetime2", nullable: true), - NonEditable = table.Column(type: "bit", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ApiResources", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Clients", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - Enabled = table.Column(type: "bit", nullable: false), - ClientId = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), - ProtocolType = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), - RequireClientSecret = table.Column(type: "bit", nullable: false), - ClientName = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), - Description = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: true), - ClientUri = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: true), - LogoUri = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: true), - RequireConsent = table.Column(type: "bit", nullable: false), - AllowRememberConsent = table.Column(type: "bit", nullable: false), - AlwaysIncludeUserClaimsInIdToken = table.Column(type: "bit", nullable: false), - RequirePkce = table.Column(type: "bit", nullable: false), - AllowPlainTextPkce = table.Column(type: "bit", nullable: false), - AllowAccessTokensViaBrowser = table.Column(type: "bit", nullable: false), - FrontChannelLogoutUri = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: true), - FrontChannelLogoutSessionRequired = table.Column(type: "bit", nullable: false), - BackChannelLogoutUri = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: true), - BackChannelLogoutSessionRequired = table.Column(type: "bit", nullable: false), - AllowOfflineAccess = table.Column(type: "bit", nullable: false), - IdentityTokenLifetime = table.Column(type: "int", nullable: false), - AccessTokenLifetime = table.Column(type: "int", nullable: false), - AuthorizationCodeLifetime = table.Column(type: "int", nullable: false), - ConsentLifetime = table.Column(type: "int", nullable: true), - AbsoluteRefreshTokenLifetime = table.Column(type: "int", nullable: false), - SlidingRefreshTokenLifetime = table.Column(type: "int", nullable: false), - RefreshTokenUsage = table.Column(type: "int", nullable: false), - UpdateAccessTokenClaimsOnRefresh = table.Column(type: "bit", nullable: false), - RefreshTokenExpiration = table.Column(type: "int", nullable: false), - AccessTokenType = table.Column(type: "int", nullable: false), - EnableLocalLogin = table.Column(type: "bit", nullable: false), - IncludeJwtId = table.Column(type: "bit", nullable: false), - AlwaysSendClientClaims = table.Column(type: "bit", nullable: false), - ClientClaimsPrefix = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), - PairWiseSubjectSalt = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), - Created = table.Column(type: "datetime2", nullable: false), - Updated = table.Column(type: "datetime2", nullable: true), - LastAccessed = table.Column(type: "datetime2", nullable: true), - UserSsoLifetime = table.Column(type: "int", nullable: true), - UserCodeType = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), - DeviceCodeLifetime = table.Column(type: "int", nullable: false), - NonEditable = table.Column(type: "bit", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Clients", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "IdentityResources", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - Enabled = table.Column(type: "bit", nullable: false), - Name = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), - DisplayName = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), - Description = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: true), - Required = table.Column(type: "bit", nullable: false), - Emphasize = table.Column(type: "bit", nullable: false), - ShowInDiscoveryDocument = table.Column(type: "bit", nullable: false), - Created = table.Column(type: "datetime2", nullable: false), - Updated = table.Column(type: "datetime2", nullable: true), - NonEditable = table.Column(type: "bit", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_IdentityResources", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "ApiClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ApiResourceId = table.Column(type: "int", nullable: false), - Type = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ApiClaims", x => x.Id); - table.ForeignKey( - name: "FK_ApiClaims_ApiResources_ApiResourceId", - column: x => x.ApiResourceId, - principalTable: "ApiResources", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ApiProperties", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ApiResourceId = table.Column(type: "int", nullable: false), - Key = table.Column(type: "nvarchar(250)", maxLength: 250, nullable: false), - Value = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ApiProperties", x => x.Id); - table.ForeignKey( - name: "FK_ApiProperties_ApiResources_ApiResourceId", - column: x => x.ApiResourceId, - principalTable: "ApiResources", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ApiScopes", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - Name = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), - DisplayName = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), - Description = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: true), - Required = table.Column(type: "bit", nullable: false), - Emphasize = table.Column(type: "bit", nullable: false), - ShowInDiscoveryDocument = table.Column(type: "bit", nullable: false), - ApiResourceId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ApiScopes", x => x.Id); - table.ForeignKey( - name: "FK_ApiScopes_ApiResources_ApiResourceId", - column: x => x.ApiResourceId, - principalTable: "ApiResources", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ApiSecrets", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ApiResourceId = table.Column(type: "int", nullable: false), - Description = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: true), - Value = table.Column(type: "nvarchar(4000)", maxLength: 4000, nullable: false), - Expiration = table.Column(type: "datetime2", nullable: true), - Type = table.Column(type: "nvarchar(250)", maxLength: 250, nullable: false), - Created = table.Column(type: "datetime2", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ApiSecrets", x => x.Id); - table.ForeignKey( - name: "FK_ApiSecrets_ApiResources_ApiResourceId", - column: x => x.ApiResourceId, - principalTable: "ApiResources", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ClientClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - Type = table.Column(type: "nvarchar(250)", maxLength: 250, nullable: false), - Value = table.Column(type: "nvarchar(250)", maxLength: 250, nullable: false), - ClientId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ClientClaims", x => x.Id); - table.ForeignKey( - name: "FK_ClientClaims_Clients_ClientId", - column: x => x.ClientId, - principalTable: "Clients", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ClientCorsOrigins", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - Origin = table.Column(type: "nvarchar(150)", maxLength: 150, nullable: false), - ClientId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ClientCorsOrigins", x => x.Id); - table.ForeignKey( - name: "FK_ClientCorsOrigins_Clients_ClientId", - column: x => x.ClientId, - principalTable: "Clients", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ClientGrantTypes", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - GrantType = table.Column(type: "nvarchar(250)", maxLength: 250, nullable: false), - ClientId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ClientGrantTypes", x => x.Id); - table.ForeignKey( - name: "FK_ClientGrantTypes_Clients_ClientId", - column: x => x.ClientId, - principalTable: "Clients", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ClientIdPRestrictions", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - Provider = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), - ClientId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ClientIdPRestrictions", x => x.Id); - table.ForeignKey( - name: "FK_ClientIdPRestrictions_Clients_ClientId", - column: x => x.ClientId, - principalTable: "Clients", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ClientPostLogoutRedirectUris", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - PostLogoutRedirectUri = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: false), - ClientId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ClientPostLogoutRedirectUris", x => x.Id); - table.ForeignKey( - name: "FK_ClientPostLogoutRedirectUris_Clients_ClientId", - column: x => x.ClientId, - principalTable: "Clients", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ClientProperties", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ClientId = table.Column(type: "int", nullable: false), - Key = table.Column(type: "nvarchar(250)", maxLength: 250, nullable: false), - Value = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ClientProperties", x => x.Id); - table.ForeignKey( - name: "FK_ClientProperties_Clients_ClientId", - column: x => x.ClientId, - principalTable: "Clients", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ClientRedirectUris", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - RedirectUri = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: false), - ClientId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ClientRedirectUris", x => x.Id); - table.ForeignKey( - name: "FK_ClientRedirectUris_Clients_ClientId", - column: x => x.ClientId, - principalTable: "Clients", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ClientScopes", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - Scope = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), - ClientId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ClientScopes", x => x.Id); - table.ForeignKey( - name: "FK_ClientScopes_Clients_ClientId", - column: x => x.ClientId, - principalTable: "Clients", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ClientSecrets", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ClientId = table.Column(type: "int", nullable: false), - Description = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: true), - Value = table.Column(type: "nvarchar(4000)", maxLength: 4000, nullable: false), - Expiration = table.Column(type: "datetime2", nullable: true), - Type = table.Column(type: "nvarchar(250)", maxLength: 250, nullable: false), - Created = table.Column(type: "datetime2", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ClientSecrets", x => x.Id); - table.ForeignKey( - name: "FK_ClientSecrets_Clients_ClientId", - column: x => x.ClientId, - principalTable: "Clients", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "IdentityClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - IdentityResourceId = table.Column(type: "int", nullable: false), - Type = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_IdentityClaims", x => x.Id); - table.ForeignKey( - name: "FK_IdentityClaims_IdentityResources_IdentityResourceId", - column: x => x.IdentityResourceId, - principalTable: "IdentityResources", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "IdentityProperties", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - IdentityResourceId = table.Column(type: "int", nullable: false), - Key = table.Column(type: "nvarchar(250)", maxLength: 250, nullable: false), - Value = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_IdentityProperties", x => x.Id); - table.ForeignKey( - name: "FK_IdentityProperties_IdentityResources_IdentityResourceId", - column: x => x.IdentityResourceId, - principalTable: "IdentityResources", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ApiScopeClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ApiScopeId = table.Column(type: "int", nullable: false), - Type = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ApiScopeClaims", x => x.Id); - table.ForeignKey( - name: "FK_ApiScopeClaims_ApiScopes_ApiScopeId", - column: x => x.ApiScopeId, - principalTable: "ApiScopes", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_ApiClaims_ApiResourceId", - table: "ApiClaims", - column: "ApiResourceId"); - - migrationBuilder.CreateIndex( - name: "IX_ApiProperties_ApiResourceId", - table: "ApiProperties", - column: "ApiResourceId"); - - migrationBuilder.CreateIndex( - name: "IX_ApiResources_Name", - table: "ApiResources", - column: "Name", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_ApiScopeClaims_ApiScopeId", - table: "ApiScopeClaims", - column: "ApiScopeId"); - - migrationBuilder.CreateIndex( - name: "IX_ApiScopes_ApiResourceId", - table: "ApiScopes", - column: "ApiResourceId"); - - migrationBuilder.CreateIndex( - name: "IX_ApiScopes_Name", - table: "ApiScopes", - column: "Name", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_ApiSecrets_ApiResourceId", - table: "ApiSecrets", - column: "ApiResourceId"); - - migrationBuilder.CreateIndex( - name: "IX_ClientClaims_ClientId", - table: "ClientClaims", - column: "ClientId"); - - migrationBuilder.CreateIndex( - name: "IX_ClientCorsOrigins_ClientId", - table: "ClientCorsOrigins", - column: "ClientId"); - - migrationBuilder.CreateIndex( - name: "IX_ClientGrantTypes_ClientId", - table: "ClientGrantTypes", - column: "ClientId"); - - migrationBuilder.CreateIndex( - name: "IX_ClientIdPRestrictions_ClientId", - table: "ClientIdPRestrictions", - column: "ClientId"); - - migrationBuilder.CreateIndex( - name: "IX_ClientPostLogoutRedirectUris_ClientId", - table: "ClientPostLogoutRedirectUris", - column: "ClientId"); - - migrationBuilder.CreateIndex( - name: "IX_ClientProperties_ClientId", - table: "ClientProperties", - column: "ClientId"); - - migrationBuilder.CreateIndex( - name: "IX_ClientRedirectUris_ClientId", - table: "ClientRedirectUris", - column: "ClientId"); - - migrationBuilder.CreateIndex( - name: "IX_Clients_ClientId", - table: "Clients", - column: "ClientId", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_ClientScopes_ClientId", - table: "ClientScopes", - column: "ClientId"); - - migrationBuilder.CreateIndex( - name: "IX_ClientSecrets_ClientId", - table: "ClientSecrets", - column: "ClientId"); - - migrationBuilder.CreateIndex( - name: "IX_IdentityClaims_IdentityResourceId", - table: "IdentityClaims", - column: "IdentityResourceId"); - - migrationBuilder.CreateIndex( - name: "IX_IdentityProperties_IdentityResourceId", - table: "IdentityProperties", - column: "IdentityResourceId"); - - migrationBuilder.CreateIndex( - name: "IX_IdentityResources_Name", - table: "IdentityResources", - column: "Name", - unique: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "ApiClaims"); - - migrationBuilder.DropTable( - name: "ApiProperties"); - - migrationBuilder.DropTable( - name: "ApiScopeClaims"); - - migrationBuilder.DropTable( - name: "ApiSecrets"); - - migrationBuilder.DropTable( - name: "ClientClaims"); - - migrationBuilder.DropTable( - name: "ClientCorsOrigins"); - - migrationBuilder.DropTable( - name: "ClientGrantTypes"); - - migrationBuilder.DropTable( - name: "ClientIdPRestrictions"); - - migrationBuilder.DropTable( - name: "ClientPostLogoutRedirectUris"); - - migrationBuilder.DropTable( - name: "ClientProperties"); - - migrationBuilder.DropTable( - name: "ClientRedirectUris"); - - migrationBuilder.DropTable( - name: "ClientScopes"); - - migrationBuilder.DropTable( - name: "ClientSecrets"); - - migrationBuilder.DropTable( - name: "IdentityClaims"); - - migrationBuilder.DropTable( - name: "IdentityProperties"); - - migrationBuilder.DropTable( - name: "ApiScopes"); - - migrationBuilder.DropTable( - name: "Clients"); - - migrationBuilder.DropTable( - name: "IdentityResources"); - - migrationBuilder.DropTable( - name: "ApiResources"); - } - } -} diff --git a/src/Services/Identity/Identity.API/Migrations/ConfigurationDb/ConfigurationDbContextModelSnapshot.cs b/src/Services/Identity/Identity.API/Migrations/ConfigurationDb/ConfigurationDbContextModelSnapshot.cs deleted file mode 100644 index 7eb4d34e4..000000000 --- a/src/Services/Identity/Identity.API/Migrations/ConfigurationDb/ConfigurationDbContextModelSnapshot.cs +++ /dev/null @@ -1,909 +0,0 @@ -// -using System; -using IdentityServer4.EntityFramework.DbContexts; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -namespace Identity.API.Migrations.ConfigurationDb -{ - [DbContext(typeof(ConfigurationDbContext))] - partial class ConfigurationDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "6.0.0") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("Created") - .HasColumnType("datetime2"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("DisplayName") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("Enabled") - .HasColumnType("bit"); - - b.Property("LastAccessed") - .HasColumnType("datetime2"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("NonEditable") - .HasColumnType("bit"); - - b.Property("Updated") - .HasColumnType("datetime2"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("ApiResources", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ApiResourceId") - .HasColumnType("int"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.ToTable("ApiClaims", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ApiResourceId") - .HasColumnType("int"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.ToTable("ApiProperties", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ApiResourceId") - .HasColumnType("int"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("DisplayName") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("Emphasize") - .HasColumnType("bit"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("Required") - .HasColumnType("bit"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("bit"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("ApiScopes", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ApiScopeId") - .HasColumnType("int"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.HasKey("Id"); - - b.HasIndex("ApiScopeId"); - - b.ToTable("ApiScopeClaims", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ApiResourceId") - .HasColumnType("int"); - - b.Property("Created") - .HasColumnType("datetime2"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("Expiration") - .HasColumnType("datetime2"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(4000) - .HasColumnType("nvarchar(4000)"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.ToTable("ApiSecrets", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - 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("ClientClaimsPrefix") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("ClientId") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("ClientName") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("ClientUri") - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.Property("ConsentLifetime") - .HasColumnType("int"); - - b.Property("Created") - .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("LastAccessed") - .HasColumnType("datetime2"); - - b.Property("LogoUri") - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.Property("NonEditable") - .HasColumnType("bit"); - - b.Property("PairWiseSubjectSalt") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("ProtocolType") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("RefreshTokenExpiration") - .HasColumnType("int"); - - b.Property("RefreshTokenUsage") - .HasColumnType("int"); - - b.Property("RequireClientSecret") - .HasColumnType("bit"); - - b.Property("RequireConsent") - .HasColumnType("bit"); - - b.Property("RequirePkce") - .HasColumnType("bit"); - - b.Property("SlidingRefreshTokenLifetime") - .HasColumnType("int"); - - b.Property("UpdateAccessTokenClaimsOnRefresh") - .HasColumnType("bit"); - - b.Property("Updated") - .HasColumnType("datetime2"); - - b.Property("UserCodeType") - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("UserSsoLifetime") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("ClientId") - .IsUnique(); - - b.ToTable("Clients", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientClaims", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("Origin") - .IsRequired() - .HasMaxLength(150) - .HasColumnType("nvarchar(150)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientCorsOrigins", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("GrantType") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientGrantTypes", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientIdPRestrictions", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("PostLogoutRedirectUri") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientPostLogoutRedirectUris", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientProperties", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("RedirectUri") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientRedirectUris", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("Scope") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientScopes", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("Created") - .HasColumnType("datetime2"); - - b.Property("Description") - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.Property("Expiration") - .HasColumnType("datetime2"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(4000) - .HasColumnType("nvarchar(4000)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientSecrets", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("IdentityResourceId") - .HasColumnType("int"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.HasKey("Id"); - - b.HasIndex("IdentityResourceId"); - - b.ToTable("IdentityClaims", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("Created") - .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("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("NonEditable") - .HasColumnType("bit"); - - b.Property("Required") - .HasColumnType("bit"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("bit"); - - b.Property("Updated") - .HasColumnType("datetime2"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("IdentityResources", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("IdentityResourceId") - .HasColumnType("int"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.HasKey("Id"); - - b.HasIndex("IdentityResourceId"); - - b.ToTable("IdentityProperties", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany("UserClaims") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany("Properties") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany("Scopes") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "ApiScope") - .WithMany("UserClaims") - .HasForeignKey("ApiScopeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiScope"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiSecret", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany("Secrets") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("Claims") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedCorsOrigins") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedGrantTypes") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("IdentityProviderRestrictions") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("PostLogoutRedirectUris") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("Properties") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("RedirectUris") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedScopes") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("ClientSecrets") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityClaim", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource") - .WithMany("UserClaims") - .HasForeignKey("IdentityResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("IdentityResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource") - .WithMany("Properties") - .HasForeignKey("IdentityResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("IdentityResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b => - { - b.Navigation("Properties"); - - b.Navigation("Scopes"); - - b.Navigation("Secrets"); - - b.Navigation("UserClaims"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => - { - b.Navigation("UserClaims"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b => - { - b.Navigation("AllowedCorsOrigins"); - - b.Navigation("AllowedGrantTypes"); - - b.Navigation("AllowedScopes"); - - b.Navigation("Claims"); - - b.Navigation("ClientSecrets"); - - b.Navigation("IdentityProviderRestrictions"); - - b.Navigation("PostLogoutRedirectUris"); - - b.Navigation("Properties"); - - b.Navigation("RedirectUris"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b => - { - b.Navigation("Properties"); - - b.Navigation("UserClaims"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Services/Identity/Identity.API/Migrations/PersistedGrantDb/20210813072513_InitialMigration.Designer.cs b/src/Services/Identity/Identity.API/Migrations/PersistedGrantDb/20210813072513_InitialMigration.Designer.cs deleted file mode 100644 index 6e2567b4a..000000000 --- a/src/Services/Identity/Identity.API/Migrations/PersistedGrantDb/20210813072513_InitialMigration.Designer.cs +++ /dev/null @@ -1,108 +0,0 @@ -// -using System; -using IdentityServer4.EntityFramework.DbContexts; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -namespace Identity.API.Migrations.PersistedGrantDb -{ - [DbContext(typeof(PersistedGrantDbContext))] - [Migration("20210813072513_InitialMigration")] - partial class InitialMigration - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "6.0.0") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b => - { - b.Property("UserCode") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("ClientId") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("CreationTime") - .HasColumnType("datetime2"); - - b.Property("Data") - .IsRequired() - .HasMaxLength(50000) - .HasColumnType("nvarchar(max)"); - - b.Property("DeviceCode") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("Expiration") - .IsRequired() - .HasColumnType("datetime2"); - - b.Property("SubjectId") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.HasKey("UserCode"); - - b.HasIndex("DeviceCode") - .IsUnique(); - - b.HasIndex("Expiration"); - - b.ToTable("DeviceCodes", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b => - { - b.Property("Key") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("ClientId") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("CreationTime") - .HasColumnType("datetime2"); - - b.Property("Data") - .IsRequired() - .HasMaxLength(50000) - .HasColumnType("nvarchar(max)"); - - b.Property("Expiration") - .HasColumnType("datetime2"); - - b.Property("SubjectId") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.HasKey("Key"); - - b.HasIndex("Expiration"); - - b.HasIndex("SubjectId", "ClientId", "Type"); - - b.ToTable("PersistedGrants", (string)null); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Services/Identity/Identity.API/Migrations/PersistedGrantDb/20210813072513_InitialMigration.cs b/src/Services/Identity/Identity.API/Migrations/PersistedGrantDb/20210813072513_InitialMigration.cs deleted file mode 100644 index e81f8a197..000000000 --- a/src/Services/Identity/Identity.API/Migrations/PersistedGrantDb/20210813072513_InitialMigration.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Identity.API.Migrations.PersistedGrantDb -{ - public partial class InitialMigration : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "DeviceCodes", - columns: table => new - { - UserCode = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), - DeviceCode = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), - SubjectId = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), - ClientId = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), - CreationTime = table.Column(type: "datetime2", nullable: false), - Expiration = table.Column(type: "datetime2", nullable: false), - Data = table.Column(type: "nvarchar(max)", maxLength: 50000, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_DeviceCodes", x => x.UserCode); - }); - - migrationBuilder.CreateTable( - name: "PersistedGrants", - columns: table => new - { - Key = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), - Type = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - SubjectId = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), - ClientId = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), - CreationTime = table.Column(type: "datetime2", nullable: false), - Expiration = table.Column(type: "datetime2", nullable: true), - Data = table.Column(type: "nvarchar(max)", maxLength: 50000, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_PersistedGrants", x => x.Key); - }); - - migrationBuilder.CreateIndex( - name: "IX_DeviceCodes_DeviceCode", - table: "DeviceCodes", - column: "DeviceCode", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_DeviceCodes_Expiration", - table: "DeviceCodes", - column: "Expiration"); - - migrationBuilder.CreateIndex( - name: "IX_PersistedGrants_Expiration", - table: "PersistedGrants", - column: "Expiration"); - - migrationBuilder.CreateIndex( - name: "IX_PersistedGrants_SubjectId_ClientId_Type", - table: "PersistedGrants", - columns: new[] { "SubjectId", "ClientId", "Type" }); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "DeviceCodes"); - - migrationBuilder.DropTable( - name: "PersistedGrants"); - } - } -} diff --git a/src/Services/Identity/Identity.API/Migrations/PersistedGrantDb/PersistedGrantDbContextModelSnapshot.cs b/src/Services/Identity/Identity.API/Migrations/PersistedGrantDb/PersistedGrantDbContextModelSnapshot.cs deleted file mode 100644 index 769f60a5a..000000000 --- a/src/Services/Identity/Identity.API/Migrations/PersistedGrantDb/PersistedGrantDbContextModelSnapshot.cs +++ /dev/null @@ -1,106 +0,0 @@ -// -using System; -using IdentityServer4.EntityFramework.DbContexts; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -namespace Identity.API.Migrations.PersistedGrantDb -{ - [DbContext(typeof(PersistedGrantDbContext))] - partial class PersistedGrantDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "6.0.0") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b => - { - b.Property("UserCode") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("ClientId") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("CreationTime") - .HasColumnType("datetime2"); - - b.Property("Data") - .IsRequired() - .HasMaxLength(50000) - .HasColumnType("nvarchar(max)"); - - b.Property("DeviceCode") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("Expiration") - .IsRequired() - .HasColumnType("datetime2"); - - b.Property("SubjectId") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.HasKey("UserCode"); - - b.HasIndex("DeviceCode") - .IsUnique(); - - b.HasIndex("Expiration"); - - b.ToTable("DeviceCodes", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b => - { - b.Property("Key") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("ClientId") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("CreationTime") - .HasColumnType("datetime2"); - - b.Property("Data") - .IsRequired() - .HasMaxLength(50000) - .HasColumnType("nvarchar(max)"); - - b.Property("Expiration") - .HasColumnType("datetime2"); - - b.Property("SubjectId") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.HasKey("Key"); - - b.HasIndex("Expiration"); - - b.HasIndex("SubjectId", "ClientId", "Type"); - - b.ToTable("PersistedGrants", (string)null); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Services/Identity/Identity.API/Models/AccountViewModels/ConsentInputModel.cs b/src/Services/Identity/Identity.API/Models/AccountViewModels/ConsentInputModel.cs deleted file mode 100644 index fd4e524cf..000000000 --- a/src/Services/Identity/Identity.API/Models/AccountViewModels/ConsentInputModel.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Identity.API.Models.AccountViewModels -{ - public record ConsentInputModel - { - public string Button { get; init; } - public IEnumerable ScopesConsented { get; init; } - public bool RememberConsent { get; init; } - public string ReturnUrl { get; init; } - } -} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Models/AccountViewModels/ConsentViewModel.cs b/src/Services/Identity/Identity.API/Models/AccountViewModels/ConsentViewModel.cs deleted file mode 100644 index 6e9e46fe6..000000000 --- a/src/Services/Identity/Identity.API/Models/AccountViewModels/ConsentViewModel.cs +++ /dev/null @@ -1,61 +0,0 @@ -using IdentityServer4.Models; - -namespace Microsoft.eShopOnContainers.Services.Identity.API.Models.AccountViewModels -{ - public record ConsentViewModel : ConsentInputModel - { - public ConsentViewModel(ConsentInputModel model, string returnUrl, AuthorizationRequest request, Client client, Resources resources) - { - RememberConsent = model?.RememberConsent ?? true; - ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty(); - - ReturnUrl = returnUrl; - - ClientName = client.ClientName; - ClientUrl = client.ClientUri; - ClientLogoUrl = client.LogoUri; - AllowRememberConsent = client.AllowRememberConsent; - - IdentityScopes = resources.IdentityResources.Select(x => new ScopeViewModel(x, ScopesConsented.Contains(x.Name) || model == null)).ToArray(); - ResourceScopes = resources.ApiResources.SelectMany(x => x.Scopes).Select(x => new ScopeViewModel(x, ScopesConsented.Contains(x.Name) || model == null)).ToArray(); - } - - public string ClientName { get; init; } - public string ClientUrl { get; init; } - public string ClientLogoUrl { get; init; } - public bool AllowRememberConsent { get; init; } - - public IEnumerable IdentityScopes { get; init; } - public IEnumerable ResourceScopes { get; init; } - } - - public record ScopeViewModel - { - public ScopeViewModel(Scope scope, bool check) - { - Name = scope.Name; - DisplayName = scope.DisplayName; - Description = scope.Description; - Emphasize = scope.Emphasize; - Required = scope.Required; - Checked = check || scope.Required; - } - - public ScopeViewModel(IdentityResource identity, bool check) - { - Name = identity.Name; - DisplayName = identity.DisplayName; - Description = identity.Description; - Emphasize = identity.Emphasize; - Required = identity.Required; - Checked = check || identity.Required; - } - - public string Name { get; init; } - public string DisplayName { get; init; } - public string Description { get; init; } - public bool Emphasize { get; init; } - public bool Required { get; init; } - public bool Checked { get; init; } - } -} diff --git a/src/Services/Identity/Identity.API/Models/AccountViewModels/RedirectViewModel.cs b/src/Services/Identity/Identity.API/Models/AccountViewModels/RedirectViewModel.cs new file mode 100644 index 000000000..1693abb7d --- /dev/null +++ b/src/Services/Identity/Identity.API/Models/AccountViewModels/RedirectViewModel.cs @@ -0,0 +1,7 @@ +namespace Microsoft.eShopOnContainers.Services.Identity.API.Models.AccountViewModels +{ + public class RedirectViewModel + { + public string RedirectUrl { get; set; } + } +} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Models/ConsentViewModels/ConsentInputModel.cs b/src/Services/Identity/Identity.API/Models/ConsentViewModels/ConsentInputModel.cs new file mode 100644 index 000000000..38592c36f --- /dev/null +++ b/src/Services/Identity/Identity.API/Models/ConsentViewModels/ConsentInputModel.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Microsoft.eShopOnContainers.Services.Identity.API.Models.ConsentViewModels +{ + public class ConsentInputModel + { + 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; } + } +} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Models/ConsentViewModels/ConsentOptions.cs b/src/Services/Identity/Identity.API/Models/ConsentViewModels/ConsentOptions.cs new file mode 100644 index 000000000..065b1825c --- /dev/null +++ b/src/Services/Identity/Identity.API/Models/ConsentViewModels/ConsentOptions.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopOnContainers.Services.Identity.API.Models.ConsentViewModels +{ + public class ConsentOptions + { + public static bool EnableOfflineAccess = true; + public static string OfflineAccessDisplayName = "Offline Access"; + public static string OfflineAccessDescription = "Access to your applications and resources, even when you are offline"; + + public static readonly string MustChooseOneErrorMessage = "You must pick at least one permission"; + public static readonly string InvalidSelectionErrorMessage = "Invalid selection"; + } +} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Models/ConsentViewModels/ConsentViewModel.cs b/src/Services/Identity/Identity.API/Models/ConsentViewModels/ConsentViewModel.cs new file mode 100644 index 000000000..8f3b7594c --- /dev/null +++ b/src/Services/Identity/Identity.API/Models/ConsentViewModels/ConsentViewModel.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace Microsoft.eShopOnContainers.Services.Identity.API.Models.ConsentViewModels +{ + public class ConsentViewModel : ConsentInputModel + { + public string ClientName { get; set; } + public string ClientUrl { get; set; } + public string ClientLogoUrl { get; set; } + public bool AllowRememberConsent { get; set; } + + public IEnumerable IdentityScopes { get; set; } + public IEnumerable ApiScopes { get; set; } + } +} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Models/ConsentViewModels/ProcessConsentResult.cs b/src/Services/Identity/Identity.API/Models/ConsentViewModels/ProcessConsentResult.cs new file mode 100644 index 000000000..cecfc6898 --- /dev/null +++ b/src/Services/Identity/Identity.API/Models/ConsentViewModels/ProcessConsentResult.cs @@ -0,0 +1,17 @@ +using Duende.IdentityServer.Models; + +namespace Microsoft.eShopOnContainers.Services.Identity.API.Models.ConsentViewModels +{ + public class ProcessConsentResult + { + public bool IsRedirect => RedirectUri != null; + public string RedirectUri { get; set; } + public Client Client { get; set; } + + public bool ShowView => ViewModel != null; + public ConsentViewModel ViewModel { get; set; } + + public bool HasValidationError => ValidationError != null; + public string ValidationError { get; set; } + } +} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Models/ConsentViewModels/ScopeViewModel.cs b/src/Services/Identity/Identity.API/Models/ConsentViewModels/ScopeViewModel.cs new file mode 100644 index 000000000..55c842dc9 --- /dev/null +++ b/src/Services/Identity/Identity.API/Models/ConsentViewModels/ScopeViewModel.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopOnContainers.Services.Identity.API.Models.ConsentViewModels +{ + public class ScopeViewModel + { + public string Value { get; set; } + public string DisplayName { get; set; } + public string Description { get; set; } + public bool Emphasize { get; set; } + public bool Required { get; set; } + public bool Checked { get; set; } + } +} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Program.cs b/src/Services/Identity/Identity.API/Program.cs index 7c021c7a4..5a2fd3976 100644 --- a/src/Services/Identity/Identity.API/Program.cs +++ b/src/Services/Identity/Identity.API/Program.cs @@ -1,71 +1,75 @@ -string Namespace = typeof(Startup).Namespace; -string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1); +var appName = "Identity.API"; +var builder = WebApplication.CreateBuilder(); -var configuration = GetConfiguration(); +builder.AddCustomConfiguration(); +builder.AddCustomSerilog(); +builder.AddCustomMvc(); +builder.AddCustomDatabase(); +builder.AddCustomIdentity(); +builder.AddCustomIdentityServer(); +builder.AddCustomAuthentication(); +builder.AddCustomHealthChecks(); +builder.AddCustomApplicationServices(); -Log.Logger = CreateSerilogLogger(configuration); +var app = builder.Build(); +if (app.Environment.IsDevelopment()) +{ + app.UseDeveloperExceptionPage(); +} + +var pathBase = builder.Configuration["PATH_BASE"]; +if (!string.IsNullOrEmpty(pathBase)) +{ + app.UsePathBase(pathBase); +} +app.UseStaticFiles(); + +// This cookie policy fixes login issues with Chrome 80+ using HHTP +app.UseCookiePolicy(new CookiePolicyOptions { MinimumSameSitePolicy = SameSiteMode.Lax }); + +app.UseRouting(); + +app.UseIdentityServer(); + + +app.UseAuthorization(); + +app.MapDefaultControllerRoute(); +app.MapHealthChecks("/hc", new HealthCheckOptions() +{ + Predicate = _ => true, + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse +}); +app.MapHealthChecks("/liveness", new HealthCheckOptions +{ + Predicate = r => r.Name.Contains("self") +}); try { - Log.Information("Configuring web host ({ApplicationContext})...", AppName); - var host = BuildWebHost(configuration, args); - - Log.Information("Applying migrations ({ApplicationContext})...", AppName); - host.MigrateDbContext((_, __) => { }) - .MigrateDbContext((context, services) => - { - var env = services.GetService(); - var logger = services.GetService>(); - var settings = services.GetService>(); - - new ApplicationDbContextSeed() - .SeedAsync(context, env, logger, settings) - .Wait(); - }) - .MigrateDbContext((context, services) => - { - new ConfigurationDbContextSeed() - .SeedAsync(context, configuration) - .Wait(); - }); - - Log.Information("Starting web host ({ApplicationContext})...", AppName); - host.Run(); + app.Logger.LogInformation("Seeding database ({ApplicationName})...", appName); + + // Apply database migration automatically. Note that this approach is not + // recommended for production scenarios. Consider generating SQL scripts from + // migrations instead. + using (var scope = app.Services.CreateScope()) + { + await SeedData.EnsureSeedData(scope, app.Configuration, app.Logger); + } + + app.Logger.LogInformation("Starting web host ({ApplicationName})...", appName); + app.Run(); return 0; } catch (Exception ex) { - Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", AppName); + app.Logger.LogCritical(ex, "Host terminated unexpectedly ({ApplicationName})...", appName); return 1; } finally { - Log.CloseAndFlush(); -} - -IWebHost BuildWebHost(IConfiguration configuration, string[] args) => - WebHost.CreateDefaultBuilder(args) - .CaptureStartupErrors(false) - .ConfigureAppConfiguration(x => x.AddConfiguration(configuration)) - .UseStartup() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseSerilog() - .Build(); - -Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) -{ - var seqServerUrl = configuration["Serilog:SeqServerUrl"]; - var logstashUrl = configuration["Serilog:LogstashgUrl"]; - return new LoggerConfiguration() - .MinimumLevel.Verbose() - .Enrich.WithProperty("ApplicationContext", AppName) - .Enrich.FromLogContext() - .WriteTo.Console() - .WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) - .WriteTo.Http(string.IsNullOrWhiteSpace(logstashUrl) ? "http://localhost:8080" : logstashUrl) - .ReadFrom.Configuration(configuration) - .CreateLogger(); + Serilog.Log.CloseAndFlush(); } IConfiguration GetConfiguration() diff --git a/src/Services/Identity/Identity.API/ProgramExtensions.cs b/src/Services/Identity/Identity.API/ProgramExtensions.cs new file mode 100644 index 000000000..729295570 --- /dev/null +++ b/src/Services/Identity/Identity.API/ProgramExtensions.cs @@ -0,0 +1,117 @@ +using Serilog; + +namespace Microsoft.eShopOnContainers.Services.Identity.API; + +public static class ProgramExtensions +{ + private const string AppName = "Identity API"; + + public static void AddCustomConfiguration(this WebApplicationBuilder builder) + { + builder.Configuration.AddConfiguration(GetConfiguration()).Build(); + + } + + public static void AddCustomSerilog(this WebApplicationBuilder builder) + { + var seqServerUrl = builder.Configuration["SeqServerUrl"]; + var logstashUrl = builder.Configuration["LogstashgUrl"]; + + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Verbose() + .Enrich.WithProperty("ApplicationContext", AppName) + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) + .WriteTo.Http(string.IsNullOrWhiteSpace(logstashUrl) ? "http://localhost:8080" : logstashUrl, null) + .ReadFrom.Configuration(builder.Configuration) + .CreateLogger(); + + builder.Host.UseSerilog(); + } + + public static void AddCustomMvc(this WebApplicationBuilder builder) + { + builder.Services.AddControllersWithViews(); + builder.Services.AddControllers(); + builder.Services.AddRazorPages(); + + } + + + public static void AddCustomDatabase(this WebApplicationBuilder builder) => + builder.Services.AddDbContext( + options => options.UseSqlServer(builder.Configuration["ConnectionString"])); + + public static void AddCustomIdentity(this WebApplicationBuilder builder) + { + builder.Services.AddIdentity() + .AddEntityFrameworkStores() + .AddDefaultTokenProviders(); + } + + + public static void AddCustomIdentityServer(this WebApplicationBuilder builder) + { + var identityServerBuilder = builder.Services.AddIdentityServer(options => + { + options.IssuerUri = "null"; + options.Authentication.CookieLifetime = TimeSpan.FromHours(2); + + options.Events.RaiseErrorEvents = true; + options.Events.RaiseInformationEvents = true; + options.Events.RaiseFailureEvents = true; + options.Events.RaiseSuccessEvents = true; + }) + .AddInMemoryIdentityResources(Config.GetResources()) + .AddInMemoryApiScopes(Config.GetApiScopes()) + .AddInMemoryApiResources(Config.GetApis()) + .AddInMemoryClients(Config.GetClients(builder.Configuration)) + .AddAspNetIdentity(); + + // not recommended for production - you need to store your key material somewhere secure + identityServerBuilder.AddDeveloperSigningCredential(); + } + + public static void AddCustomAuthentication(this WebApplicationBuilder builder) + { + builder.Services.AddAuthentication(); + } + + public static void AddCustomHealthChecks(this WebApplicationBuilder builder) + { + builder.Services.AddHealthChecks() + .AddCheck("self", () => HealthCheckResult.Healthy()) + .AddSqlServer(builder.Configuration["ConnectionString"], + name: "IdentityDB-check", + tags: new string[] { "IdentityDB" }); + } + + public static void AddCustomApplicationServices(this WebApplicationBuilder builder) + { + builder.Services.AddTransient(); + builder.Services.AddTransient, EFLoginService>(); + builder.Services.AddTransient(); + } + + static IConfiguration GetConfiguration() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddEnvironmentVariables(); + + var config = builder.Build(); + + if (config.GetValue("UseVault", false)) + { + TokenCredential credential = new ClientSecretCredential( + config["Vault:TenantId"], + config["Vault:ClientId"], + config["Vault:ClientSecret"]); + builder.AddAzureKeyVault(new Uri($"https://{config["Vault:Name"]}.vault.azure.net/"), credential); + } + + return builder.Build(); + } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Account/AccountController.cs b/src/Services/Identity/Identity.API/Quickstart/Account/AccountController.cs new file mode 100644 index 000000000..f14c99896 --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Account/AccountController.cs @@ -0,0 +1,341 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +using Duende.IdentityServer.Events; +using Duende.IdentityServer.Extensions; +using Duende.IdentityServer.Stores; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace IdentityServerHost.Quickstart.UI +{ + [SecurityHeaders] + [AllowAnonymous] + public class AccountController : Controller + { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + private readonly IIdentityServerInteractionService _interaction; + private readonly IClientStore _clientStore; + private readonly IAuthenticationSchemeProvider _schemeProvider; + private readonly IAuthenticationHandlerProvider _handlerProvider; + private readonly IEventService _events; + + public AccountController( + UserManager userManager, + SignInManager signInManager, + IIdentityServerInteractionService interaction, + IClientStore clientStore, + IAuthenticationSchemeProvider schemeProvider, + IAuthenticationHandlerProvider handlerProvider, + IEventService events) + { + _userManager = userManager; + _signInManager = signInManager; + _interaction = interaction; + _clientStore = clientStore; + _schemeProvider = schemeProvider; + _handlerProvider = handlerProvider; + _events = events; + } + + /// + /// Entry point into the login workflow + /// + [HttpGet] + public async Task Login(string returnUrl) + { + // build a model so we know what to show on the login page + var vm = await BuildLoginViewModelAsync(returnUrl); + + ViewData["ReturnUrl"] = returnUrl; + + if (vm.IsExternalLoginOnly) + { + // we only have one option for logging in and it's an external provider + return RedirectToAction("Challenge", "External", new { scheme = vm.ExternalLoginScheme, returnUrl }); + } + + return View(vm); + } + + /// + /// Handle postback from username/password login + /// + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Login(LoginInputModel model, string button) + { + // check if we are in the context of an authorization request + var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); + + // the user clicked the "cancel" button + if (button != "login") + { + if (context != null) + { + // if the user cancels, send a result back into IdentityServer as if they + // denied the consent (even if this client does not require consent). + // this will send back an access denied OIDC error response to the client. + await _interaction.DenyAuthorizationAsync(context, AuthorizationError.AccessDenied); + + // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null + if (context.IsNativeClient()) + { + // The client is native, so this change in how to + // return the response is for better UX for the end user. + return this.LoadingPage("Redirect", model.ReturnUrl); + } + + return Redirect(model.ReturnUrl); + } + else + { + // since we don't have a valid context, then we just go back to the home page + return Redirect("~/"); + } + } + + if (ModelState.IsValid) + { + var result = await _signInManager.PasswordSignInAsync(model.Username, model.Password, model.RememberLogin, lockoutOnFailure: true); + if (result.Succeeded) + { + var user = await _userManager.FindByNameAsync(model.Username); + await _events.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id, user.UserName, clientId: context?.Client.ClientId)); + + if (context != null) + { + if (context.IsNativeClient()) + { + // The client is native, so this change in how to + // return the response is for better UX for the end user. + return this.LoadingPage("Redirect", model.ReturnUrl); + } + + // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null + return Redirect(model.ReturnUrl); + } + + // request for a local page + if (Url.IsLocalUrl(model.ReturnUrl)) + { + return Redirect(model.ReturnUrl); + } + else if (string.IsNullOrEmpty(model.ReturnUrl)) + { + return Redirect("~/"); + } + else + { + // user might have clicked on a malicious link - should be logged + throw new Exception("invalid return URL"); + } + } + + await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials", clientId:context?.Client.ClientId)); + ModelState.AddModelError(string.Empty, AccountOptions.InvalidCredentialsErrorMessage); + } + + // something went wrong, show form with error + var vm = await BuildLoginViewModelAsync(model); + + ViewData["ReturnUrl"] = model.ReturnUrl; + + return View(vm); + } + + + /// + /// Show logout page + /// + [HttpGet] + public async Task Logout(string logoutId) + { + // build a model so the logout page knows what to display + var vm = await BuildLogoutViewModelAsync(logoutId); + + if (vm.ShowLogoutPrompt == false) + { + // if the request for logout was properly authenticated from IdentityServer, then + // we don't need to show the prompt and can just log the user out directly. + return await Logout(vm); + } + + return View(vm); + } + + /// + /// Handle logout page postback + /// + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Logout(LogoutInputModel model) + { + // build a model so the logged out page knows what to display + var vm = await BuildLoggedOutViewModelAsync(model.LogoutId); + + if (User?.Identity.IsAuthenticated == true) + { + // delete local authentication cookie + await _signInManager.SignOutAsync(); + + // raise the logout event + await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName())); + } + + // check if we need to trigger sign-out at an upstream identity provider + if (vm.TriggerExternalSignout) + { + // 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 }); + + // this triggers a redirect to the external provider for sign-out + return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme); + } + + return View("LoggedOut", vm); + } + + [HttpGet] + public IActionResult AccessDenied() + { + return View(); + } + + + /*****************************************/ + /* helper APIs for the AccountController */ + /*****************************************/ + private async Task BuildLoginViewModelAsync(string returnUrl) + { + var context = await _interaction.GetAuthorizationContextAsync(returnUrl); + if (context?.IdP != null && await _schemeProvider.GetSchemeAsync(context.IdP) != null) + { + var local = context.IdP == IdentityServerConstants.LocalIdentityProvider; + + // this is meant to short circuit the UI and only trigger the one external IdP + var vm = new LoginViewModel + { + EnableLocalLogin = local, + ReturnUrl = returnUrl, + Username = context?.LoginHint, + }; + + if (!local) + { + vm.ExternalProviders = new[] { new ExternalProvider { AuthenticationScheme = context.IdP } }; + } + + return vm; + } + + var schemes = await _schemeProvider.GetAllSchemesAsync(); + + var providers = schemes + .Where(x => x.DisplayName != null) + .Select(x => new ExternalProvider + { + DisplayName = x.DisplayName ?? x.Name, + AuthenticationScheme = x.Name + }).ToList(); + + var allowLocal = true; + if (context?.Client.ClientId != null) + { + var client = await _clientStore.FindEnabledClientByIdAsync(context.Client.ClientId); + if (client != null) + { + allowLocal = client.EnableLocalLogin; + + if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Any()) + { + providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList(); + } + } + } + + return new LoginViewModel + { + AllowRememberLogin = AccountOptions.AllowRememberLogin, + EnableLocalLogin = allowLocal && AccountOptions.AllowLocalLogin, + ReturnUrl = returnUrl, + Username = context?.LoginHint, + ExternalProviders = providers.ToArray() + }; + } + + private async Task BuildLoginViewModelAsync(LoginInputModel model) + { + var vm = await BuildLoginViewModelAsync(model.ReturnUrl); + vm.Username = model.Username; + vm.RememberLogin = model.RememberLogin; + return vm; + } + + private async Task BuildLogoutViewModelAsync(string logoutId) + { + var vm = new LogoutViewModel { LogoutId = logoutId, ShowLogoutPrompt = AccountOptions.ShowLogoutPrompt }; + + if (User?.Identity.IsAuthenticated != true) + { + // if the user is not authenticated, then just show logged out page + vm.ShowLogoutPrompt = false; + return vm; + } + + var context = await _interaction.GetLogoutContextAsync(logoutId); + if (context?.ShowSignoutPrompt == false) + { + // it's safe to automatically sign-out + vm.ShowLogoutPrompt = false; + return vm; + } + + // show the logout prompt. this prevents attacks where the user + // is automatically signed out by another malicious web page. + return vm; + } + + private async Task BuildLoggedOutViewModelAsync(string logoutId) + { + // get context information (client name, post logout redirect URI and iframe for federated signout) + var logout = await _interaction.GetLogoutContextAsync(logoutId); + + var vm = new LoggedOutViewModel + { + AutomaticRedirectAfterSignOut = AccountOptions.AutomaticRedirectAfterSignOut, + PostLogoutRedirectUri = logout?.PostLogoutRedirectUri, + ClientName = string.IsNullOrEmpty(logout?.ClientName) ? logout?.ClientId : logout?.ClientName, + SignOutIframeUrl = logout?.SignOutIFrameUrl, + LogoutId = logoutId + }; + + if (User?.Identity.IsAuthenticated == true) + { + var idp = User.FindFirst(JwtClaimTypes.IdentityProvider)?.Value; + if (idp != null && idp != IdentityServerConstants.LocalIdentityProvider) + { + var handler = await _handlerProvider.GetHandlerAsync(HttpContext, idp); + if (handler is IAuthenticationSignOutHandler) + { + if (vm.LogoutId == null) + { + // if there's no current logout context, we need to create one + // this captures necessary info from the current logged in user + // before we signout and redirect away to the external IdP for signout + vm.LogoutId = await _interaction.CreateLogoutContextAsync(); + } + + vm.ExternalAuthenticationScheme = idp; + } + } + } + + return vm; + } + } +} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Quickstart/Account/AccountOptions.cs b/src/Services/Identity/Identity.API/Quickstart/Account/AccountOptions.cs new file mode 100644 index 000000000..00ef16bc0 --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Account/AccountOptions.cs @@ -0,0 +1,16 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +namespace IdentityServerHost.Quickstart.UI; + +public class AccountOptions +{ + public static bool AllowLocalLogin = true; + public static bool AllowRememberLogin = true; + public static TimeSpan RememberMeLoginDuration = TimeSpan.FromDays(30); + + public static bool ShowLogoutPrompt = false; + public static bool AutomaticRedirectAfterSignOut = true; + + public static string InvalidCredentialsErrorMessage = "Invalid username or password"; +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Account/ExternalController.cs b/src/Services/Identity/Identity.API/Quickstart/Account/ExternalController.cs new file mode 100644 index 000000000..5abc2fa3b --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Account/ExternalController.cs @@ -0,0 +1,238 @@ +namespace IdentityServerHost.Quickstart.UI; + +[SecurityHeaders] +[AllowAnonymous] +public class ExternalController : Controller +{ + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + private readonly IIdentityServerInteractionService _interaction; + private readonly IClientStore _clientStore; + private readonly IEventService _events; + private readonly ILogger _logger; + + public ExternalController( + UserManager userManager, + SignInManager signInManager, + IIdentityServerInteractionService interaction, + IClientStore clientStore, + IEventService events, + ILogger logger) + { + _userManager = userManager; + _signInManager = signInManager; + _interaction = interaction; + _clientStore = clientStore; + _events = events; + _logger = logger; + } + + /// + /// initiate roundtrip to external authentication provider + /// + [HttpGet] + public IActionResult Challenge(string scheme, string returnUrl) + { + if (string.IsNullOrEmpty(returnUrl)) returnUrl = "~/"; + + // validate returnUrl - either it is a valid OIDC URL or back to a local page + if (Url.IsLocalUrl(returnUrl) == false && _interaction.IsValidReturnUrl(returnUrl) == false) + { + // user might have clicked on a malicious link - should be logged + throw new Exception("invalid return URL"); + } + + // start challenge and roundtrip the return URL and scheme + var props = new AuthenticationProperties + { + RedirectUri = Url.Action(nameof(Callback)), + Items = + { + { "returnUrl", returnUrl }, + { "scheme", scheme }, + } + }; + + return Challenge(props, scheme); + + } + + /// + /// Post processing of external authentication + /// + [HttpGet] + public async Task Callback() + { + // read external identity from the temporary cookie + var result = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme); + if (result?.Succeeded != true) + { + throw new Exception("External authentication error"); + } + + if (_logger.IsEnabled(LogLevel.Debug)) + { + var externalClaims = result.Principal.Claims.Select(c => $"{c.Type}: {c.Value}"); + _logger.LogDebug("External claims: {@claims}", externalClaims); + } + + // lookup our user and external provider info + var (user, provider, providerUserId, claims) = await FindUserFromExternalProviderAsync(result); + if (user == null) + { + // this might be where you might initiate a custom workflow for user registration + // in this sample we don't show how that would be done, as our sample implementation + // simply auto-provisions new external user + user = await AutoProvisionUserAsync(provider, providerUserId, claims); + } + + // this allows us to collect any additional claims or properties + // for the specific protocols used and store them in the local auth cookie. + // this is typically used to store data needed for signout from those protocols. + var additionalLocalClaims = new List(); + var localSignInProps = new AuthenticationProperties(); + ProcessLoginCallback(result, additionalLocalClaims, localSignInProps); + + // issue authentication cookie for user + // we must issue the cookie maually, and can't use the SignInManager because + // it doesn't expose an API to issue additional claims from the login workflow + var principal = await _signInManager.CreateUserPrincipalAsync(user); + additionalLocalClaims.AddRange(principal.Claims); + var name = principal.FindFirst(JwtClaimTypes.Name)?.Value ?? user.Id; + + var isuser = new IdentityServerUser(user.Id) + { + DisplayName = name, + IdentityProvider = provider, + AdditionalClaims = additionalLocalClaims + }; + + await HttpContext.SignInAsync(isuser, localSignInProps); + + // delete temporary cookie used during external authentication + await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme); + + // retrieve return URL + var returnUrl = result.Properties.Items["returnUrl"] ?? "~/"; + + // check if external login is in the context of an OIDC request + var context = await _interaction.GetAuthorizationContextAsync(returnUrl); + await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.Id, name, true, context?.Client.ClientId)); + + if (context != null) + { + if (context.IsNativeClient()) + { + // The client is native, so this change in how to + // return the response is for better UX for the end user. + return this.LoadingPage("Redirect", returnUrl); + } + } + + return Redirect(returnUrl); + } + + private async Task<(ApplicationUser user, string provider, string providerUserId, IEnumerable claims)> + FindUserFromExternalProviderAsync(AuthenticateResult result) + { + 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 + // depending on the external provider, some other claim type might be used + var userIdClaim = externalUser.FindFirst(JwtClaimTypes.Subject) ?? + externalUser.FindFirst(ClaimTypes.NameIdentifier) ?? + throw new Exception("Unknown userid"); + + // remove the user id claim so we don't include it as an extra claim if/when we provision the user + var claims = externalUser.Claims.ToList(); + claims.Remove(userIdClaim); + + var provider = result.Properties.Items["scheme"]; + var providerUserId = userIdClaim.Value; + + // find external user + var user = await _userManager.FindByLoginAsync(provider, providerUserId); + + return (user, provider, providerUserId, claims); + } + + private async Task AutoProvisionUserAsync(string provider, string providerUserId, IEnumerable claims) + { + // create a list of claims that we want to transfer into our store + var filtered = new List(); + + // user's display name + var name = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.Name)?.Value ?? + claims.FirstOrDefault(x => x.Type == ClaimTypes.Name)?.Value; + if (name != null) + { + filtered.Add(new Claim(JwtClaimTypes.Name, name)); + } + else + { + var first = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.GivenName)?.Value ?? + claims.FirstOrDefault(x => x.Type == ClaimTypes.GivenName)?.Value; + var last = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.FamilyName)?.Value ?? + claims.FirstOrDefault(x => x.Type == ClaimTypes.Surname)?.Value; + if (first != null && last != null) + { + filtered.Add(new Claim(JwtClaimTypes.Name, first + " " + last)); + } + else if (first != null) + { + filtered.Add(new Claim(JwtClaimTypes.Name, first)); + } + else if (last != null) + { + filtered.Add(new Claim(JwtClaimTypes.Name, last)); + } + } + + // email + var email = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.Email)?.Value ?? + claims.FirstOrDefault(x => x.Type == ClaimTypes.Email)?.Value; + if (email != null) + { + filtered.Add(new Claim(JwtClaimTypes.Email, email)); + } + + var user = new ApplicationUser + { + UserName = Guid.NewGuid().ToString(), + }; + var identityResult = await _userManager.CreateAsync(user); + if (!identityResult.Succeeded) throw new Exception(identityResult.Errors.First().Description); + + if (filtered.Any()) + { + identityResult = await _userManager.AddClaimsAsync(user, filtered); + if (!identityResult.Succeeded) throw new Exception(identityResult.Errors.First().Description); + } + + identityResult = await _userManager.AddLoginAsync(user, new UserLoginInfo(provider, providerUserId, provider)); + if (!identityResult.Succeeded) throw new Exception(identityResult.Errors.First().Description); + + return user; + } + + // if the external login is OIDC-based, there are certain things we need to preserve to make logout work + // this will be different for WS-Fed, SAML2p or other protocols + private void ProcessLoginCallback(AuthenticateResult externalResult, List localClaims, AuthenticationProperties localSignInProps) + { + // if the external system sent a session id claim, copy it over + // so we can use it for single sign-out + var sid = externalResult.Principal.Claims.FirstOrDefault(x => x.Type == JwtClaimTypes.SessionId); + if (sid != null) + { + localClaims.Add(new Claim(JwtClaimTypes.SessionId, sid.Value)); + } + + // if the external provider issued an id_token, we'll keep it for signout + var idToken = externalResult.Properties.GetTokenValue("id_token"); + if (idToken != null) + { + localSignInProps.StoreTokens(new[] { new AuthenticationToken { Name = "id_token", Value = idToken } }); + } + } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Account/ExternalProvider.cs b/src/Services/Identity/Identity.API/Quickstart/Account/ExternalProvider.cs new file mode 100644 index 000000000..c92594f4e --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Account/ExternalProvider.cs @@ -0,0 +1,10 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +namespace IdentityServerHost.Quickstart.UI; + +public class ExternalProvider +{ + public string DisplayName { get; set; } + public string AuthenticationScheme { get; set; } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Account/LoggedOutViewModel.cs b/src/Services/Identity/Identity.API/Quickstart/Account/LoggedOutViewModel.cs new file mode 100644 index 000000000..7e55f2cb6 --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Account/LoggedOutViewModel.cs @@ -0,0 +1,18 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI; + +public class LoggedOutViewModel +{ + 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 bool TriggerExternalSignout => ExternalAuthenticationScheme != null; + public string ExternalAuthenticationScheme { get; set; } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Account/LoginInputModel.cs b/src/Services/Identity/Identity.API/Quickstart/Account/LoginInputModel.cs new file mode 100644 index 000000000..813ba8fc1 --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Account/LoginInputModel.cs @@ -0,0 +1,15 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI; + +public class LoginInputModel +{ + [Required] + public string Username { get; set; } + [Required] + public string Password { get; set; } + public bool RememberLogin { get; set; } + public string ReturnUrl { get; set; } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Account/LoginViewModel.cs b/src/Services/Identity/Identity.API/Quickstart/Account/LoginViewModel.cs new file mode 100644 index 000000000..753f4773e --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Account/LoginViewModel.cs @@ -0,0 +1,17 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI; + +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 bool IsExternalLoginOnly => EnableLocalLogin == false && ExternalProviders?.Count() == 1; + public string ExternalLoginScheme => IsExternalLoginOnly ? ExternalProviders?.SingleOrDefault()?.AuthenticationScheme : null; +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Account/LogoutInputModel.cs b/src/Services/Identity/Identity.API/Quickstart/Account/LogoutInputModel.cs new file mode 100644 index 000000000..2fd5d8c8f --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Account/LogoutInputModel.cs @@ -0,0 +1,10 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI; + +public class LogoutInputModel +{ + public string LogoutId { get; set; } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Account/LogoutViewModel.cs b/src/Services/Identity/Identity.API/Quickstart/Account/LogoutViewModel.cs new file mode 100644 index 000000000..841289b70 --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Account/LogoutViewModel.cs @@ -0,0 +1,10 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI; + +public class LogoutViewModel : LogoutInputModel +{ + public bool ShowLogoutPrompt { get; set; } = true; +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Account/RedirectViewModel.cs b/src/Services/Identity/Identity.API/Quickstart/Account/RedirectViewModel.cs new file mode 100644 index 000000000..e5005f4c2 --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Account/RedirectViewModel.cs @@ -0,0 +1,11 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + + +namespace IdentityServerHost.Quickstart.UI; + +public class RedirectViewModel +{ + public string RedirectUrl { get; set; } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Consent/ConsentController.cs b/src/Services/Identity/Identity.API/Quickstart/Consent/ConsentController.cs new file mode 100644 index 000000000..1255df44a --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Consent/ConsentController.cs @@ -0,0 +1,247 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +namespace IdentityServerHost.Quickstart.UI; + +/// +/// This controller processes the consent UI +/// +[SecurityHeaders] +[Authorize] +public class ConsentController : Controller +{ + private readonly IIdentityServerInteractionService _interaction; + private readonly IEventService _events; + private readonly ILogger _logger; + + public ConsentController( + IIdentityServerInteractionService interaction, + IEventService events, + ILogger logger) + { + _interaction = interaction; + _events = events; + _logger = logger; + } + + /// + /// Shows the consent screen + /// + /// + /// + [HttpGet] + public async Task Index(string returnUrl) + { + var vm = await BuildViewModelAsync(returnUrl); + if (vm != null) + { + return View("Index", vm); + } + + return View("Error"); + } + + /// + /// Handles the consent screen postback + /// + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Index(ConsentInputModel model) + { + var result = await ProcessConsent(model); + + if (result.IsRedirect) + { + var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); + if (context?.IsNativeClient() == true) + { + // The client is native, so this change in how to + // return the response is for better UX for the end user. + return this.LoadingPage("Redirect", result.RedirectUri); + } + + return Redirect(result.RedirectUri); + } + + if (result.HasValidationError) + { + ModelState.AddModelError(string.Empty, result.ValidationError); + } + + if (result.ShowView) + { + return View("Index", result.ViewModel); + } + + return View("Error"); + } + + /*****************************************/ + /* helper APIs for the ConsentController */ + /*****************************************/ + private async Task ProcessConsent(ConsentInputModel model) + { + var result = new ProcessConsentResult(); + + // validate return url is still valid + var request = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); + if (request == null) return result; + + ConsentResponse grantedConsent = null; + + // user clicked 'no' - send back the standard 'access_denied' response + if (model?.Button == "no") + { + grantedConsent = new ConsentResponse { Error = AuthorizationError.AccessDenied }; + + // emit event + await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); + } + // user clicked 'yes' - validate the data + else if (model?.Button == "yes") + { + // if the user consented to some scope, build the response model + if (model.ScopesConsented != null && model.ScopesConsented.Any()) + { + var scopes = model.ScopesConsented; + if (ConsentOptions.EnableOfflineAccess == false) + { + scopes = scopes.Where(x => x != IdentityServerConstants.StandardScopes.OfflineAccess); + } + + grantedConsent = new ConsentResponse + { + RememberConsent = model.RememberConsent, + ScopesValuesConsented = scopes.ToArray(), + Description = model.Description + }; + + // emit event + await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); + } + else + { + result.ValidationError = ConsentOptions.MustChooseOneErrorMessage; + } + } + else + { + result.ValidationError = ConsentOptions.InvalidSelectionErrorMessage; + } + + if (grantedConsent != null) + { + // communicate outcome of consent back to identityserver + await _interaction.GrantConsentAsync(request, grantedConsent); + + // indicate that's it ok to redirect back to authorization endpoint + result.RedirectUri = model.ReturnUrl; + result.Client = request.Client; + } + else + { + // we need to redisplay the consent UI + result.ViewModel = await BuildViewModelAsync(model.ReturnUrl, model); + } + + return result; + } + + private async Task BuildViewModelAsync(string returnUrl, ConsentInputModel model = null) + { + var request = await _interaction.GetAuthorizationContextAsync(returnUrl); + if (request != null) + { + return CreateConsentViewModel(model, returnUrl, request); + } + else + { + _logger.LogError("No consent request matching request: {0}", returnUrl); + } + + return null; + } + + private ConsentViewModel CreateConsentViewModel( + ConsentInputModel model, string returnUrl, + AuthorizationRequest request) + { + var vm = new ConsentViewModel + { + RememberConsent = model?.RememberConsent ?? true, + ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty(), + Description = model?.Description, + + ReturnUrl = returnUrl, + + ClientName = request.Client.ClientName ?? request.Client.ClientId, + ClientUrl = request.Client.ClientUri, + ClientLogoUrl = request.Client.LogoUri, + AllowRememberConsent = request.Client.AllowRememberConsent + }; + + vm.IdentityScopes = request.ValidatedResources.Resources.IdentityResources.Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray(); + + var apiScopes = new List(); + foreach (var parsedScope in request.ValidatedResources.ParsedScopes) + { + var apiScope = request.ValidatedResources.Resources.FindApiScope(parsedScope.ParsedName); + if (apiScope != null) + { + var scopeVm = CreateScopeViewModel(parsedScope, apiScope, vm.ScopesConsented.Contains(parsedScope.RawValue) || model == null); + apiScopes.Add(scopeVm); + } + } + if (ConsentOptions.EnableOfflineAccess && request.ValidatedResources.Resources.OfflineAccess) + { + apiScopes.Add(GetOfflineAccessScope(vm.ScopesConsented.Contains(IdentityServerConstants.StandardScopes.OfflineAccess) || model == null)); + } + vm.ApiScopes = apiScopes; + + return vm; + } + + private ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check) + { + return new ScopeViewModel + { + Value = identity.Name, + DisplayName = identity.DisplayName ?? identity.Name, + Description = identity.Description, + Emphasize = identity.Emphasize, + Required = identity.Required, + Checked = check || identity.Required + }; + } + + public ScopeViewModel CreateScopeViewModel(ParsedScopeValue parsedScopeValue, ApiScope apiScope, bool check) + { + var displayName = apiScope.DisplayName ?? apiScope.Name; + if (!String.IsNullOrWhiteSpace(parsedScopeValue.ParsedParameter)) + { + displayName += ":" + parsedScopeValue.ParsedParameter; + } + + return new ScopeViewModel + { + Value = parsedScopeValue.RawValue, + DisplayName = displayName, + Description = apiScope.Description, + Emphasize = apiScope.Emphasize, + Required = apiScope.Required, + Checked = check || apiScope.Required + }; + } + + private ScopeViewModel GetOfflineAccessScope(bool check) + { + return new ScopeViewModel + { + Value = IdentityServerConstants.StandardScopes.OfflineAccess, + DisplayName = ConsentOptions.OfflineAccessDisplayName, + Description = ConsentOptions.OfflineAccessDescription, + Emphasize = true, + Checked = check + }; + } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Consent/ConsentInputModel.cs b/src/Services/Identity/Identity.API/Quickstart/Consent/ConsentInputModel.cs new file mode 100644 index 000000000..fc661024e --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Consent/ConsentInputModel.cs @@ -0,0 +1,14 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI; + +public class ConsentInputModel +{ + 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; } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Consent/ConsentOptions.cs b/src/Services/Identity/Identity.API/Quickstart/Consent/ConsentOptions.cs new file mode 100644 index 000000000..4a689278c --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Consent/ConsentOptions.cs @@ -0,0 +1,15 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI; + +public class ConsentOptions +{ + public static bool EnableOfflineAccess = true; + public static string OfflineAccessDisplayName = "Offline Access"; + public static string OfflineAccessDescription = "Access to your applications and resources, even when you are offline"; + + public static readonly string MustChooseOneErrorMessage = "You must pick at least one permission"; + public static readonly string InvalidSelectionErrorMessage = "Invalid selection"; +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Consent/ConsentViewModel.cs b/src/Services/Identity/Identity.API/Quickstart/Consent/ConsentViewModel.cs new file mode 100644 index 000000000..0603d7836 --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Consent/ConsentViewModel.cs @@ -0,0 +1,16 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI; + +public class ConsentViewModel : ConsentInputModel +{ + public string ClientName { get; set; } + public string ClientUrl { get; set; } + public string ClientLogoUrl { get; set; } + public bool AllowRememberConsent { get; set; } + + public IEnumerable IdentityScopes { get; set; } + public IEnumerable ApiScopes { get; set; } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Consent/ProcessConsentResult.cs b/src/Services/Identity/Identity.API/Quickstart/Consent/ProcessConsentResult.cs new file mode 100644 index 000000000..a7dd8bb7e --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Consent/ProcessConsentResult.cs @@ -0,0 +1,17 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +namespace IdentityServerHost.Quickstart.UI; + +public class ProcessConsentResult +{ + public bool IsRedirect => RedirectUri != null; + public string RedirectUri { get; set; } + public Client Client { get; set; } + + public bool ShowView => ViewModel != null; + public ConsentViewModel ViewModel { get; set; } + + public bool HasValidationError => ValidationError != null; + public string ValidationError { get; set; } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Consent/ScopeViewModel.cs b/src/Services/Identity/Identity.API/Quickstart/Consent/ScopeViewModel.cs new file mode 100644 index 000000000..c052253c2 --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Consent/ScopeViewModel.cs @@ -0,0 +1,15 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI; + +public class ScopeViewModel +{ + public string Value { get; set; } + public string DisplayName { get; set; } + public string Description { get; set; } + public bool Emphasize { get; set; } + public bool Required { get; set; } + public bool Checked { get; set; } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Device/DeviceAuthorizationInputModel.cs b/src/Services/Identity/Identity.API/Quickstart/Device/DeviceAuthorizationInputModel.cs new file mode 100644 index 000000000..8e6403960 --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Device/DeviceAuthorizationInputModel.cs @@ -0,0 +1,10 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI; + +public class DeviceAuthorizationInputModel : ConsentInputModel +{ + public string UserCode { get; set; } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Device/DeviceAuthorizationViewModel.cs b/src/Services/Identity/Identity.API/Quickstart/Device/DeviceAuthorizationViewModel.cs new file mode 100644 index 000000000..230413f26 --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Device/DeviceAuthorizationViewModel.cs @@ -0,0 +1,11 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI; + +public class DeviceAuthorizationViewModel : ConsentViewModel +{ + public string UserCode { get; set; } + public bool ConfirmUserCode { get; set; } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Device/DeviceController.cs b/src/Services/Identity/Identity.API/Quickstart/Device/DeviceController.cs new file mode 100644 index 000000000..5eb55caad --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Device/DeviceController.cs @@ -0,0 +1,214 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +namespace IdentityServerHost.Quickstart.UI; + +[Authorize] +[SecurityHeaders] +public class DeviceController : Controller +{ + private readonly IDeviceFlowInteractionService _interaction; + private readonly IEventService _events; + private readonly IOptions _options; + private readonly ILogger _logger; + + public DeviceController( + IDeviceFlowInteractionService interaction, + IEventService eventService, + IOptions options, + ILogger logger) + { + _interaction = interaction; + _events = eventService; + _options = options; + _logger = logger; + } + + [HttpGet] + public async Task Index() + { + string userCodeParamName = _options.Value.UserInteraction.DeviceVerificationUserCodeParameter; + string userCode = Request.Query[userCodeParamName]; + if (string.IsNullOrWhiteSpace(userCode)) return View("UserCodeCapture"); + + var vm = await BuildViewModelAsync(userCode); + if (vm == null) return View("Error"); + + vm.ConfirmUserCode = true; + return View("UserCodeConfirmation", vm); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task UserCodeCapture(string userCode) + { + var vm = await BuildViewModelAsync(userCode); + if (vm == null) return View("Error"); + + return View("UserCodeConfirmation", vm); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Callback(DeviceAuthorizationInputModel model) + { + if (model == null) throw new ArgumentNullException(nameof(model)); + + var result = await ProcessConsent(model); + if (result.HasValidationError) return View("Error"); + + return View("Success"); + } + + private async Task ProcessConsent(DeviceAuthorizationInputModel model) + { + var result = new ProcessConsentResult(); + + var request = await _interaction.GetAuthorizationContextAsync(model.UserCode); + if (request == null) return result; + + ConsentResponse grantedConsent = null; + + // user clicked 'no' - send back the standard 'access_denied' response + if (model.Button == "no") + { + grantedConsent = new ConsentResponse { Error = AuthorizationError.AccessDenied }; + + // emit event + await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); + } + // user clicked 'yes' - validate the data + else if (model.Button == "yes") + { + // if the user consented to some scope, build the response model + if (model.ScopesConsented != null && model.ScopesConsented.Any()) + { + var scopes = model.ScopesConsented; + if (ConsentOptions.EnableOfflineAccess == false) + { + scopes = scopes.Where(x => x != IdentityServerConstants.StandardScopes.OfflineAccess); + } + + grantedConsent = new ConsentResponse + { + RememberConsent = model.RememberConsent, + ScopesValuesConsented = scopes.ToArray(), + Description = model.Description + }; + + // emit event + await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); + } + else + { + result.ValidationError = ConsentOptions.MustChooseOneErrorMessage; + } + } + else + { + result.ValidationError = ConsentOptions.InvalidSelectionErrorMessage; + } + + if (grantedConsent != null) + { + // communicate outcome of consent back to identityserver + await _interaction.HandleRequestAsync(model.UserCode, grantedConsent); + + // indicate that's it ok to redirect back to authorization endpoint + result.RedirectUri = model.ReturnUrl; + result.Client = request.Client; + } + else + { + // we need to redisplay the consent UI + result.ViewModel = await BuildViewModelAsync(model.UserCode, model); + } + + return result; + } + + private async Task BuildViewModelAsync(string userCode, DeviceAuthorizationInputModel model = null) + { + var request = await _interaction.GetAuthorizationContextAsync(userCode); + if (request != null) + { + return CreateConsentViewModel(userCode, model, request); + } + + return null; + } + + private DeviceAuthorizationViewModel CreateConsentViewModel(string userCode, DeviceAuthorizationInputModel model, DeviceFlowAuthorizationRequest request) + { + var vm = new DeviceAuthorizationViewModel + { + UserCode = userCode, + Description = model?.Description, + + RememberConsent = model?.RememberConsent ?? true, + ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty(), + + ClientName = request.Client.ClientName ?? request.Client.ClientId, + ClientUrl = request.Client.ClientUri, + ClientLogoUrl = request.Client.LogoUri, + AllowRememberConsent = request.Client.AllowRememberConsent + }; + + vm.IdentityScopes = request.ValidatedResources.Resources.IdentityResources.Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray(); + + var apiScopes = new List(); + foreach (var parsedScope in request.ValidatedResources.ParsedScopes) + { + var apiScope = request.ValidatedResources.Resources.FindApiScope(parsedScope.ParsedName); + if (apiScope != null) + { + var scopeVm = CreateScopeViewModel(parsedScope, apiScope, vm.ScopesConsented.Contains(parsedScope.RawValue) || model == null); + apiScopes.Add(scopeVm); + } + } + if (ConsentOptions.EnableOfflineAccess && request.ValidatedResources.Resources.OfflineAccess) + { + apiScopes.Add(GetOfflineAccessScope(vm.ScopesConsented.Contains(IdentityServerConstants.StandardScopes.OfflineAccess) || model == null)); + } + vm.ApiScopes = apiScopes; + + return vm; + } + + private ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check) + { + return new ScopeViewModel + { + Value = identity.Name, + DisplayName = identity.DisplayName ?? identity.Name, + Description = identity.Description, + Emphasize = identity.Emphasize, + Required = identity.Required, + Checked = check || identity.Required + }; + } + + public ScopeViewModel CreateScopeViewModel(ParsedScopeValue parsedScopeValue, ApiScope apiScope, bool check) + { + return new ScopeViewModel + { + Value = parsedScopeValue.RawValue, + DisplayName = apiScope.DisplayName ?? apiScope.Name, + Description = apiScope.Description, + Emphasize = apiScope.Emphasize, + Required = apiScope.Required, + Checked = check || apiScope.Required + }; + } + private ScopeViewModel GetOfflineAccessScope(bool check) + { + return new ScopeViewModel + { + Value = IdentityServerConstants.StandardScopes.OfflineAccess, + DisplayName = ConsentOptions.OfflineAccessDisplayName, + Description = ConsentOptions.OfflineAccessDescription, + Emphasize = true, + Checked = check + }; + } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Diagnostics/DiagnosticsController.cs b/src/Services/Identity/Identity.API/Quickstart/Diagnostics/DiagnosticsController.cs new file mode 100644 index 000000000..9668eec1a --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Diagnostics/DiagnosticsController.cs @@ -0,0 +1,22 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI; + +[SecurityHeaders] +[Authorize] +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())) + { + return NotFound(); + } + + var model = new DiagnosticsViewModel(await HttpContext.AuthenticateAsync()); + return View(model); + } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Diagnostics/DiagnosticsViewModel.cs b/src/Services/Identity/Identity.API/Quickstart/Diagnostics/DiagnosticsViewModel.cs new file mode 100644 index 000000000..6f87fd1d7 --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Diagnostics/DiagnosticsViewModel.cs @@ -0,0 +1,28 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using System.Text; +using System.Text.Json; + +namespace IdentityServerHost.Quickstart.UI; + +public class DiagnosticsViewModel +{ + public DiagnosticsViewModel(AuthenticateResult result) + { + AuthenticateResult = result; + + if (result.Properties.Items.ContainsKey("client_list")) + { + var encoded = result.Properties.Items["client_list"]; + var bytes = Base64Url.Decode(encoded); + var value = Encoding.UTF8.GetString(bytes); + + Clients = JsonSerializer.Deserialize(value); + } + } + + public AuthenticateResult AuthenticateResult { get; } + public IEnumerable Clients { get; } = new List(); +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Extensions.cs b/src/Services/Identity/Identity.API/Quickstart/Extensions.cs new file mode 100644 index 000000000..4dbaf5385 --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Extensions.cs @@ -0,0 +1,22 @@ +namespace IdentityServerHost.Quickstart.UI; + +public static class Extensions +{ + /// + /// Checks if the redirect URI is for a native client. + /// + /// + public static bool IsNativeClient(this AuthorizationRequest context) + { + return !context.RedirectUri.StartsWith("https", StringComparison.Ordinal) + && !context.RedirectUri.StartsWith("http", StringComparison.Ordinal); + } + + public static IActionResult LoadingPage(this Controller controller, string viewName, string redirectUri) + { + controller.HttpContext.Response.StatusCode = 200; + controller.HttpContext.Response.Headers["Location"] = ""; + + return controller.View(viewName, new RedirectViewModel { RedirectUrl = redirectUri }); + } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Grants/GrantsController.cs b/src/Services/Identity/Identity.API/Quickstart/Grants/GrantsController.cs new file mode 100644 index 000000000..3045c44d5 --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Grants/GrantsController.cs @@ -0,0 +1,85 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +namespace IdentityServerHost.Quickstart.UI; + +/// +/// This sample controller allows a user to revoke grants given to clients +/// +[SecurityHeaders] +[Authorize] +public class GrantsController : Controller +{ + private readonly IIdentityServerInteractionService _interaction; + private readonly IClientStore _clients; + private readonly IResourceStore _resources; + private readonly IEventService _events; + + public GrantsController(IIdentityServerInteractionService interaction, + IClientStore clients, + IResourceStore resources, + IEventService events) + { + _interaction = interaction; + _clients = clients; + _resources = resources; + _events = events; + } + + /// + /// Show list of grants + /// + [HttpGet] + public async Task Index() + { + return View("Index", await BuildViewModelAsync()); + } + + /// + /// Handle postback to revoke a client + /// + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Revoke(string clientId) + { + await _interaction.RevokeUserConsentAsync(clientId); + await _events.RaiseAsync(new GrantsRevokedEvent(User.GetSubjectId(), clientId)); + + return RedirectToAction("Index"); + } + + private async Task BuildViewModelAsync() + { + var grants = await _interaction.GetAllUserGrantsAsync(); + + var list = new List(); + foreach (var grant in grants) + { + var client = await _clients.FindClientByIdAsync(grant.ClientId); + if (client != null) + { + var resources = await _resources.FindResourcesByScopeAsync(grant.Scopes); + + var item = new GrantViewModel() + { + ClientId = client.ClientId, + ClientName = client.ClientName ?? client.ClientId, + ClientLogoUrl = client.LogoUri, + ClientUrl = client.ClientUri, + Description = grant.Description, + Created = grant.CreationTime, + Expires = grant.Expiration, + IdentityGrantNames = resources.IdentityResources.Select(x => x.DisplayName ?? x.Name).ToArray(), + ApiGrantNames = resources.ApiScopes.Select(x => x.DisplayName ?? x.Name).ToArray() + }; + + list.Add(item); + } + } + + return new GrantsViewModel + { + Grants = list + }; + } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Grants/GrantsViewModel.cs b/src/Services/Identity/Identity.API/Quickstart/Grants/GrantsViewModel.cs new file mode 100644 index 000000000..991d27967 --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Grants/GrantsViewModel.cs @@ -0,0 +1,23 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI; + +public class GrantsViewModel +{ + public IEnumerable Grants { get; set; } +} + +public class GrantViewModel +{ + public string ClientId { get; set; } + public string ClientName { get; set; } + public string ClientUrl { get; set; } + public string ClientLogoUrl { get; set; } + public string Description { get; set; } + public DateTime Created { get; set; } + public DateTime? Expires { get; set; } + public IEnumerable IdentityGrantNames { get; set; } + public IEnumerable ApiGrantNames { get; set; } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Home/ErrorViewModel.cs b/src/Services/Identity/Identity.API/Quickstart/Home/ErrorViewModel.cs new file mode 100644 index 000000000..e1cfa86cb --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Home/ErrorViewModel.cs @@ -0,0 +1,18 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +namespace IdentityServerHost.Quickstart.UI; + +public class ErrorViewModel +{ + public ErrorViewModel() + { + } + + public ErrorViewModel(string error) + { + Error = new ErrorMessage { Error = error }; + } + + public ErrorMessage Error { get; set; } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Home/HomeController.cs b/src/Services/Identity/Identity.API/Quickstart/Home/HomeController.cs new file mode 100644 index 000000000..6110df1f8 --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Home/HomeController.cs @@ -0,0 +1,63 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace IdentityServerHost.Quickstart.UI +{ + [SecurityHeaders] + [AllowAnonymous] + public class HomeController : Controller + { + private readonly IIdentityServerInteractionService _interaction; + private readonly IWebHostEnvironment _environment; + private readonly ILogger _logger; + + public HomeController( + IIdentityServerInteractionService interaction, + IWebHostEnvironment environment, + ILogger logger) + { + _interaction = interaction; + _environment = environment; + _logger = logger; + } + + public IActionResult Index() + { + if (_environment.IsDevelopment()) + { + // only show in development + return View(); + } + + _logger.LogInformation("Homepage is disabled in production. Returning 404."); + return NotFound(); + } + + /// + /// Shows the error page + /// + public async Task Error(string errorId) + { + var vm = new ErrorViewModel(); + + // retrieve error details from identityserver + var message = await _interaction.GetErrorContextAsync(errorId); + if (message != null) + { + vm.Error = message; + + if (!_environment.IsDevelopment()) + { + // only show in development + message.ErrorDescription = null; + } + } + + return View("Error", vm); + } + } +} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Quickstart/SecurityHeadersAttribute.cs b/src/Services/Identity/Identity.API/Quickstart/SecurityHeadersAttribute.cs new file mode 100644 index 000000000..df926beea --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/SecurityHeadersAttribute.cs @@ -0,0 +1,52 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI; + +public class SecurityHeadersAttribute : ActionFilterAttribute +{ + public override void OnResultExecuting(ResultExecutingContext context) + { + var result = context.Result; + if (result is ViewResult) + { + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options + if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Type-Options")) + { + context.HttpContext.Response.Headers.Add("X-Content-Type-Options", "nosniff"); + } + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options + if (!context.HttpContext.Response.Headers.ContainsKey("X-Frame-Options")) + { + context.HttpContext.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN"); + } + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy + var csp = "default-src 'self'; object-src 'none'; frame-ancestors 'none'; sandbox allow-forms allow-same-origin allow-scripts; base-uri 'self';"; + // also consider adding upgrade-insecure-requests once you have HTTPS in place for production + //csp += "upgrade-insecure-requests;"; + // also an example if you need client images to be displayed from twitter + // csp += "img-src 'self' https://pbs.twimg.com;"; + + // once for standards compliant browsers + if (!context.HttpContext.Response.Headers.ContainsKey("Content-Security-Policy")) + { + context.HttpContext.Response.Headers.Add("Content-Security-Policy", csp); + } + // and once again for IE + if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Security-Policy")) + { + context.HttpContext.Response.Headers.Add("X-Content-Security-Policy", csp); + } + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy + var referrer_policy = "no-referrer"; + if (!context.HttpContext.Response.Headers.ContainsKey("Referrer-Policy")) + { + context.HttpContext.Response.Headers.Add("Referrer-Policy", referrer_policy); + } + } + } +} diff --git a/src/Services/Identity/Identity.API/SeedData.cs b/src/Services/Identity/Identity.API/SeedData.cs new file mode 100644 index 000000000..ece8c6d54 --- /dev/null +++ b/src/Services/Identity/Identity.API/SeedData.cs @@ -0,0 +1,124 @@ +using System.Threading.Tasks; +using System; + +namespace Microsoft.eShopOnContainers.Services.Identity.API; + +public class SeedData +{ + public static async Task EnsureSeedData(IServiceScope scope, IConfiguration configuration, Microsoft.Extensions.Logging.ILogger logger) + { + var retryPolicy = CreateRetryPolicy(configuration, logger); + var context = scope.ServiceProvider.GetRequiredService(); + + await retryPolicy.ExecuteAsync(async () => + { + await context.Database.MigrateAsync(); + + var userMgr = scope.ServiceProvider.GetRequiredService>(); + var alice = await userMgr.FindByNameAsync("alice"); + + if (alice == null) + { + alice = new ApplicationUser + { + UserName = "alice", + Email = "AliceSmith@email.com", + EmailConfirmed = true, + CardHolderName = "Alice Smith", + CardNumber = "4012888888881881", + CardType = 1, + City = "Redmond", + Country = "U.S.", + Expiration = "12/24", + Id = Guid.NewGuid().ToString(), + LastName = "Smith", + Name = "Alice", + PhoneNumber = "1234567890", + ZipCode = "98052", + State = "WA", + Street = "15703 NE 61st Ct", + SecurityNumber = "123" + }; + + var result = userMgr.CreateAsync(alice, "Pass123$").Result; + + if (!result.Succeeded) + { + throw new Exception(result.Errors.First().Description); + } + + logger.LogDebug("alice created"); + } + else + { + logger.LogDebug("alice already exists"); + } + + var bob = await userMgr.FindByNameAsync("bob"); + + if (bob == null) + { + bob = new ApplicationUser + { + UserName = "bob", + Email = "BobSmith@email.com", + EmailConfirmed = true, + CardHolderName = "Bob Smith", + CardNumber = "4012888888881881", + CardType = 1, + City = "Redmond", + Country = "U.S.", + Expiration = "12/24", + Id = Guid.NewGuid().ToString(), + LastName = "Smith", + Name = "Bob", + PhoneNumber = "1234567890", + ZipCode = "98052", + State = "WA", + Street = "15703 NE 61st Ct", + SecurityNumber = "456" + }; + + var result = await userMgr.CreateAsync(bob, "Pass123$"); + + if (!result.Succeeded) + { + throw new Exception(result.Errors.First().Description); + } + + logger.LogDebug("bob created"); + } + else + { + logger.LogDebug("bob already exists"); + } + }); + } + + private static AsyncPolicy CreateRetryPolicy(IConfiguration configuration, Microsoft.Extensions.Logging.ILogger logger) + { + var retryMigrations = false; + bool.TryParse(configuration["RetryMigrations"], out retryMigrations); + + // Only use a retry policy if configured to do so. + // When running in an orchestrator/K8s, it will take care of restarting failed services. + if (retryMigrations) + { + return Policy.Handle(). + WaitAndRetryForeverAsync( + sleepDurationProvider: retry => TimeSpan.FromSeconds(5), + onRetry: (exception, retry, timeSpan) => + { + logger.LogWarning( + exception, + "Exception {ExceptionType} with message {Message} detected during database migration (retry attempt {retry})", + exception.GetType().Name, + exception.Message, + retry); + } + ); + } + + return Policy.NoOpAsync(); + } +} diff --git a/src/Services/Identity/Identity.API/Setup/Users.csv b/src/Services/Identity/Identity.API/Setup/Users.csv deleted file mode 100644 index a0955884b..000000000 --- a/src/Services/Identity/Identity.API/Setup/Users.csv +++ /dev/null @@ -1,2 +0,0 @@ -CardHolderName,CardNumber,CardType,City,Country,Email,Expiration,LastName,Name,PhoneNumber,UserName,ZipCode,State,Street,SecurityNumber,NormalizedEmail,NormalizedUserName,Password -DemoUser,4012888888881881,1,Redmond,U.S.,demouser@microsoft.com,12/25,DemoLastName,DemoUser,1234567890,demouser@microsoft.com,98052,WA,15703 NE 61st Ct,535,DEMOUSER@MICROSOFT.COM,DEMOUSER@MICROSOFT.COM,Pass@word1 \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Setup/images.zip b/src/Services/Identity/Identity.API/Setup/images.zip deleted file mode 100644 index 1b901d3af..000000000 Binary files a/src/Services/Identity/Identity.API/Setup/images.zip and /dev/null differ diff --git a/src/Services/Identity/Identity.API/Startup.cs b/src/Services/Identity/Identity.API/Startup.cs deleted file mode 100644 index 43662acdd..000000000 --- a/src/Services/Identity/Identity.API/Startup.cs +++ /dev/null @@ -1,163 +0,0 @@ -using Microsoft.AspNetCore.DataProtection; - -namespace Microsoft.eShopOnContainers.Services.Identity.API -{ - public class Startup - { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public IServiceProvider ConfigureServices(IServiceCollection services) - { - RegisterAppInsights(services); - - // Add framework services. - services.AddDbContext(options => - options.UseSqlServer(Configuration["ConnectionString"], - sqlServerOptionsAction: sqlOptions => - { - sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name); - //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency - sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); - })); - - services.AddIdentity() - .AddEntityFrameworkStores() - .AddDefaultTokenProviders(); - - services.Configure(Configuration); - - if (Configuration.GetValue("IsClusterEnv") == bool.TrueString) - { - services.AddDataProtection(opts => - { - opts.ApplicationDiscriminator = "eshop.identity"; - }) - .PersistKeysToStackExchangeRedis(ConnectionMultiplexer.Connect(Configuration["DPConnectionString"]), "DataProtection-Keys"); - } - - services.AddHealthChecks() - .AddCheck("self", () => HealthCheckResult.Healthy()) - .AddSqlServer(Configuration["ConnectionString"], - name: "IdentityDB-check", - tags: new string[] { "IdentityDB" }); - - services.AddTransient, EFLoginService>(); - services.AddTransient(); - - var connectionString = Configuration["ConnectionString"]; - var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name; - - // Adds IdentityServer - services.AddIdentityServer(x => - { - x.IssuerUri = "null"; - x.Authentication.CookieLifetime = TimeSpan.FromHours(2); - }) - .AddDevspacesIfNeeded(Configuration.GetValue("EnableDevspaces", false)) - .AddSigningCredential(Certificate.Get()) - .AddAspNetIdentity() - .AddConfigurationStore(options => - { - options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString, - sqlServerOptionsAction: sqlOptions => - { - sqlOptions.MigrationsAssembly(migrationsAssembly); - //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency - sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); - }); - }) - .AddOperationalStore(options => - { - options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString, - sqlServerOptionsAction: sqlOptions => - { - sqlOptions.MigrationsAssembly(migrationsAssembly); - //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency - sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); - }); - }) - .Services.AddTransient(); - - //services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0); - services.AddControllers(); - services.AddControllersWithViews(); - services.AddRazorPages(); - - var container = new ContainerBuilder(); - container.Populate(services); - - return new AutofacServiceProvider(container.Build()); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) - { - //loggerFactory.AddConsole(Configuration.GetSection("Logging")); - //loggerFactory.AddDebug(); - //loggerFactory.AddAzureWebAppDiagnostics(); - //loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); - - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - else - { - app.UseExceptionHandler("/Home/Error"); - } - - var pathBase = Configuration["PATH_BASE"]; - if (!string.IsNullOrEmpty(pathBase)) - { - //loggerFactory.CreateLogger().LogDebug("Using PATH BASE '{pathBase}'", pathBase); - app.UsePathBase(pathBase); - } - - app.UseStaticFiles(); - - // Make work identity server redirections in Edge and lastest versions of browsers. WARN: Not valid in a production environment. - app.Use(async (context, next) => - { - context.Response.Headers.Add("Content-Security-Policy", "script-src 'unsafe-inline'"); - await next(); - }); - - app.UseForwardedHeaders(); - // Adds IdentityServer - app.UseIdentityServer(); - - // Fix a problem with chrome. Chrome enabled a new feature "Cookies without SameSite must be secure", - // the cookies should be expired from https, but in eShop, the internal communication in aks and docker compose is http. - // To avoid this problem, the policy of cookies should be in Lax mode. - app.UseCookiePolicy(new CookiePolicyOptions { MinimumSameSitePolicy = AspNetCore.Http.SameSiteMode.Lax }); - app.UseRouting(); - - app.UseEndpoints(endpoints => - { - endpoints.MapDefaultControllerRoute(); - endpoints.MapControllers(); - endpoints.MapHealthChecks("/hc", new HealthCheckOptions() - { - Predicate = _ => true, - ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse - }); - endpoints.MapHealthChecks("/liveness", new HealthCheckOptions - { - Predicate = r => r.Name.Contains("self") - }); - }); - } - - private void RegisterAppInsights(IServiceCollection services) - { - services.AddApplicationInsightsTelemetry(Configuration); - services.AddApplicationInsightsKubernetesEnricher(); - } - } -} diff --git a/src/Services/Identity/Identity.API/Views/Account/AccessDenied.cshtml b/src/Services/Identity/Identity.API/Views/Account/AccessDenied.cshtml new file mode 100644 index 000000000..32e6c531b --- /dev/null +++ b/src/Services/Identity/Identity.API/Views/Account/AccessDenied.cshtml @@ -0,0 +1,7 @@ + +
+
+

Access Denied

+

You do not have access to that resource.

+
+
\ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Views/Account/LoggedOut.cshtml b/src/Services/Identity/Identity.API/Views/Account/LoggedOut.cshtml index f843973f8..3cc190b0b 100644 --- a/src/Services/Identity/Identity.API/Views/Account/LoggedOut.cshtml +++ b/src/Services/Identity/Identity.API/Views/Account/LoggedOut.cshtml @@ -1,6 +1,11 @@ -@model Microsoft.eShopOnContainers.Services.Identity.API.Models.AccountViewModels.LoggedOutViewModel +@model LoggedOutViewModel -