diff --git a/src/Services/Ordering/Ordering.API/Program.cs b/src/Services/Ordering/Ordering.API/Program.cs index 66499d73e..632bc37af 100644 --- a/src/Services/Ordering/Ordering.API/Program.cs +++ b/src/Services/Ordering/Ordering.API/Program.cs @@ -1,65 +1,152 @@ -var configuration = GetConfiguration(); +using Autofac.Core; +using Microsoft.Azure.Amqp.Framing; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; -Log.Logger = CreateSerilogLogger(configuration); +var appName = "Ordering.API"; +var builder = WebApplication.CreateBuilder(new WebApplicationOptions { + Args = args, + ApplicationName = typeof(Program).Assembly.FullName, + ContentRootPath = Directory.GetCurrentDirectory() +}); +if (builder.Configuration.GetValue("UseVault", false)) { + TokenCredential credential = new ClientSecretCredential( + builder.Configuration["Vault:TenantId"], + builder.Configuration["Vault:ClientId"], + builder.Configuration["Vault:ClientSecret"]); + builder.Configuration.AddAzureKeyVault(new Uri($"https://{builder.Configuration["Vault:Name"]}.vault.azure.net/"), credential); +} + + + + +builder.Configuration.SetBasePath(Directory.GetCurrentDirectory()); +builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true); +builder.Configuration.AddEnvironmentVariables(); +builder.WebHost.ConfigureKestrel(options => { + var ports = GetDefinedPorts(builder.Configuration); + options.Listen(IPAddress.Any, ports.httpPort, listenOptions => { + listenOptions.Protocols = HttpProtocols.Http1AndHttp2; + }); + + options.Listen(IPAddress.Any, ports.grpcPort, listenOptions => { + listenOptions.Protocols = HttpProtocols.Http2; + }); -try -{ +}); +builder.WebHost.CaptureStartupErrors(false); +builder.Host.UseSerilog(CreateSerilogLogger(builder.Configuration)); + +builder.Services + .AddGrpc(options => { + options.EnableDetailedErrors = true; + }) + .Services + .AddApplicationInsights(builder.Configuration) + .AddCustomMvc() + .AddHealthChecks(builder.Configuration) + .AddCustomDbContext(builder.Configuration) + .AddCustomSwagger(builder.Configuration) + .AddCustomAuthentication(builder.Configuration) + .AddCustomAuthorization(builder.Configuration) + .AddCustomIntegrations(builder.Configuration) + .AddCustomConfiguration(builder.Configuration) + .AddEventBus(builder.Configuration); + +builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); + +// Register your own things directly with Autofac here. Don't +// call builder.Populate(), that happens in AutofacServiceProviderFactory +// for you. +builder.Host.ConfigureContainer(conbuilder => conbuilder.RegisterModule(new MediatorModule())); +builder.Host.ConfigureContainer(conbuilder => conbuilder.RegisterModule(new ApplicationModule(builder.Configuration["ConnectionString"]))); + +var app = builder.Build(); +if (app.Environment.IsDevelopment()) { + app.UseDeveloperExceptionPage(); +} +else { + app.UseExceptionHandler("/Home/Error"); +} +var pathBase = app.Configuration["PATH_BASE"]; +if (!string.IsNullOrEmpty(pathBase)) { + app.UsePathBase(pathBase); +} + +app.UseSwagger() + .UseSwaggerUI(c => { + c.SwaggerEndpoint($"{(!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty)}/swagger/v1/swagger.json", "Ordering.API V1"); + c.OAuthClientId("orderingswaggerui"); + c.OAuthAppName("Ordering Swagger UI"); + }); + +app.UseRouting(); +app.UseCors("CorsPolicy"); +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapGrpcService(); + app.MapDefaultControllerRoute(); + app.MapControllers(); + app.MapGet("/_proto/", async ctx => + { + ctx.Response.ContentType = "text/plain"; + using var fs = new FileStream(Path.Combine(app.Environment.ContentRootPath, "Proto", "basket.proto"), FileMode.Open, FileAccess.Read); + using var sr = new StreamReader(fs); + while (!sr.EndOfStream) { + var line = await sr.ReadLineAsync(); + if (line != "/* >>" || line != "<< */") { + await ctx.Response.WriteAsync(line); + } + } + }); + app.MapHealthChecks("/hc", new HealthCheckOptions() { + Predicate = _ => true, + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse + }); + app.MapHealthChecks("/liveness", new HealthCheckOptions { + Predicate = r => r.Name.Contains("self") + }); +ConfigureEventBus(app); +try { Log.Information("Configuring web host ({ApplicationContext})...", Program.AppName); - var host = BuildWebHost(configuration, args); + Log.Information("Applying migrations ({ApplicationContext})...", Program.AppName); - host.MigrateDbContext((context, services) => - { - var env = services.GetService(); - var settings = services.GetService>(); - var logger = services.GetService>(); + using var scope = app.Services.CreateScope(); + var context = scope.ServiceProvider.GetRequiredService(); + var env = app.Services.GetService(); + var settings = app.Services.GetService>(); + var logger = app.Services.GetService>(); + await context.Database.MigrateAsync(); - new OrderingContextSeed() - .SeedAsync(context, env, settings, logger) - .Wait(); - }) - .MigrateDbContext((_, __) => { }); + await new OrderingContextSeed().SeedAsync(context, env, settings, logger); + var integEventContext = scope.ServiceProvider.GetRequiredService(); + await integEventContext.Database.MigrateAsync(); Log.Information("Starting web host ({ApplicationContext})...", Program.AppName); - host.Run(); + await app.RunAsync(); return 0; } -catch (Exception ex) -{ +catch (Exception ex) { Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", Program.AppName); return 1; } -finally -{ +finally { Log.CloseAndFlush(); } +void ConfigureEventBus(IApplicationBuilder app) { + var eventBus = app.ApplicationServices.GetRequiredService(); -IWebHost BuildWebHost(IConfiguration configuration, string[] args) => - WebHost.CreateDefaultBuilder(args) - .CaptureStartupErrors(false) - .ConfigureKestrel(options => - { - var ports = GetDefinedPorts(configuration); - options.Listen(IPAddress.Any, ports.httpPort, listenOptions => - { - listenOptions.Protocols = HttpProtocols.Http1AndHttp2; - }); - - options.Listen(IPAddress.Any, ports.grpcPort, listenOptions => - { - listenOptions.Protocols = HttpProtocols.Http2; - }); - - }) - .ConfigureAppConfiguration(x => x.AddConfiguration(configuration)) - .UseStartup() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseSerilog() - .Build(); - -Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) -{ + eventBus.Subscribe>(); + eventBus.Subscribe>(); + eventBus.Subscribe>(); + eventBus.Subscribe>(); + eventBus.Subscribe>(); + eventBus.Subscribe>(); +} +Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) { var seqServerUrl = configuration["Serilog:SeqServerUrl"]; var logstashUrl = configuration["Serilog:LogstashgUrl"]; return new LoggerConfiguration() @@ -68,42 +155,257 @@ 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,null) + .WriteTo.Http(string.IsNullOrWhiteSpace(logstashUrl) ? "http://logstash:8080" : logstashUrl, null) .ReadFrom.Configuration(configuration) .CreateLogger(); } -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(); -} -(int httpPort, int grpcPort) GetDefinedPorts(IConfiguration config) -{ +(int httpPort, int grpcPort) GetDefinedPorts(IConfiguration config) { var grpcPort = config.GetValue("GRPC_PORT", 5001); var port = config.GetValue("PORT", 80); return (port, grpcPort); } -public partial class Program -{ +public partial class Program { - public static string Namespace = typeof(Startup).Namespace; + public static string Namespace = typeof(Program).Assembly.GetName().Name; public static string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1); +} +static class CustomExtensionsMethods { + public static IServiceCollection AddApplicationInsights(this IServiceCollection services, IConfiguration configuration) { + services.AddApplicationInsightsTelemetry(configuration); + services.AddApplicationInsightsKubernetesEnricher(); + + return services; + } + + public static IServiceCollection AddCustomMvc(this IServiceCollection services) { + // Add framework services. + services.AddControllers(options => + { + options.Filters.Add(typeof(HttpGlobalExceptionFilter)); + }) + // Added for functional tests + .AddApplicationPart(typeof(OrdersController).Assembly) + .AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true); + + services.AddCors(options => { + options.AddPolicy("CorsPolicy", + builder => builder + .SetIsOriginAllowed((host) => true) + .AllowAnyMethod() + .AllowAnyHeader() + .AllowCredentials()); + }); + + return services; + } + + public static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration) { + var hcBuilder = services.AddHealthChecks(); + + hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy()); + + hcBuilder + .AddSqlServer( + configuration["ConnectionString"], + name: "OrderingDB-check", + tags: new string[] { "orderingdb" }); + + if (configuration.GetValue("AzureServiceBusEnabled")) { + hcBuilder + .AddAzureServiceBusTopic( + configuration["EventBusConnection"], + topicName: "eshop_event_bus", + name: "ordering-servicebus-check", + tags: new string[] { "servicebus" }); + } + else { + hcBuilder + .AddRabbitMQ( + $"amqp://{configuration["EventBusConnection"]}", + name: "ordering-rabbitmqbus-check", + tags: new string[] { "rabbitmqbus" }); + } + + return services; + } + + public static IServiceCollection AddCustomDbContext(this IServiceCollection services, IConfiguration configuration) { + services.AddDbContext(options => + { + options.UseSqlServer(configuration["ConnectionString"], + sqlServerOptionsAction: sqlOptions => { + sqlOptions.MigrationsAssembly(typeof(Program).GetTypeInfo().Assembly.GetName().Name); + sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); + }); + }, + ServiceLifetime.Scoped //Showing explicitly that the DbContext is shared across the HTTP request scope (graph of objects started in the HTTP request) + ); + + services.AddDbContext(options => { + options.UseSqlServer(configuration["ConnectionString"], + sqlServerOptionsAction: sqlOptions => { + sqlOptions.MigrationsAssembly(typeof(Program).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); + }); + }); + + return services; + } + + public static IServiceCollection AddCustomSwagger(this IServiceCollection services, IConfiguration configuration) { + services.AddSwaggerGen(options => { + options.SwaggerDoc("v1", new OpenApiInfo { + Title = "eShopOnContainers - Ordering HTTP API", + Version = "v1", + Description = "The Ordering Service HTTP API" + }); + options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme { + Type = SecuritySchemeType.OAuth2, + Flows = new OpenApiOAuthFlows() { + Implicit = new OpenApiOAuthFlow() { + AuthorizationUrl = new Uri($"{configuration.GetValue("IdentityUrlExternal")}/connect/authorize"), + TokenUrl = new Uri($"{configuration.GetValue("IdentityUrlExternal")}/connect/token"), + Scopes = new Dictionary() + { + { "orders", "Ordering API" } + } + } + } + }); + + options.OperationFilter(); + }); + + return services; + } + + public static IServiceCollection AddCustomIntegrations(this IServiceCollection services, IConfiguration configuration) { + services.AddSingleton(); + services.AddTransient(); + services.AddTransient>( + sp => (DbConnection c) => new IntegrationEventLogService(c)); + + services.AddTransient(); + + if (configuration.GetValue("AzureServiceBusEnabled")) { + services.AddSingleton(sp => { + var serviceBusConnectionString = configuration["EventBusConnection"]; + + var subscriptionClientName = configuration["SubscriptionClientName"]; + + return new DefaultServiceBusPersisterConnection(serviceBusConnectionString); + }); + } + else { + services.AddSingleton(sp => { + var logger = sp.GetRequiredService>(); + + + var factory = new ConnectionFactory() { + HostName = configuration["EventBusConnection"], + DispatchConsumersAsync = true + }; + + if (!string.IsNullOrEmpty(configuration["EventBusUserName"])) { + factory.UserName = configuration["EventBusUserName"]; + } + + if (!string.IsNullOrEmpty(configuration["EventBusPassword"])) { + factory.Password = configuration["EventBusPassword"]; + } + + var retryCount = 5; + if (!string.IsNullOrEmpty(configuration["EventBusRetryCount"])) { + retryCount = int.Parse(configuration["EventBusRetryCount"]); + } + + return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount); + }); + } + + return services; + } + + public static IServiceCollection AddCustomConfiguration(this IServiceCollection services, IConfiguration configuration) { + services.AddOptions(); + services.Configure(configuration); + services.Configure(options => { + options.InvalidModelStateResponseFactory = context => { + var problemDetails = new ValidationProblemDetails(context.ModelState) { + Instance = context.HttpContext.Request.Path, + Status = StatusCodes.Status400BadRequest, + Detail = "Please refer to the errors property for additional details." + }; + + return new BadRequestObjectResult(problemDetails) { + ContentTypes = { "application/problem+json", "application/problem+xml" } + }; + }; + }); + + return services; + } + + public static IServiceCollection AddEventBus(this IServiceCollection services, IConfiguration configuration) { + if (configuration.GetValue("AzureServiceBusEnabled")) { + services.AddSingleton(sp => { + var serviceBusPersisterConnection = sp.GetRequiredService(); + var logger = sp.GetRequiredService>(); + var eventBusSubcriptionsManager = sp.GetRequiredService(); + string subscriptionName = configuration["SubscriptionClientName"]; + + return new EventBusServiceBus(serviceBusPersisterConnection, logger, + eventBusSubcriptionsManager, sp, subscriptionName); + }); + } + else { + services.AddSingleton(sp => { + var subscriptionClientName = configuration["SubscriptionClientName"]; + var rabbitMQPersistentConnection = sp.GetRequiredService(); + var logger = sp.GetRequiredService>(); + var eventBusSubcriptionsManager = sp.GetRequiredService(); + + var retryCount = 5; + if (!string.IsNullOrEmpty(configuration["EventBusRetryCount"])) { + retryCount = int.Parse(configuration["EventBusRetryCount"]); + } + + return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, sp, eventBusSubcriptionsManager, subscriptionClientName, retryCount); + }); + } + + services.AddSingleton(); + + return services; + } + + public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration) { + // prevent from mapping "sub" claim to nameidentifier. + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); + + var identityUrl = configuration.GetValue("IdentityUrl"); + + services.AddAuthentication("Bearer").AddJwtBearer(options => { + options.Authority = identityUrl; + options.RequireHttpsMetadata = false; + options.Audience = "orders"; + options.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", "orders"); + }); + }); + return services; + } } \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Startup.cs b/src/Services/Ordering/Ordering.API/Startup.cs deleted file mode 100644 index 8c8ecb117..000000000 --- a/src/Services/Ordering/Ordering.API/Startup.cs +++ /dev/null @@ -1,396 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Ordering.API; - -public class Startup -{ - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - public virtual IServiceProvider ConfigureServices(IServiceCollection services) - { - services - .AddGrpc(options => - { - options.EnableDetailedErrors = true; - }) - .Services - .AddApplicationInsights(Configuration) - .AddCustomMvc() - .AddHealthChecks(Configuration) - .AddCustomDbContext(Configuration) - .AddCustomSwagger(Configuration) - .AddCustomAuthentication(Configuration) - .AddCustomAuthorization(Configuration) - .AddCustomIntegrations(Configuration) - .AddCustomConfiguration(Configuration) - .AddEventBus(Configuration); - //configure autofac - - var container = new ContainerBuilder(); - container.Populate(services); - - container.RegisterModule(new MediatorModule()); - container.RegisterModule(new ApplicationModule(Configuration["ConnectionString"])); - - return new AutofacServiceProvider(container.Build()); - } - - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) - { - //loggerFactory.AddAzureWebAppDiagnostics(); - //loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); - - var pathBase = Configuration["PATH_BASE"]; - if (!string.IsNullOrEmpty(pathBase)) - { - loggerFactory.CreateLogger().LogDebug("Using PATH BASE '{pathBase}'", pathBase); - app.UsePathBase(pathBase); - } - - app.UseSwagger() - .UseSwaggerUI(c => - { - c.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Ordering.API V1"); - c.OAuthClientId("orderingswaggerui"); - c.OAuthAppName("Ordering Swagger UI"); - }); - - app.UseRouting(); - app.UseCors("CorsPolicy"); - ConfigureAuth(app); - - app.UseEndpoints(endpoints => - { - endpoints.MapGrpcService(); - endpoints.MapDefaultControllerRoute(); - endpoints.MapControllers(); - endpoints.MapGet("/_proto/", async ctx => - { - ctx.Response.ContentType = "text/plain"; - using var fs = new FileStream(Path.Combine(env.ContentRootPath, "Proto", "basket.proto"), FileMode.Open, FileAccess.Read); - using var sr = new StreamReader(fs); - while (!sr.EndOfStream) - { - var line = await sr.ReadLineAsync(); - if (line != "/* >>" || line != "<< */") - { - await ctx.Response.WriteAsync(line); - } - } - }); - endpoints.MapHealthChecks("/hc", new HealthCheckOptions() - { - Predicate = _ => true, - ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse - }); - endpoints.MapHealthChecks("/liveness", new HealthCheckOptions - { - Predicate = r => r.Name.Contains("self") - }); - }); - - ConfigureEventBus(app); - } - - - private void ConfigureEventBus(IApplicationBuilder app) - { - var eventBus = app.ApplicationServices.GetRequiredService(); - - eventBus.Subscribe>(); - eventBus.Subscribe>(); - eventBus.Subscribe>(); - eventBus.Subscribe>(); - eventBus.Subscribe>(); - eventBus.Subscribe>(); - } - - protected virtual void ConfigureAuth(IApplicationBuilder app) - { - app.UseAuthentication(); - app.UseAuthorization(); - } -} - -static class CustomExtensionsMethods -{ - public static IServiceCollection AddApplicationInsights(this IServiceCollection services, IConfiguration configuration) - { - services.AddApplicationInsightsTelemetry(configuration); - services.AddApplicationInsightsKubernetesEnricher(); - - return services; - } - - public static IServiceCollection AddCustomMvc(this IServiceCollection services) - { - // Add framework services. - services.AddControllers(options => - { - options.Filters.Add(typeof(HttpGlobalExceptionFilter)); - }) - // Added for functional tests - .AddApplicationPart(typeof(OrdersController).Assembly) - .AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true); - - services.AddCors(options => - { - options.AddPolicy("CorsPolicy", - builder => builder - .SetIsOriginAllowed((host) => true) - .AllowAnyMethod() - .AllowAnyHeader() - .AllowCredentials()); - }); - - return services; - } - - public static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration) - { - var hcBuilder = services.AddHealthChecks(); - - hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy()); - - hcBuilder - .AddSqlServer( - configuration["ConnectionString"], - name: "OrderingDB-check", - tags: new string[] { "orderingdb" }); - - if (configuration.GetValue("AzureServiceBusEnabled")) - { - hcBuilder - .AddAzureServiceBusTopic( - configuration["EventBusConnection"], - topicName: "eshop_event_bus", - name: "ordering-servicebus-check", - tags: new string[] { "servicebus" }); - } - else - { - hcBuilder - .AddRabbitMQ( - $"amqp://{configuration["EventBusConnection"]}", - name: "ordering-rabbitmqbus-check", - tags: new string[] { "rabbitmqbus" }); - } - - return services; - } - - public static IServiceCollection AddCustomDbContext(this IServiceCollection services, IConfiguration configuration) - { - services.AddDbContext(options => - { - options.UseSqlServer(configuration["ConnectionString"], - sqlServerOptionsAction: sqlOptions => - { - sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name); - sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); - }); - }, - ServiceLifetime.Scoped //Showing explicitly that the DbContext is shared across the HTTP request scope (graph of objects started in the HTTP request) - ); - - 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); - }); - }); - - return services; - } - - public static IServiceCollection AddCustomSwagger(this IServiceCollection services, IConfiguration configuration) - { - services.AddSwaggerGen(options => - { - options.SwaggerDoc("v1", new OpenApiInfo - { - Title = "eShopOnContainers - Ordering HTTP API", - Version = "v1", - Description = "The Ordering Service HTTP API" - }); - options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme - { - Type = SecuritySchemeType.OAuth2, - Flows = new OpenApiOAuthFlows() - { - Implicit = new OpenApiOAuthFlow() - { - AuthorizationUrl = new Uri($"{configuration.GetValue("IdentityUrlExternal")}/connect/authorize"), - TokenUrl = new Uri($"{configuration.GetValue("IdentityUrlExternal")}/connect/token"), - Scopes = new Dictionary() - { - { "orders", "Ordering API" } - } - } - } - }); - - options.OperationFilter(); - }); - - return services; - } - - public static IServiceCollection AddCustomIntegrations(this IServiceCollection services, IConfiguration configuration) - { - services.AddSingleton(); - services.AddTransient(); - services.AddTransient>( - sp => (DbConnection c) => new IntegrationEventLogService(c)); - - services.AddTransient(); - - if (configuration.GetValue("AzureServiceBusEnabled")) - { - services.AddSingleton(sp => - { - var serviceBusConnectionString = configuration["EventBusConnection"]; - - var subscriptionClientName = configuration["SubscriptionClientName"]; - - return new DefaultServiceBusPersisterConnection(serviceBusConnectionString); - }); - } - else - { - services.AddSingleton(sp => - { - var logger = sp.GetRequiredService>(); - - - var factory = new ConnectionFactory() - { - HostName = configuration["EventBusConnection"], - DispatchConsumersAsync = true - }; - - if (!string.IsNullOrEmpty(configuration["EventBusUserName"])) - { - factory.UserName = configuration["EventBusUserName"]; - } - - if (!string.IsNullOrEmpty(configuration["EventBusPassword"])) - { - factory.Password = configuration["EventBusPassword"]; - } - - var retryCount = 5; - if (!string.IsNullOrEmpty(configuration["EventBusRetryCount"])) - { - retryCount = int.Parse(configuration["EventBusRetryCount"]); - } - - return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount); - }); - } - - return services; - } - - public static IServiceCollection AddCustomConfiguration(this IServiceCollection services, IConfiguration configuration) - { - services.AddOptions(); - services.Configure(configuration); - services.Configure(options => - { - options.InvalidModelStateResponseFactory = context => - { - var problemDetails = new ValidationProblemDetails(context.ModelState) - { - Instance = context.HttpContext.Request.Path, - Status = StatusCodes.Status400BadRequest, - Detail = "Please refer to the errors property for additional details." - }; - - return new BadRequestObjectResult(problemDetails) - { - ContentTypes = { "application/problem+json", "application/problem+xml" } - }; - }; - }); - - return services; - } - - public static IServiceCollection AddEventBus(this IServiceCollection services, IConfiguration configuration) - { - if (configuration.GetValue("AzureServiceBusEnabled")) - { - services.AddSingleton(sp => - { - var serviceBusPersisterConnection = sp.GetRequiredService(); - var logger = sp.GetRequiredService>(); - var eventBusSubcriptionsManager = sp.GetRequiredService(); - string subscriptionName = configuration["SubscriptionClientName"]; - - return new EventBusServiceBus(serviceBusPersisterConnection, logger, - eventBusSubcriptionsManager, sp, subscriptionName); - }); - } - else - { - services.AddSingleton(sp => - { - var subscriptionClientName = configuration["SubscriptionClientName"]; - var rabbitMQPersistentConnection = sp.GetRequiredService(); - var logger = sp.GetRequiredService>(); - var eventBusSubcriptionsManager = sp.GetRequiredService(); - - var retryCount = 5; - if (!string.IsNullOrEmpty(configuration["EventBusRetryCount"])) - { - retryCount = int.Parse(configuration["EventBusRetryCount"]); - } - - return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, sp, eventBusSubcriptionsManager, subscriptionClientName, retryCount); - }); - } - - services.AddSingleton(); - - return services; - } - - public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration) - { - // prevent from mapping "sub" claim to nameidentifier. - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); - - var identityUrl = configuration.GetValue("IdentityUrl"); - - services.AddAuthentication("Bearer").AddJwtBearer(options => - { - options.Authority = identityUrl; - options.RequireHttpsMetadata = false; - options.Audience = "orders"; - options.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", "orders"); - }); - }); - return services; - } -} \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.FunctionalTests/OrderingScenarioBase.cs b/src/Services/Ordering/Ordering.FunctionalTests/OrderingScenarioBase.cs index d03d54380..a0b31568b 100644 --- a/src/Services/Ordering/Ordering.FunctionalTests/OrderingScenarioBase.cs +++ b/src/Services/Ordering/Ordering.FunctionalTests/OrderingScenarioBase.cs @@ -13,7 +13,7 @@ public class OrderingScenarioBase { cb.AddJsonFile("appsettings.json", optional: false) .AddEnvironmentVariables(); - }).UseStartup(); + }); var testServer = new TestServer(hostBuilder); diff --git a/src/Services/Ordering/Ordering.FunctionalTests/OrderingTestStartup.cs b/src/Services/Ordering/Ordering.FunctionalTests/OrderingTestStartup.cs deleted file mode 100644 index 7367042de..000000000 --- a/src/Services/Ordering/Ordering.FunctionalTests/OrderingTestStartup.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Ordering.FunctionalTests; - -public class OrderingTestsStartup : Startup -{ - public OrderingTestsStartup(IConfiguration env) : base(env) - { - } - - public override IServiceProvider ConfigureServices(IServiceCollection services) - { - // Added to avoid the Authorize data annotation in test environment. - // Property "SuppressCheckForUnhandledSecurityMetadata" in appsettings.json - services.Configure(Configuration); - return base.ConfigureServices(services); - } - protected override void ConfigureAuth(IApplicationBuilder app) - { - if (Configuration["isTest"] == bool.TrueString.ToLowerInvariant()) - { - app.UseMiddleware(); - } - else - { - base.ConfigureAuth(app); - } - } -} diff --git a/src/Tests/Services/Application.FunctionalTests/Services/Basket/BasketScenariosBase.cs b/src/Tests/Services/Application.FunctionalTests/Services/Basket/BasketScenariosBase.cs index 71487d7b4..0cb668b8a 100644 --- a/src/Tests/Services/Application.FunctionalTests/Services/Basket/BasketScenariosBase.cs +++ b/src/Tests/Services/Application.FunctionalTests/Services/Basket/BasketScenariosBase.cs @@ -16,7 +16,7 @@ public class BasketScenariosBase { cb.AddJsonFile("Services/Basket/appsettings.json", optional: false) .AddEnvironmentVariables(); - }).UseStartup(); + }); return new TestServer(hostBuilder); } diff --git a/src/Tests/Services/Application.FunctionalTests/Services/Basket/BasketTestsStartup.cs b/src/Tests/Services/Application.FunctionalTests/Services/Basket/BasketTestsStartup.cs deleted file mode 100644 index 23523a9f4..000000000 --- a/src/Tests/Services/Application.FunctionalTests/Services/Basket/BasketTestsStartup.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace FunctionalTests.Services.Basket; -using Microsoft.eShopOnContainers.Services.Basket.API; -class BasketTestsStartup : Startup -{ - public BasketTestsStartup(IConfiguration env) : base(env) - { - } - - protected override void ConfigureAuth(IApplicationBuilder app) - { - if (Configuration["isTest"] == bool.TrueString.ToLowerInvariant()) - { - app.UseMiddleware(); - app.UseAuthorization(); - } - else - { - base.ConfigureAuth(app); - } - } -} diff --git a/src/Tests/Services/Application.FunctionalTests/Services/Catalog/CatalogScenariosBase.cs b/src/Tests/Services/Application.FunctionalTests/Services/Catalog/CatalogScenariosBase.cs index ec762a947..0fcf32e3c 100644 --- a/src/Tests/Services/Application.FunctionalTests/Services/Catalog/CatalogScenariosBase.cs +++ b/src/Tests/Services/Application.FunctionalTests/Services/Catalog/CatalogScenariosBase.cs @@ -14,7 +14,7 @@ public class CatalogScenariosBase { cb.AddJsonFile("Services/Catalog/appsettings.json", optional: false) .AddEnvironmentVariables(); - }).UseStartup(); + }); var testServer = new TestServer(hostBuilder); diff --git a/src/Tests/Services/Application.FunctionalTests/Services/Ordering/OrderingScenariosBase.cs b/src/Tests/Services/Application.FunctionalTests/Services/Ordering/OrderingScenariosBase.cs index fce6bca18..3d419eb83 100644 --- a/src/Tests/Services/Application.FunctionalTests/Services/Ordering/OrderingScenariosBase.cs +++ b/src/Tests/Services/Application.FunctionalTests/Services/Ordering/OrderingScenariosBase.cs @@ -13,7 +13,7 @@ public class OrderingScenariosBase { cb.AddJsonFile("Services/Ordering/appsettings.json", optional: false) .AddEnvironmentVariables(); - }).UseStartup(); + }); var testServer = new TestServer(hostBuilder); diff --git a/src/Tests/Services/Application.FunctionalTests/Services/Ordering/OrderingTestsStartup.cs b/src/Tests/Services/Application.FunctionalTests/Services/Ordering/OrderingTestsStartup.cs deleted file mode 100644 index f6f3d8e51..000000000 --- a/src/Tests/Services/Application.FunctionalTests/Services/Ordering/OrderingTestsStartup.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace FunctionalTests.Services.Ordering; -using Microsoft.eShopOnContainers.Services.Ordering.API; - -public class OrderingTestsStartup : Startup -{ - public OrderingTestsStartup(IConfiguration env) : base(env) - { - } - - protected override void ConfigureAuth(IApplicationBuilder app) - { - if (Configuration["isTest"] == bool.TrueString.ToLowerInvariant()) - { - app.UseMiddleware(); - app.UseAuthorization(); - } - else - { - base.ConfigureAuth(app); - } - } -}