diff --git a/.gitignore b/.gitignore index 8b7a2cbb9..2810d6b1c 100644 --- a/.gitignore +++ b/.gitignore @@ -282,3 +282,4 @@ src/**/app.yaml src/**/inf.yaml .angular/ +/src/Services/Identity/Identity.API/keys/*.json diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile index 4143cdb2c..9a6b83be8 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile @@ -32,6 +32,7 @@ COPY "Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj" COPY "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" COPY "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" COPY "Services/Payment/Payment.API/Payment.API.csproj" "Services/Payment/Payment.API/Payment.API.csproj" +COPY "Services/Services.Common/Services.Common.csproj" "Services/Services.Common/Services.Common.csproj" COPY "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" COPY "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" COPY "Web/WebhookClient/WebhookClient.csproj" "Web/WebhookClient/WebhookClient.csproj" diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Extensions/Extensions.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Extensions/Extensions.cs new file mode 100644 index 000000000..af98ca5c5 --- /dev/null +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Extensions/Extensions.cs @@ -0,0 +1,62 @@ +internal static class Extensions +{ + public static IServiceCollection AddReverseProxy(this IServiceCollection services, IConfiguration configuration) + { + services.AddReverseProxy().LoadFromConfig(configuration.GetRequiredSection("ReverseProxy")); + return services; + } + + public static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration) + { + services.AddHealthChecks() + .AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("CatalogUrlHC")), name: "catalogapi-check", tags: new string[] { "catalogapi" }) + .AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("OrderingUrlHC")), name: "orderingapi-check", tags: new string[] { "orderingapi" }) + .AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("BasketUrlHC")), name: "basketapi-check", tags: new string[] { "basketapi" }) + .AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("IdentityUrlHC")), name: "identityapi-check", tags: new string[] { "identityapi" }); + + return services; + } + + public static IServiceCollection AddApplicationServices(this IServiceCollection services) + { + // Register delegating handlers + services.AddTransient(); + + // Register http services + services.AddHttpClient() + .AddHttpMessageHandler(); + + return services; + } + + public static IServiceCollection AddGrpcServices(this IServiceCollection services) + { + services.AddTransient(); + + services.AddScoped(); + + services.AddGrpcClient((services, options) => + { + var basketApi = services.GetRequiredService>().Value.GrpcBasket; + options.Address = new Uri(basketApi); + }).AddInterceptor(); + + services.AddScoped(); + + services.AddGrpcClient((services, options) => + { + var catalogApi = services.GetRequiredService>().Value.GrpcCatalog; + options.Address = new Uri(catalogApi); + }).AddInterceptor(); + + services.AddScoped(); + + services.AddGrpcClient((services, options) => + { + var orderingApi = services.GetRequiredService>().Value.GrpcOrdering; + options.Address = new Uri(orderingApi); + }).AddInterceptor(); + + return services; + } +} diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Filters/AuthorizeCheckOperationFilter.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Filters/AuthorizeCheckOperationFilter.cs deleted file mode 100644 index 11473d1c1..000000000 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Filters/AuthorizeCheckOperationFilter.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Filters -{ - namespace Basket.API.Infrastructure.Filters - { - public class AuthorizeCheckOperationFilter : IOperationFilter - { - public void Apply(OpenApiOperation operation, OperationFilterContext context) - { - // Check for authorize attribute - var hasAuthorize = context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType().Any() || - context.MethodInfo.GetCustomAttributes(true).OfType().Any(); - - if (!hasAuthorize) return; - - operation.Responses.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" }); - operation.Responses.TryAdd("403", new OpenApiResponse { Description = "Forbidden" }); - - var oAuthScheme = new OpenApiSecurityScheme - { - Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" } - }; - - operation.Security = new List - { - new() - { - [ oAuthScheme ] = new [] { "Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator" } - } - }; - } - } - } -} diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/GlobalUsings.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/GlobalUsings.cs index 37ac7ade9..549873d8d 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/GlobalUsings.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/GlobalUsings.cs @@ -2,32 +2,24 @@ global using Grpc.Core.Interceptors; global using Grpc.Core; global using GrpcBasket; -global using GrpcOrdering; global using HealthChecks.UI.Client; global using Microsoft.AspNetCore.Authentication.JwtBearer; global using Microsoft.AspNetCore.Authentication; global using Microsoft.AspNetCore.Authorization; global using Microsoft.AspNetCore.Builder; global using Microsoft.AspNetCore.Diagnostics.HealthChecks; -global using Microsoft.AspNetCore.Hosting; global using Microsoft.AspNetCore.Http; global using Microsoft.AspNetCore.Mvc; -global using Microsoft.AspNetCore; global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config; -global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Filters.Basket.API.Infrastructure.Filters; global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Infrastructure; global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services; -global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.Diagnostics.HealthChecks; -global using Microsoft.Extensions.Hosting; global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.Options; global using Microsoft.OpenApi.Models; -global using Serilog; -global using Swashbuckle.AspNetCore.SwaggerGen; global using System.Collections.Generic; global using System.IdentityModel.Tokens.Jwt; global using System.Linq; @@ -38,4 +30,5 @@ 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 +global using Microsoft.IdentityModel.Tokens; +global using Services.Common; diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Infrastructure/GrpcExceptionInterceptor.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Infrastructure/GrpcExceptionInterceptor.cs index c434074d3..ec159a0ea 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Infrastructure/GrpcExceptionInterceptor.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Infrastructure/GrpcExceptionInterceptor.cs @@ -28,7 +28,7 @@ public class GrpcExceptionInterceptor : Interceptor } catch (RpcException e) { - _logger.LogError("Error calling via grpc: {Status} - {Message}", e.Status, e.Message); + _logger.LogError(e, "Error calling via gRPC: {Status}", e.Status); return default; } } diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Infrastructure/HttpClientAuthorizationDelegatingHandler.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Infrastructure/HttpClientAuthorizationDelegatingHandler.cs deleted file mode 100644 index 24914ca33..000000000 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Infrastructure/HttpClientAuthorizationDelegatingHandler.cs +++ /dev/null @@ -1,44 +0,0 @@ -namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Infrastructure; - -public class HttpClientAuthorizationDelegatingHandler : DelegatingHandler -{ - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly ILogger _logger; - - public HttpClientAuthorizationDelegatingHandler(IHttpContextAccessor httpContextAccessor, ILogger logger) - { - _httpContextAccessor = httpContextAccessor; - _logger = logger; - } - - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - request.Version = new System.Version(2, 0); - request.Method = HttpMethod.Get; - - var authorizationHeader = _httpContextAccessor.HttpContext - .Request.Headers["Authorization"]; - - if (!string.IsNullOrEmpty(authorizationHeader)) - { - request.Headers.Add("Authorization", new List() { authorizationHeader }); - } - - var token = await GetToken(); - - if (token != null) - { - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - } - - return await base.SendAsync(request, cancellationToken); - } - - async Task GetToken() - { - const string ACCESS_TOKEN = "access_token"; - - return await _httpContextAccessor.HttpContext - .GetTokenAsync(ACCESS_TOKEN); - } -} 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 78b2d6780..35f37cb53 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj @@ -10,10 +10,14 @@ - + + + + + @@ -25,11 +29,13 @@ - - + + + + diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Models/UpdateBasketItemsRequest.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Models/UpdateBasketItemsRequest.cs index d0686ef51..f7e807d8d 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Models/UpdateBasketItemsRequest.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Models/UpdateBasketItemsRequest.cs @@ -2,7 +2,6 @@ public class UpdateBasketItemsRequest { - public string BasketId { get; set; } public ICollection Updates { get; set; } diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Program.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Program.cs index 92e7e3a37..91c5bca6b 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Program.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Program.cs @@ -1,23 +1,24 @@ -await BuildWebHost(args).RunAsync(); -IWebHost BuildWebHost(string[] args) => - WebHost - .CreateDefaultBuilder(args) - .ConfigureAppConfiguration(cb => - { - var sources = cb.Sources; - sources.Insert(3, new Microsoft.Extensions.Configuration.Json.JsonConfigurationSource() - { - Optional = true, - Path = "appsettings.localhost.json", - ReloadOnChange = false - }); - }) - .UseStartup() - .UseSerilog((builderContext, config) => - { - config - .MinimumLevel.Information() - .Enrich.FromLogContext() - .WriteTo.Console(); - }) - .Build(); \ No newline at end of file +var builder = WebApplication.CreateBuilder(args); + +builder.AddServiceDefaults(); + +builder.Services.AddReverseProxy(builder.Configuration); +builder.Services.AddControllers(); + +builder.Services.AddHealthChecks(builder.Configuration); + +builder.Services.AddApplicationServices(); +builder.Services.AddGrpcServices(); + +builder.Services.Configure(builder.Configuration.GetSection("urls")); + +var app = builder.Build(); + +app.UseServiceDefaults(); + +app.UseHttpsRedirection(); + +app.MapControllers(); +app.MapReverseProxy(); + +await app.RunAsync(); diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/IBasketService.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/IBasketService.cs index e1c9a24bf..bf9c1b7e9 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/IBasketService.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/IBasketService.cs @@ -5,5 +5,4 @@ public interface IBasketService Task GetByIdAsync(string id); Task UpdateAsync(BasketData currentBasket); - } diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/OrderingService.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/OrderingService.cs index 2a7de50a7..6d46a706f 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/OrderingService.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/OrderingService.cs @@ -2,10 +2,10 @@ public class OrderingService : IOrderingService { - private readonly OrderingGrpc.OrderingGrpcClient _orderingGrpcClient; + private readonly GrpcOrdering.OrderingGrpc.OrderingGrpcClient _orderingGrpcClient; private readonly ILogger _logger; - public OrderingService(OrderingGrpc.OrderingGrpcClient orderingGrpcClient, ILogger logger) + public OrderingService(GrpcOrdering.OrderingGrpc.OrderingGrpcClient orderingGrpcClient, ILogger logger) { _orderingGrpcClient = orderingGrpcClient; _logger = logger; @@ -48,14 +48,14 @@ public class OrderingService : IOrderingService return data; } - private CreateOrderDraftCommand MapToOrderDraftCommand(BasketData basketData) + private GrpcOrdering.CreateOrderDraftCommand MapToOrderDraftCommand(BasketData basketData) { - var command = new CreateOrderDraftCommand + var command = new GrpcOrdering.CreateOrderDraftCommand { BuyerId = basketData.BuyerId, }; - basketData.Items.ForEach(i => command.Items.Add(new BasketItem + basketData.Items.ForEach(i => command.Items.Add(new GrpcOrdering.BasketItem { Id = i.Id, OldUnitPrice = (double)i.OldUnitPrice, diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Startup.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Startup.cs deleted file mode 100644 index a7b5335c5..000000000 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Startup.cs +++ /dev/null @@ -1,210 +0,0 @@ -namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator; - -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 void ConfigureServices(IServiceCollection services) - { - services.AddHealthChecks() - .AddCheck("self", () => HealthCheckResult.Healthy()) - .AddUrlGroup(new Uri(Configuration["CatalogUrlHC"]), name: "catalogapi-check", tags: new string[] { "catalogapi" }) - .AddUrlGroup(new Uri(Configuration["OrderingUrlHC"]), name: "orderingapi-check", tags: new string[] { "orderingapi" }) - .AddUrlGroup(new Uri(Configuration["BasketUrlHC"]), name: "basketapi-check", tags: new string[] { "basketapi" }) - .AddUrlGroup(new Uri(Configuration["IdentityUrlHC"]), name: "identityapi-check", tags: new string[] { "identityapi" }) - .AddUrlGroup(new Uri(Configuration["PaymentUrlHC"]), name: "paymentapi-check", tags: new string[] { "paymentapi" }); - - services.AddCustomMvc(Configuration) - .AddCustomAuthentication(Configuration) - .AddHttpServices() - .AddGrpcServices(); - } - - // 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) - { - var pathBase = Configuration["PATH_BASE"]; - - if (!string.IsNullOrEmpty(pathBase)) - { - loggerFactory.CreateLogger().LogDebug("Using PATH BASE '{pathBase}'", pathBase); - app.UsePathBase(pathBase); - } - - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseSwagger().UseSwaggerUI(c => - { - c.SwaggerEndpoint($"{(!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty)}/swagger/v1/swagger.json", "Purchase BFF V1"); - - c.OAuthClientId("mobileshoppingaggswaggerui"); - c.OAuthClientSecret(string.Empty); - c.OAuthRealm(string.Empty); - c.OAuthAppName("Purchase BFF Swagger UI"); - }); - - app.UseRouting(); - app.UseCors("CorsPolicy"); - app.UseAuthentication(); - app.UseAuthorization(); - 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") - }); - }); - } -} - -public static class ServiceCollectionExtensions -{ - public static IServiceCollection AddCustomMvc(this IServiceCollection services, IConfiguration configuration) - { - services.AddOptions(); - services.Configure(configuration.GetSection("urls")); - - services.AddControllers() - .AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true); - - services.AddSwaggerGen(options => - { - //options.DescribeAllEnumsAsStrings(); - options.SwaggerDoc("v1", new OpenApiInfo - { - Title = "Shopping Aggregator for Mobile Clients", - Version = "v1", - Description = "Shopping Aggregator for Mobile Clients" - }); - 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() - { - { "mobileshoppingagg", "Shopping Aggregator for Mobile Clients" } - } - } - } - }); - - options.OperationFilter(); - }); - - services.AddCors(options => - { - options.AddPolicy("CorsPolicy", - builder => builder - .AllowAnyMethod() - .AllowAnyHeader() - .SetIsOriginAllowed((host) => true) - .AllowCredentials()); - }); - - return services; - } - public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration) - { - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); - - var identityUrl = configuration.GetValue("urls:identity"); - - services.AddAuthentication(options => - { - options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; - - }) - .AddJwtBearer(options => - { - 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) - { - //register delegating handlers - services.AddTransient(); - services.AddSingleton(); - - //register http services - - services.AddHttpClient(); - - return services; - } - - public static IServiceCollection AddGrpcServices(this IServiceCollection services) - { - services.AddTransient(); - - services.AddScoped(); - - services.AddGrpcClient((services, options) => - { - var basketApi = services.GetRequiredService>().Value.GrpcBasket; - options.Address = new Uri(basketApi); - }).AddInterceptor(); - - services.AddScoped(); - - services.AddGrpcClient((services, options) => - { - var catalogApi = services.GetRequiredService>().Value.GrpcCatalog; - options.Address = new Uri(catalogApi); - }).AddInterceptor(); - - services.AddScoped(); - - services.AddGrpcClient((services, options) => - { - var orderingApi = services.GetRequiredService>().Value.GrpcOrdering; - options.Address = new Uri(orderingApi); - }).AddInterceptor(); - - return services; - } - -} diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/appsettings.json b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/appsettings.json index 26bb0ac7a..8526211d9 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/appsettings.json +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/appsettings.json @@ -1,15 +1,138 @@ { "Logging": { - "IncludeScopes": false, - "Debug": { - "LogLevel": { - "Default": "Warning" + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "System.Net.Http": "Warning" + } + }, + "OpenApi": { + "Endpoint": { + "Name": "Purchase BFF V1" + }, + "Document": { + "Description": "Shopping Aggregator for Mobile Clients", + "Title": "Shopping Aggregator for Mobile Clients", + "Version": "v1" + }, + "Auth": { + "ClientId": "mobileshoppingaggswaggerui", + "AppName": "Mobile shopping BFF Swagger UI" + } + }, + "Identity": { + "Url": "http://localhost:5223", + "Audience": "mobileshoppingagg", + "Scopes": { + "webshoppingagg": "Shopping Aggregator for Mobile Clients" + } + }, + "ReverseProxy": { + "Routes": { + "c-short": { + "ClusterId": "catalog", + "Match": { + "Path": "c/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/c" } + ] + }, + "c-long": { + "ClusterId": "catalog", + "Match": { + "Path": "catalog-api/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/catalog-api" } + ] + }, + "b-short": { + "ClusterId": "basket", + "Match": { + "Path": "b/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/b" } + ] + }, + "b-long": { + "ClusterId": "basket", + "Match": { + "Path": "basket-api/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/basket-api" } + ] + }, + "o-short": { + "ClusterId": "orders", + "Match": { + "Path": "o/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/o" } + ] + }, + "o-long": { + "ClusterId": "orders", + "Match": { + "Path": "ordering-api/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/ordering-api" } + ] + }, + "h-long": { + "ClusterId": "signalr", + "Match": { + "Path": "hub/notificationhub/{**catch-all}" + } } }, - "Console": { - "LogLevel": { - "Default": "Warning" + "Clusters": { + "basket": { + "Destinations": { + "destination0": { + "Address": "http://localhost:5221" + } + } + }, + "catalog": { + "Destinations": { + "destination0": { + "Address": "http://localhost:5222" + } + } + }, + "orders": { + "Destinations": { + "destination0": { + "Address": "http://localhost:5224" + } + } + }, + "signalr": { + "Destinations": { + "destination0": { + "Address": "http://localhost:5225" + } + } } } - } + }, + "Urls": { + "Basket": "http://localhost:5221", + "Catalog": "http://localhost:5222", + "Orders": "http://localhost:5224", + "Identity": "http://localhost:5223", + "Signalr": "http://localhost:5225", + "GrpcBasket": "http://localhost:6221", + "GrpcCatalog": "http://localhost:6222", + "GrpcOrdering": "http://localhost:6224" + }, + "CatalogUrlHC": "http://localhost:5222/hc", + "OrderingUrlHC": "http://localhost:5224/hc", + "BasketUrlHC": "http://localhost:5221/hc", + "IdentityUrlHC": "http://localhost:5223/hc" } diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/appsettings.localhost.json b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/appsettings.localhost.json index 86fd1541d..ce1d6cfbd 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/appsettings.localhost.json +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/appsettings.localhost.json @@ -8,16 +8,19 @@ "grpcCatalog": "http://localhost:81", "grpcOrdering": "http://localhost:5581" }, - "IdentityUrlExternal": "http://localhost:5105", - "IdentityUrl": "http://localhost:5105", + "Identity": { + "ExternalUrl": "http://localhost:5105", + "Url": "http://localhost:5105", + }, "Logging": { - "IncludeScopes": false, "Debug": { + "IncludeScopes": false, "LogLevel": { "Default": "Debug" } }, "Console": { + "IncludeScopes": false, "LogLevel": { "Default": "Debug" } diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Controllers/HomeController.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Controllers/HomeController.cs deleted file mode 100644 index 55df5880b..000000000 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Controllers/HomeController.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Controllers; - -[Route("")] -public class HomeController : Controller -{ - [HttpGet] - public IActionResult Index() - { - return new RedirectResult("~/swagger"); - } -} diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile b/src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile index d28a12d47..e8bd952a2 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile @@ -32,6 +32,7 @@ COPY "Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj" COPY "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" COPY "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" COPY "Services/Payment/Payment.API/Payment.API.csproj" "Services/Payment/Payment.API/Payment.API.csproj" +COPY "Services/Services.Common/Services.Common.csproj" "Services/Services.Common/Services.Common.csproj" COPY "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" COPY "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" COPY "Web/WebhookClient/WebhookClient.csproj" "Web/WebhookClient/WebhookClient.csproj" diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Extensions/Extensions.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Extensions/Extensions.cs new file mode 100644 index 000000000..fefb4f351 --- /dev/null +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Extensions/Extensions.cs @@ -0,0 +1,63 @@ +internal static class Extensions +{ + public static IServiceCollection AddReverseProxy(this IServiceCollection services, IConfiguration configuration) + { + services.AddReverseProxy().LoadFromConfig(configuration.GetRequiredSection("ReverseProxy")); + + return services; + } + + public static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration) + { + services.AddHealthChecks() + .AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("CatalogUrlHC")), name: "catalogapi-check", tags: new string[] { "catalogapi" }) + .AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("OrderingUrlHC")), name: "orderingapi-check", tags: new string[] { "orderingapi" }) + .AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("BasketUrlHC")), name: "basketapi-check", tags: new string[] { "basketapi" }) + .AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("IdentityUrlHC")), name: "identityapi-check", tags: new string[] { "identityapi" }); + + return services; + } + + public static IServiceCollection AddApplicationServices(this IServiceCollection services) + { + // Register delegating handlers + services.AddTransient(); + + // Register http services + services.AddHttpClient() + .AddHttpMessageHandler(); + + return services; + } + + public static IServiceCollection AddGrpcServices(this IServiceCollection services) + { + services.AddTransient(); + + services.AddScoped(); + + services.AddGrpcClient((services, options) => + { + var basketApi = services.GetRequiredService>().Value.GrpcBasket; + options.Address = new Uri(basketApi); + }).AddInterceptor(); + + services.AddScoped(); + + services.AddGrpcClient((services, options) => + { + var catalogApi = services.GetRequiredService>().Value.GrpcCatalog; + options.Address = new Uri(catalogApi); + }).AddInterceptor(); + + services.AddScoped(); + + services.AddGrpcClient((services, options) => + { + var orderingApi = services.GetRequiredService>().Value.GrpcOrdering; + options.Address = new Uri(orderingApi); + }).AddInterceptor(); + + return services; + } +} diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Filters/AuthorizeCheckOperationFilter.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Filters/AuthorizeCheckOperationFilter.cs deleted file mode 100644 index 99bf07048..000000000 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Filters/AuthorizeCheckOperationFilter.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Filters -{ - namespace Basket.API.Infrastructure.Filters - { - public class AuthorizeCheckOperationFilter : IOperationFilter - { - public void Apply(OpenApiOperation operation, OperationFilterContext context) - { - // Check for authorize attribute - var hasAuthorize = context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType().Any() || - context.MethodInfo.GetCustomAttributes(true).OfType().Any(); - - if (!hasAuthorize) return; - - operation.Responses.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" }); - operation.Responses.TryAdd("403", new OpenApiResponse { Description = "Forbidden" }); - - var oAuthScheme = new OpenApiSecurityScheme - { - Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" } - }; - - operation.Security = new List - { - new() - { - [ oAuthScheme ] = new[] { "Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator" } - } - }; - } - } - } - -} \ No newline at end of file diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/GlobalUsings.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/GlobalUsings.cs index 75525a2a5..97b9e1222 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/GlobalUsings.cs +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/GlobalUsings.cs @@ -1,41 +1,27 @@ -global using CatalogApi; -global using Grpc.Core.Interceptors; +global using System; +global using System.Collections.Generic; +global using System.Linq; +global using System.Net; +global using System.Net.Http; +global using System.Net.Http.Headers; +global using System.Text.Json; +global using System.Threading; +global using System.Threading.Tasks; +global using CatalogApi; global using Grpc.Core; +global using Grpc.Core.Interceptors; global using GrpcBasket; -global using GrpcOrdering; -global using HealthChecks.UI.Client; global using Microsoft.AspNetCore.Authentication; global using Microsoft.AspNetCore.Authorization; global using Microsoft.AspNetCore.Builder; -global using Microsoft.AspNetCore.Diagnostics.HealthChecks; -global using Microsoft.AspNetCore.Hosting; global using Microsoft.AspNetCore.Http; global using Microsoft.AspNetCore.Mvc; -global using Microsoft.AspNetCore; global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Config; -global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Filters.Basket.API.Infrastructure.Filters; global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Infrastructure; global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models; global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services; -global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.Diagnostics.HealthChecks; -global using Microsoft.Extensions.Hosting; global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.Options; -global using Microsoft.OpenApi.Models; -global using Serilog; -global using Swashbuckle.AspNetCore.SwaggerGen; -global using System.Collections.Generic; -global using System.IdentityModel.Tokens.Jwt; -global using System.Linq; -global using System.Net.Http.Headers; -global using System.Net.Http; -global using System.Net; -global using System.Text.Json; -global using System.Threading.Tasks; -global using System.Threading; -global using System; -global using Microsoft.IdentityModel.Tokens; -global using Serilog.Context; \ No newline at end of file +global using Services.Common; diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Infrastructure/GrpcExceptionInterceptor.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Infrastructure/GrpcExceptionInterceptor.cs index 20adb2fc7..9611c6177 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Infrastructure/GrpcExceptionInterceptor.cs +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Infrastructure/GrpcExceptionInterceptor.cs @@ -28,7 +28,7 @@ public class GrpcExceptionInterceptor : Interceptor } catch (RpcException e) { - _logger.LogError("Error calling via grpc: {Status} - {Message}", e.Status, e.Message); + _logger.LogError(e, "Error calling via gRPC: {Status}", e.Status); return default; } } diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Infrastructure/HttpClientAuthorizationDelegatingHandler.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Infrastructure/HttpClientAuthorizationDelegatingHandler.cs deleted file mode 100644 index d9b3b0ee1..000000000 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Infrastructure/HttpClientAuthorizationDelegatingHandler.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Infrastructure; - -public class HttpClientAuthorizationDelegatingHandler - : DelegatingHandler -{ - private readonly IHttpContextAccessor _httpContextAccessor; - - public HttpClientAuthorizationDelegatingHandler(IHttpContextAccessor httpContextAccessor) - { - _httpContextAccessor = httpContextAccessor; - } - - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - var authorizationHeader = _httpContextAccessor.HttpContext - .Request.Headers["Authorization"]; - - if (!string.IsNullOrWhiteSpace(authorizationHeader)) - { - request.Headers.Add("Authorization", new List() { authorizationHeader }); - } - - var token = await GetTokenAsync(); - - if (token != null) - { - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - } - - return await base.SendAsync(request, cancellationToken); - } - - Task GetTokenAsync() - { - const string ACCESS_TOKEN = "access_token"; - - return _httpContextAccessor.HttpContext - .GetTokenAsync(ACCESS_TOKEN); - } -} diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Program.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Program.cs index b589ec3cc..6bb3a0d3b 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Program.cs +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Program.cs @@ -1,208 +1,38 @@ -var appName = "Web.Shopping.HttpAggregator"; -var builder = WebApplication.CreateBuilder(args); +var builder = WebApplication.CreateBuilder(args); -builder.Host.UseSerilog(CreateSerilogLogger(builder.Configuration)); -builder.Services.AddHealthChecks() - .AddCheck("self", () => HealthCheckResult.Healthy()) - .AddUrlGroup(new Uri(builder.Configuration["CatalogUrlHC"]), name: "catalogapi-check", tags: new string[] { "catalogapi" }) - .AddUrlGroup(new Uri(builder.Configuration["OrderingUrlHC"]), name: "orderingapi-check", tags: new string[] { "orderingapi" }) - .AddUrlGroup(new Uri(builder.Configuration["BasketUrlHC"]), name: "basketapi-check", tags: new string[] { "basketapi" }) - .AddUrlGroup(new Uri(builder.Configuration["IdentityUrlHC"]), name: "identityapi-check", tags: new string[] { "identityapi" }) - .AddUrlGroup(new Uri(builder.Configuration["PaymentUrlHC"]), name: "paymentapi-check", tags: new string[] { "paymentapi" }); -builder.Services.AddCustomMvc(builder.Configuration) - .AddCustomAuthentication(builder.Configuration) - .AddApplicationServices() - .AddGrpcServices(); -var app = builder.Build(); -if (app.Environment.IsDevelopment()) -{ - app.UseDeveloperExceptionPage(); -} -else -{ - app.UseExceptionHandler("/Home/Error"); -} -var pathBase = builder.Configuration["PATH_BASE"]; -if (!string.IsNullOrEmpty(pathBase)) -{ - app.UsePathBase(pathBase); -} +builder.AddServiceDefaults(); -app.UseHttpsRedirection(); +builder.Services.AddReverseProxy(builder.Configuration); +builder.Services.AddControllers(); -app.UseSwagger().UseSwaggerUI(c => +builder.Services.AddHealthChecks(builder.Configuration); +builder.Services.AddCors(options => { - c.SwaggerEndpoint($"{(!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty)}/swagger/v1/swagger.json", "Purchase BFF V1"); - - c.OAuthClientId("webshoppingaggswaggerui"); - c.OAuthClientSecret(string.Empty); - c.OAuthRealm(string.Empty); - c.OAuthAppName("web shopping bff Swagger UI"); + // TODO: Read allowed origins from configuration + options.AddPolicy("CorsPolicy", + builder => builder + .SetIsOriginAllowed((host) => true) + .AllowAnyMethod() + .AllowAnyHeader() + .AllowCredentials()); }); -app.UseRouting(); -app.UseCors("CorsPolicy"); -app.UseAuthentication(); -app.UseAuthorization(); - -app.MapDefaultControllerRoute(); -app.MapControllers(); -app.MapHealthChecks("/hc", new HealthCheckOptions() -{ - Predicate = _ => true, - ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse -}); -app.MapHealthChecks("/liveness", new HealthCheckOptions -{ - Predicate = r => r.Name.Contains("self") -}); - -try -{ - Log.Information("Starts Web Application ({ApplicationContext})...", Program.AppName); - await app.RunAsync(); - - return 0; -} -catch (Exception ex) -{ - Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", Program.AppName); - return 1; -} -finally -{ - Log.CloseAndFlush(); -} - -Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) -{ - var seqServerUrl = configuration["Serilog:SeqServerUrl"]; - var logstashUrl = configuration["Serilog:LogstashgUrl"]; - return new LoggerConfiguration() - .MinimumLevel.Verbose() - .Enrich.WithProperty("ApplicationContext", Program.AppName) - .Enrich.FromLogContext() - .WriteTo.Console() - .ReadFrom.Configuration(configuration) - .CreateLogger(); -} -public partial class Program -{ - - public static string Namespace = typeof(Program).Assembly.GetName().Name; - public static string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1); -} - -public static class ServiceCollectionExtensions -{ - public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration) - { - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); - - var identityUrl = configuration.GetValue("urls:identity"); - 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(); - services.Configure(configuration.GetSection("urls")); - - services.AddControllers() - .AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true); - - services.AddSwaggerGen(options => - { - options.SwaggerDoc("v1", new OpenApiInfo - { - Title = "Shopping Aggregator for Web Clients", - Version = "v1", - Description = "Shopping Aggregator for Web Clients" - }); - 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() - { - { "webshoppingagg", "Shopping Aggregator for Web Clients" } - } - } - } - }); - - options.OperationFilter(); - }); - services.AddCors(options => - { - options.AddPolicy("CorsPolicy", - builder => builder - .SetIsOriginAllowed((host) => true) - .AllowAnyMethod() - .AllowAnyHeader() - .AllowCredentials()); - }); - - return services; - } - public static IServiceCollection AddApplicationServices(this IServiceCollection services) - { - //register delegating handlers - services.AddTransient(); - services.AddSingleton(); +builder.Services.AddApplicationServices(); +builder.Services.AddGrpcServices(); - //register http services +builder.Services.Configure(builder.Configuration.GetSection("urls")); - services.AddHttpClient() - .AddHttpMessageHandler(); - - return services; - } - - public static IServiceCollection AddGrpcServices(this IServiceCollection services) - { - services.AddTransient(); - - services.AddScoped(); - - services.AddGrpcClient((services, options) => - { - var basketApi = services.GetRequiredService>().Value.GrpcBasket; - options.Address = new Uri(basketApi); - }).AddInterceptor(); +var app = builder.Build(); - services.AddScoped(); +app.UseServiceDefaults(); - services.AddGrpcClient((services, options) => - { - var catalogApi = services.GetRequiredService>().Value.GrpcCatalog; - options.Address = new Uri(catalogApi); - }).AddInterceptor(); +app.UseHttpsRedirection(); - services.AddScoped(); +app.UseCors("CorsPolicy"); +app.UseAuthentication(); +app.UseAuthorization(); - services.AddGrpcClient((services, options) => - { - var orderingApi = services.GetRequiredService>().Value.GrpcOrdering; - options.Address = new Uri(orderingApi); - }).AddInterceptor(); +app.MapControllers(); +app.MapReverseProxy(); - return services; - } -} +await app.RunAsync(); diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Properties/launchSettings.json b/src/ApiGateways/Web.Bff.Shopping/aggregator/Properties/launchSettings.json index 925e70b0d..4525154b7 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Properties/launchSettings.json +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Properties/launchSettings.json @@ -1,29 +1,12 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:57425/", - "sslPort": 0 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "api/values", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "PurchaseForMvc": { + "Web.Shopping.HttpAggregator": { "commandName": "Project", "launchBrowser": true, - "launchUrl": "api/values", + "applicationUrl": "http://localhost:5229/", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:61632/" + } } } } \ No newline at end of file diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/OrderingService.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/OrderingService.cs index afa86b31b..c9398179f 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/OrderingService.cs +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/OrderingService.cs @@ -2,10 +2,10 @@ public class OrderingService : IOrderingService { - private readonly OrderingGrpc.OrderingGrpcClient _orderingGrpcClient; + private readonly GrpcOrdering.OrderingGrpc.OrderingGrpcClient _orderingGrpcClient; private readonly ILogger _logger; - public OrderingService(OrderingGrpc.OrderingGrpcClient orderingGrpcClient, ILogger logger) + public OrderingService(GrpcOrdering.OrderingGrpc.OrderingGrpcClient orderingGrpcClient, ILogger logger) { _orderingGrpcClient = orderingGrpcClient; _logger = logger; @@ -48,14 +48,14 @@ public class OrderingService : IOrderingService return data; } - private CreateOrderDraftCommand MapToOrderDraftCommand(BasketData basketData) + private GrpcOrdering.CreateOrderDraftCommand MapToOrderDraftCommand(BasketData basketData) { - var command = new CreateOrderDraftCommand + var command = new GrpcOrdering.CreateOrderDraftCommand { BuyerId = basketData.BuyerId, }; - basketData.Items.ForEach(i => command.Items.Add(new BasketItem + basketData.Items.ForEach(i => command.Items.Add(new GrpcOrdering.BasketItem { Id = i.Id, OldUnitPrice = (double)i.OldUnitPrice, 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 782c730ab..d7523af19 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj @@ -5,30 +5,18 @@ Web.Shopping.HttpAggregator Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator ..\..\..\docker-compose.dcproj - false - true - - - - - + - - - - - - - - - - + + + + diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.Development.json b/src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.Development.json index 19b8c1529..b0bacf428 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.Development.json +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.Development.json @@ -1,15 +1,8 @@ { "Logging": { - "IncludeScopes": false, - "Debug": { - "LogLevel": { - "Default": "Debug" - } - }, - "Console": { - "LogLevel": { - "Default": "Debug" - } + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" } } } diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.json b/src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.json index 26bb0ac7a..711da6143 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.json +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.json @@ -1,15 +1,138 @@ { "Logging": { - "IncludeScopes": false, - "Debug": { - "LogLevel": { - "Default": "Warning" + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "System.Net.Http": "Warning" + } + }, + "OpenApi": { + "Endpoint": { + "Name": "Purchase BFF V1" + }, + "Document": { + "Description": "Shopping Aggregator for Web Clients", + "Title": "Shopping Aggregator for Web Clients", + "Version": "v1" + }, + "Auth": { + "ClientId": "webshoppingaggswaggerui", + "AppName": "Web Shopping BFF Swagger UI" + } + }, + "Identity": { + "Url": "http://localhost:5223", + "Audience": "webshoppingagg", + "Scopes": { + "webshoppingagg": "Shopping Aggregator for Web Clients" + } + }, + "ReverseProxy": { + "Routes": { + "c-short": { + "ClusterId": "catalog", + "Match": { + "Path": "c/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/c" } + ] + }, + "c-long": { + "ClusterId": "catalog", + "Match": { + "Path": "catalog-api/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/catalog-api" } + ] + }, + "b-short": { + "ClusterId": "basket", + "Match": { + "Path": "b/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/b" } + ] + }, + "b-long": { + "ClusterId": "basket", + "Match": { + "Path": "basket-api/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/basket-api" } + ] + }, + "o-short": { + "ClusterId": "orders", + "Match": { + "Path": "o/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/o" } + ] + }, + "o-long": { + "ClusterId": "orders", + "Match": { + "Path": "ordering-api/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/ordering-api" } + ] + }, + "h-long": { + "ClusterId": "signalr", + "Match": { + "Path": "hub/notificationhub/{**catch-all}" + } } }, - "Console": { - "LogLevel": { - "Default": "Warning" + "Clusters": { + "basket": { + "Destinations": { + "destination0": { + "Address": "http://localhost:5221" + } + } + }, + "catalog": { + "Destinations": { + "destination0": { + "Address": "http://localhost:5222" + } + } + }, + "orders": { + "Destinations": { + "destination0": { + "Address": "http://localhost:5224" + } + } + }, + "signalr": { + "Destinations": { + "destination0": { + "Address": "http://localhost:5225" + } + } } } - } + }, + "Urls": { + "Basket": "http://localhost:5221", + "Catalog": "http://localhost:5222", + "Orders": "http://localhost:5224", + "Identity": "http://localhost:5223", + "Signalr": "http://localhost:5225", + "GrpcBasket": "http://localhost:6221", + "GrpcCatalog": "http://localhost:6222", + "GrpcOrdering": "http://localhost:6224" + }, + "CatalogUrlHC": "http://localhost:5222/hc", + "OrderingUrlHC": "http://localhost:5224/hc", + "BasketUrlHC": "http://localhost:5221/hc", + "IdentityUrlHC": "http://localhost:5223/hc" } diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.localhost.json b/src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.localhost.json deleted file mode 100644 index 055bcfc7f..000000000 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.localhost.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "urls": { - "basket": "http://localhost:55103", - "catalog": "http://localhost:55101", - "orders": "http://localhost:55102", - "identity": "http://localhost:55105", - "grpcBasket": "http://localhost:5580", - "grpcCatalog": "http://localhost:81", - "grpcOrdering": "http://localhost:5581" - } -} diff --git a/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEventHandler.cs b/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEventHandler.cs index 8545dc893..b82ac6a5f 100644 --- a/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEventHandler.cs +++ b/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEventHandler.cs @@ -12,9 +12,10 @@ namespace EventBus.Tests Handled = false; } - public async Task Handle(TestIntegrationEvent @event) + public Task Handle(TestIntegrationEvent @event) { Handled = true; + return Task.CompletedTask; } } } diff --git a/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationOtherEventHandler.cs b/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationOtherEventHandler.cs index b4ea66bb4..82ea25baa 100644 --- a/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationOtherEventHandler.cs +++ b/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationOtherEventHandler.cs @@ -12,9 +12,10 @@ namespace EventBus.Tests Handled = false; } - public async Task Handle(TestIntegrationEvent @event) + public Task Handle(TestIntegrationEvent @event) { Handled = true; + return Task.CompletedTask; } } } diff --git a/src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs b/src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs index 492a10e42..cab6338e8 100644 --- a/src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs +++ b/src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs @@ -8,12 +8,6 @@ public interface IEventBus where T : IntegrationEvent where TH : IIntegrationEventHandler; - void SubscribeDynamic(string eventName) - where TH : IDynamicIntegrationEventHandler; - - void UnsubscribeDynamic(string eventName) - where TH : IDynamicIntegrationEventHandler; - void Unsubscribe() where TH : IIntegrationEventHandler where T : IntegrationEvent; diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersistentConnection.cs b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersistentConnection.cs index 48714cd2f..4f3f573f5 100644 --- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersistentConnection.cs +++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersistentConnection.cs @@ -59,7 +59,7 @@ public class DefaultRabbitMQPersistentConnection .Or() .WaitAndRetry(_retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) => { - _logger.LogWarning(ex, "RabbitMQ Client could not connect after {TimeOut}s ({ExceptionMessage})", $"{time.TotalSeconds:n1}", ex.Message); + _logger.LogWarning(ex, "RabbitMQ Client could not connect after {TimeOut}s", $"{time.TotalSeconds:n1}"); } ); @@ -81,7 +81,7 @@ public class DefaultRabbitMQPersistentConnection } else { - _logger.LogCritical("FATAL ERROR: RabbitMQ connections could not be created and opened"); + _logger.LogCritical("Fatal error: RabbitMQ connections could not be created and opened"); return false; } diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs index b8312d7c1..aa4dc17ef 100644 --- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs +++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs @@ -4,7 +4,6 @@ using Microsoft.Extensions.DependencyInjection; public class EventBusRabbitMQ : IEventBus, IDisposable { const string BROKER_NAME = "eshop_event_bus"; - const string AUTOFAC_SCOPE_NAME = "eshop_event_bus"; private readonly IRabbitMQPersistentConnection _persistentConnection; private readonly ILogger _logger; @@ -58,7 +57,7 @@ public class EventBusRabbitMQ : IEventBus, IDisposable .Or() .WaitAndRetry(_retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) => { - _logger.LogWarning(ex, "Could not publish event: {EventId} after {Timeout}s ({ExceptionMessage})", @event.Id, $"{time.TotalSeconds:n1}", ex.Message); + _logger.LogWarning(ex, "Could not publish event: {EventId} after {Timeout}s", @event.Id, $"{time.TotalSeconds:n1}"); }); var eventName = @event.GetType().Name; @@ -194,7 +193,7 @@ public class EventBusRabbitMQ : IEventBus, IDisposable } catch (Exception ex) { - _logger.LogWarning(ex, "----- ERROR Processing message \"{Message}\"", message); + _logger.LogWarning(ex, "Error Processing message \"{Message}\"", message); } // Even on exception we take the message off the queue. diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj index bf442f8b1..329cc99e1 100644 --- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj +++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj @@ -6,7 +6,6 @@ - diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/GlobalUsings.cs b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/GlobalUsings.cs index 6fa5f0bf4..d63437f0e 100644 --- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/GlobalUsings.cs +++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/GlobalUsings.cs @@ -7,7 +7,6 @@ global using RabbitMQ.Client.Exceptions; global using System; global using System.IO; global using System.Net.Sockets; -global using Autofac; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; diff --git a/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs b/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs index 0b350967d..10abbfafc 100644 --- a/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs +++ b/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus; @@ -12,7 +12,6 @@ public class EventBusServiceBus : IEventBus, IAsyncDisposable private readonly string _subscriptionName; private readonly ServiceBusSender _sender; private readonly ServiceBusProcessor _processor; - private readonly string AUTOFAC_SCOPE_NAME = "eshop_event_bus"; private const string INTEGRATION_EVENT_SUFFIX = "IntegrationEvent"; public EventBusServiceBus(IServiceBusPersisterConnection serviceBusPersisterConnection, @@ -141,7 +140,7 @@ public class EventBusServiceBus : IEventBus, IAsyncDisposable var ex = args.Exception; var context = args.ErrorSource; - _logger.LogError(ex, "ERROR handling message: {ExceptionMessage} - Context: {@ExceptionContext}", ex.Message, context); + _logger.LogError(ex, "Error handling message - Context: {@ExceptionContext}", context); return Task.CompletedTask; } @@ -198,4 +197,4 @@ public class EventBusServiceBus : IEventBus, IAsyncDisposable _subsManager.Clear(); await _processor.CloseAsync(); } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.csproj b/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.csproj index 545f190ed..8b1785e6f 100644 --- a/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.csproj +++ b/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.csproj @@ -6,7 +6,6 @@ - diff --git a/src/BuildingBlocks/EventBus/EventBusServiceBus/GlobalUsings.cs b/src/BuildingBlocks/EventBus/EventBusServiceBus/GlobalUsings.cs index 73eb45c1f..836f5ae52 100644 --- a/src/BuildingBlocks/EventBus/EventBusServiceBus/GlobalUsings.cs +++ b/src/BuildingBlocks/EventBus/EventBusServiceBus/GlobalUsings.cs @@ -2,7 +2,6 @@ global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; global using System.Threading.Tasks; global using System; -global using Autofac; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; global using Microsoft.Extensions.Logging; global using System.Text; diff --git a/src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHostExtensions.cs b/src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHostExtensions.cs index 3c7fc105a..45bedc2c4 100644 --- a/src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHostExtensions.cs +++ b/src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHostExtensions.cs @@ -1,30 +1,30 @@ -using Microsoft.EntityFrameworkCore; +using System; +using System.Data.SqlClient; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Polly; -using System; -using System.Data.SqlClient; namespace Microsoft.AspNetCore.Hosting { public static class IWebHostExtensions { - public static bool IsInKubernetes(this IWebHost webHost) + public static bool IsInKubernetes(this IServiceProvider services) { - var cfg = webHost.Services.GetService(); + var cfg = services.GetService(); var orchestratorType = cfg.GetValue("OrchestratorType"); return orchestratorType?.ToUpper() == "K8S"; } - public static IWebHost MigrateDbContext(this IWebHost webHost, Action seeder) where TContext : DbContext + public static IServiceProvider MigrateDbContext(this IServiceProvider services, Action seeder) where TContext : DbContext { - var underK8s = webHost.IsInKubernetes(); + var underK8s = services.IsInKubernetes(); - using var scope = webHost.Services.CreateScope(); - var services = scope.ServiceProvider; - var logger = services.GetRequiredService>(); - var context = services.GetService(); + using var scope = services.CreateScope(); + var scopeServices = scope.ServiceProvider; + var logger = scopeServices.GetRequiredService>(); + var context = scopeServices.GetService(); try { @@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Hosting if (underK8s) { - InvokeSeeder(seeder, context, services); + InvokeSeeder(seeder, context, scopeServices); } else { @@ -43,14 +43,14 @@ namespace Microsoft.AspNetCore.Hosting sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), onRetry: (exception, timeSpan, retry, ctx) => { - logger.LogWarning(exception, "[{prefix}] Exception {ExceptionType} with message {Message} detected on attempt {retry} of {retries}", nameof(TContext), exception.GetType().Name, exception.Message, retry, retries); + logger.LogWarning(exception, "[{prefix}] Error migrating database (attempt {retry} of {retries})", nameof(TContext), retry, retries); }); //if the sql server container is not created on run docker compose this //migration can't fail for network related exception. The retry options for DbContext only //apply to transient exceptions // Note that this is NOT applied when running some orchestrators (let the orchestrator to recreate the failing service) - retry.Execute(() => InvokeSeeder(seeder, context, services)); + retry.Execute(() => InvokeSeeder(seeder, context, scopeServices)); } logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name); @@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.Hosting } } - return webHost; + return services; } private static void InvokeSeeder(Action seeder, TContext context, IServiceProvider services) diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index fc3d89ee9..9a7ce49ec 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -3,7 +3,6 @@ true true - @@ -14,8 +13,6 @@ - - @@ -29,6 +26,7 @@ + @@ -51,6 +49,7 @@ + @@ -82,12 +81,6 @@ - - - - - - @@ -95,5 +88,6 @@ + - + \ No newline at end of file diff --git a/src/Services/Basket/Basket.API/Auth/Client/enable-token-client.js b/src/Services/Basket/Basket.API/Auth/Client/enable-token-client.js deleted file mode 100644 index 3c207e7ea..000000000 --- a/src/Services/Basket/Basket.API/Auth/Client/enable-token-client.js +++ /dev/null @@ -1,28 +0,0 @@ -(function ($, swaggerUi) { - $(function () { - var settings = { - authority: 'https://localhost:5105', - client_id: 'js', - popup_redirect_uri: window.location.protocol - + '//' - + window.location.host - + '/tokenclient/popup.html', - - response_type: 'id_token token', - scope: 'openid profile basket', - - filter_protocol_claims: true - }, - manager = new OidcTokenManager(settings), - $inputApiKey = $('#input_apiKey'); - - $inputApiKey.on('dblclick', function () { - manager.openPopupForTokenAsync() - .then(function () { - $inputApiKey.val(manager.access_token).change(); - }, function (error) { - console.error(error); - }); - }); - }); -})(jQuery, window.swaggerUi); \ No newline at end of file diff --git a/src/Services/Basket/Basket.API/Auth/Client/oidc-token-manager.js b/src/Services/Basket/Basket.API/Auth/Client/oidc-token-manager.js deleted file mode 100644 index a6f3f29e5..000000000 --- a/src/Services/Basket/Basket.API/Auth/Client/oidc-token-manager.js +++ /dev/null @@ -1,8896 +0,0 @@ -(function () { - - // globals - var _promiseFactory; - var _httpRequest; -/* -CryptoJS v3.1.2 -code.google.com/p/crypto-js -(c) 2009-2013 by Jeff Mott. All rights reserved. -code.google.com/p/crypto-js/wiki/License -*/ -/** - * CryptoJS core components. - */ -var CryptoJS = CryptoJS || (function (Math, undefined) { - /** - * CryptoJS namespace. - */ - var C = {}; - - /** - * Library namespace. - */ - var C_lib = C.lib = {}; - - /** - * Base object for prototypal inheritance. - */ - var Base = C_lib.Base = (function () { - function F() {} - - return { - /** - * Creates a new object that inherits from this object. - * - * @param {Object} overrides Properties to copy into the new object. - * - * @return {Object} The new object. - * - * @static - * - * @example - * - * var MyType = CryptoJS.lib.Base.extend({ - * field: 'value', - * - * method: function () { - * } - * }); - */ - extend: function (overrides) { - // Spawn - F.prototype = this; - var subtype = new F(); - - // Augment - if (overrides) { - subtype.mixIn(overrides); - } - - // Create default initializer - if (!subtype.hasOwnProperty('init')) { - subtype.init = function () { - subtype.$super.init.apply(this, arguments); - }; - } - - // Initializer's prototype is the subtype object - subtype.init.prototype = subtype; - - // Reference supertype - subtype.$super = this; - - return subtype; - }, - - /** - * Extends this object and runs the init method. - * Arguments to create() will be passed to init(). - * - * @return {Object} The new object. - * - * @static - * - * @example - * - * var instance = MyType.create(); - */ - create: function () { - var instance = this.extend(); - instance.init.apply(instance, arguments); - - return instance; - }, - - /** - * Initializes a newly created object. - * Override this method to add some logic when your objects are created. - * - * @example - * - * var MyType = CryptoJS.lib.Base.extend({ - * init: function () { - * // ... - * } - * }); - */ - init: function () { - }, - - /** - * Copies properties into this object. - * - * @param {Object} properties The properties to mix in. - * - * @example - * - * MyType.mixIn({ - * field: 'value' - * }); - */ - mixIn: function (properties) { - for (var propertyName in properties) { - if (properties.hasOwnProperty(propertyName)) { - this[propertyName] = properties[propertyName]; - } - } - - // IE won't copy toString using the loop above - if (properties.hasOwnProperty('toString')) { - this.toString = properties.toString; - } - }, - - /** - * Creates a copy of this object. - * - * @return {Object} The clone. - * - * @example - * - * var clone = instance.clone(); - */ - clone: function () { - return this.init.prototype.extend(this); - } - }; - }()); - - /** - * An array of 32-bit words. - * - * @property {Array} words The array of 32-bit words. - * @property {number} sigBytes The number of significant bytes in this word array. - */ - var WordArray = C_lib.WordArray = Base.extend({ - /** - * Initializes a newly created word array. - * - * @param {Array} words (Optional) An array of 32-bit words. - * @param {number} sigBytes (Optional) The number of significant bytes in the words. - * - * @example - * - * var wordArray = CryptoJS.lib.WordArray.create(); - * var wordArray = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607]); - * var wordArray = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607], 6); - */ - init: function (words, sigBytes) { - words = this.words = words || []; - - if (sigBytes != undefined) { - this.sigBytes = sigBytes; - } else { - this.sigBytes = words.length * 4; - } - }, - - /** - * Converts this word array to a string. - * - * @param {Encoder} encoder (Optional) The encoding strategy to use. Default: CryptoJS.enc.Hex - * - * @return {string} The stringified word array. - * - * @example - * - * var string = wordArray + ''; - * var string = wordArray.toString(); - * var string = wordArray.toString(CryptoJS.enc.Utf8); - */ - toString: function (encoder) { - return (encoder || Hex).stringify(this); - }, - - /** - * Concatenates a word array to this word array. - * - * @param {WordArray} wordArray The word array to append. - * - * @return {WordArray} This word array. - * - * @example - * - * wordArray1.concat(wordArray2); - */ - concat: function (wordArray) { - // Shortcuts - var thisWords = this.words; - var thatWords = wordArray.words; - var thisSigBytes = this.sigBytes; - var thatSigBytes = wordArray.sigBytes; - - // Clamp excess bits - this.clamp(); - - // Concat - if (thisSigBytes % 4) { - // Copy one byte at a time - for (var i = 0; i < thatSigBytes; i++) { - var thatByte = (thatWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; - thisWords[(thisSigBytes + i) >>> 2] |= thatByte << (24 - ((thisSigBytes + i) % 4) * 8); - } - } else if (thatWords.length > 0xffff) { - // Copy one word at a time - for (var i = 0; i < thatSigBytes; i += 4) { - thisWords[(thisSigBytes + i) >>> 2] = thatWords[i >>> 2]; - } - } else { - // Copy all words at once - thisWords.push.apply(thisWords, thatWords); - } - this.sigBytes += thatSigBytes; - - // Chainable - return this; - }, - - /** - * Removes insignificant bits. - * - * @example - * - * wordArray.clamp(); - */ - clamp: function () { - // Shortcuts - var words = this.words; - var sigBytes = this.sigBytes; - - // Clamp - words[sigBytes >>> 2] &= 0xffffffff << (32 - (sigBytes % 4) * 8); - words.length = Math.ceil(sigBytes / 4); - }, - - /** - * Creates a copy of this word array. - * - * @return {WordArray} The clone. - * - * @example - * - * var clone = wordArray.clone(); - */ - clone: function () { - var clone = Base.clone.call(this); - clone.words = this.words.slice(0); - - return clone; - }, - - /** - * Creates a word array filled with random bytes. - * - * @param {number} nBytes The number of random bytes to generate. - * - * @return {WordArray} The random word array. - * - * @static - * - * @example - * - * var wordArray = CryptoJS.lib.WordArray.random(16); - */ - random: function (nBytes) { - var words = []; - for (var i = 0; i < nBytes; i += 4) { - words.push((Math.random() * 0x100000000) | 0); - } - - return new WordArray.init(words, nBytes); - } - }); - - /** - * Encoder namespace. - */ - var C_enc = C.enc = {}; - - /** - * Hex encoding strategy. - */ - var Hex = C_enc.Hex = { - /** - * Converts a word array to a hex string. - * - * @param {WordArray} wordArray The word array. - * - * @return {string} The hex string. - * - * @static - * - * @example - * - * var hexString = CryptoJS.enc.Hex.stringify(wordArray); - */ - stringify: function (wordArray) { - // Shortcuts - var words = wordArray.words; - var sigBytes = wordArray.sigBytes; - - // Convert - var hexChars = []; - for (var i = 0; i < sigBytes; i++) { - var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; - hexChars.push((bite >>> 4).toString(16)); - hexChars.push((bite & 0x0f).toString(16)); - } - - return hexChars.join(''); - }, - - /** - * Converts a hex string to a word array. - * - * @param {string} hexStr The hex string. - * - * @return {WordArray} The word array. - * - * @static - * - * @example - * - * var wordArray = CryptoJS.enc.Hex.parse(hexString); - */ - parse: function (hexStr) { - // Shortcut - var hexStrLength = hexStr.length; - - // Convert - var words = []; - for (var i = 0; i < hexStrLength; i += 2) { - words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4); - } - - return new WordArray.init(words, hexStrLength / 2); - } - }; - - /** - * Latin1 encoding strategy. - */ - var Latin1 = C_enc.Latin1 = { - /** - * Converts a word array to a Latin1 string. - * - * @param {WordArray} wordArray The word array. - * - * @return {string} The Latin1 string. - * - * @static - * - * @example - * - * var latin1String = CryptoJS.enc.Latin1.stringify(wordArray); - */ - stringify: function (wordArray) { - // Shortcuts - var words = wordArray.words; - var sigBytes = wordArray.sigBytes; - - // Convert - var latin1Chars = []; - for (var i = 0; i < sigBytes; i++) { - var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; - latin1Chars.push(String.fromCharCode(bite)); - } - - return latin1Chars.join(''); - }, - - /** - * Converts a Latin1 string to a word array. - * - * @param {string} latin1Str The Latin1 string. - * - * @return {WordArray} The word array. - * - * @static - * - * @example - * - * var wordArray = CryptoJS.enc.Latin1.parse(latin1String); - */ - parse: function (latin1Str) { - // Shortcut - var latin1StrLength = latin1Str.length; - - // Convert - var words = []; - for (var i = 0; i < latin1StrLength; i++) { - words[i >>> 2] |= (latin1Str.charCodeAt(i) & 0xff) << (24 - (i % 4) * 8); - } - - return new WordArray.init(words, latin1StrLength); - } - }; - - /** - * UTF-8 encoding strategy. - */ - var Utf8 = C_enc.Utf8 = { - /** - * Converts a word array to a UTF-8 string. - * - * @param {WordArray} wordArray The word array. - * - * @return {string} The UTF-8 string. - * - * @static - * - * @example - * - * var utf8String = CryptoJS.enc.Utf8.stringify(wordArray); - */ - stringify: function (wordArray) { - try { - return decodeURIComponent(escape(Latin1.stringify(wordArray))); - } catch (e) { - throw new Error('Malformed UTF-8 data'); - } - }, - - /** - * Converts a UTF-8 string to a word array. - * - * @param {string} utf8Str The UTF-8 string. - * - * @return {WordArray} The word array. - * - * @static - * - * @example - * - * var wordArray = CryptoJS.enc.Utf8.parse(utf8String); - */ - parse: function (utf8Str) { - return Latin1.parse(unescape(encodeURIComponent(utf8Str))); - } - }; - - /** - * Abstract buffered block algorithm template. - * - * The property blockSize must be implemented in a concrete subtype. - * - * @property {number} _minBufferSize The number of blocks that should be kept unprocessed in the buffer. Default: 0 - */ - var BufferedBlockAlgorithm = C_lib.BufferedBlockAlgorithm = Base.extend({ - /** - * Resets this block algorithm's data buffer to its initial state. - * - * @example - * - * bufferedBlockAlgorithm.reset(); - */ - reset: function () { - // Initial values - this._data = new WordArray.init(); - this._nDataBytes = 0; - }, - - /** - * Adds new data to this block algorithm's buffer. - * - * @param {WordArray|string} data The data to append. Strings are converted to a WordArray using UTF-8. - * - * @example - * - * bufferedBlockAlgorithm._append('data'); - * bufferedBlockAlgorithm._append(wordArray); - */ - _append: function (data) { - // Convert string to WordArray, else assume WordArray already - if (typeof data == 'string') { - data = Utf8.parse(data); - } - - // Append - this._data.concat(data); - this._nDataBytes += data.sigBytes; - }, - - /** - * Processes available data blocks. - * - * This method invokes _doProcessBlock(offset), which must be implemented by a concrete subtype. - * - * @param {boolean} doFlush Whether all blocks and partial blocks should be processed. - * - * @return {WordArray} The processed data. - * - * @example - * - * var processedData = bufferedBlockAlgorithm._process(); - * var processedData = bufferedBlockAlgorithm._process(!!'flush'); - */ - _process: function (doFlush) { - // Shortcuts - var data = this._data; - var dataWords = data.words; - var dataSigBytes = data.sigBytes; - var blockSize = this.blockSize; - var blockSizeBytes = blockSize * 4; - - // Count blocks ready - var nBlocksReady = dataSigBytes / blockSizeBytes; - if (doFlush) { - // Round up to include partial blocks - nBlocksReady = Math.ceil(nBlocksReady); - } else { - // Round down to include only full blocks, - // less the number of blocks that must remain in the buffer - nBlocksReady = Math.max((nBlocksReady | 0) - this._minBufferSize, 0); - } - - // Count words ready - var nWordsReady = nBlocksReady * blockSize; - - // Count bytes ready - var nBytesReady = Math.min(nWordsReady * 4, dataSigBytes); - - // Process blocks - if (nWordsReady) { - for (var offset = 0; offset < nWordsReady; offset += blockSize) { - // Perform concrete-algorithm logic - this._doProcessBlock(dataWords, offset); - } - - // Remove processed words - var processedWords = dataWords.splice(0, nWordsReady); - data.sigBytes -= nBytesReady; - } - - // Return processed words - return new WordArray.init(processedWords, nBytesReady); - }, - - /** - * Creates a copy of this object. - * - * @return {Object} The clone. - * - * @example - * - * var clone = bufferedBlockAlgorithm.clone(); - */ - clone: function () { - var clone = Base.clone.call(this); - clone._data = this._data.clone(); - - return clone; - }, - - _minBufferSize: 0 - }); - - /** - * Abstract hasher template. - * - * @property {number} blockSize The number of 32-bit words this hasher operates on. Default: 16 (512 bits) - */ - var Hasher = C_lib.Hasher = BufferedBlockAlgorithm.extend({ - /** - * Configuration options. - */ - cfg: Base.extend(), - - /** - * Initializes a newly created hasher. - * - * @param {Object} cfg (Optional) The configuration options to use for this hash computation. - * - * @example - * - * var hasher = CryptoJS.algo.SHA256.create(); - */ - init: function (cfg) { - // Apply config defaults - this.cfg = this.cfg.extend(cfg); - - // Set initial values - this.reset(); - }, - - /** - * Resets this hasher to its initial state. - * - * @example - * - * hasher.reset(); - */ - reset: function () { - // Reset data buffer - BufferedBlockAlgorithm.reset.call(this); - - // Perform concrete-hasher logic - this._doReset(); - }, - - /** - * Updates this hasher with a message. - * - * @param {WordArray|string} messageUpdate The message to append. - * - * @return {Hasher} This hasher. - * - * @example - * - * hasher.update('message'); - * hasher.update(wordArray); - */ - update: function (messageUpdate) { - // Append - this._append(messageUpdate); - - // Update the hash - this._process(); - - // Chainable - return this; - }, - - /** - * Finalizes the hash computation. - * Note that the finalize operation is effectively a destructive, read-once operation. - * - * @param {WordArray|string} messageUpdate (Optional) A final message update. - * - * @return {WordArray} The hash. - * - * @example - * - * var hash = hasher.finalize(); - * var hash = hasher.finalize('message'); - * var hash = hasher.finalize(wordArray); - */ - finalize: function (messageUpdate) { - // Final message update - if (messageUpdate) { - this._append(messageUpdate); - } - - // Perform concrete-hasher logic - var hash = this._doFinalize(); - - return hash; - }, - - blockSize: 512/32, - - /** - * Creates a shortcut function to a hasher's object interface. - * - * @param {Hasher} hasher The hasher to create a helper for. - * - * @return {Function} The shortcut function. - * - * @static - * - * @example - * - * var SHA256 = CryptoJS.lib.Hasher._createHelper(CryptoJS.algo.SHA256); - */ - _createHelper: function (hasher) { - return function (message, cfg) { - return new hasher.init(cfg).finalize(message); - }; - }, - - /** - * Creates a shortcut function to the HMAC's object interface. - * - * @param {Hasher} hasher The hasher to use in this HMAC helper. - * - * @return {Function} The shortcut function. - * - * @static - * - * @example - * - * var HmacSHA256 = CryptoJS.lib.Hasher._createHmacHelper(CryptoJS.algo.SHA256); - */ - _createHmacHelper: function (hasher) { - return function (message, key) { - return new C_algo.HMAC.init(hasher, key).finalize(message); - }; - } - }); - - /** - * Algorithm namespace. - */ - var C_algo = C.algo = {}; - - return C; -}(Math)); - -/* -CryptoJS v3.1.2 -code.google.com/p/crypto-js -(c) 2009-2013 by Jeff Mott. All rights reserved. -code.google.com/p/crypto-js/wiki/License -*/ -(function () { - // Shortcuts - var C = CryptoJS; - var C_lib = C.lib; - var WordArray = C_lib.WordArray; - var Hasher = C_lib.Hasher; - var C_algo = C.algo; - - // Reusable object - var W = []; - - /** - * SHA-1 hash algorithm. - */ - var SHA1 = C_algo.SHA1 = Hasher.extend({ - _doReset: function () { - this._hash = new WordArray.init([ - 0x67452301, 0xefcdab89, - 0x98badcfe, 0x10325476, - 0xc3d2e1f0 - ]); - }, - - _doProcessBlock: function (M, offset) { - // Shortcut - var H = this._hash.words; - - // Working variables - var a = H[0]; - var b = H[1]; - var c = H[2]; - var d = H[3]; - var e = H[4]; - - // Computation - for (var i = 0; i < 80; i++) { - if (i < 16) { - W[i] = M[offset + i] | 0; - } else { - var n = W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16]; - W[i] = (n << 1) | (n >>> 31); - } - - var t = ((a << 5) | (a >>> 27)) + e + W[i]; - if (i < 20) { - t += ((b & c) | (~b & d)) + 0x5a827999; - } else if (i < 40) { - t += (b ^ c ^ d) + 0x6ed9eba1; - } else if (i < 60) { - t += ((b & c) | (b & d) | (c & d)) - 0x70e44324; - } else /* if (i < 80) */ { - t += (b ^ c ^ d) - 0x359d3e2a; - } - - e = d; - d = c; - c = (b << 30) | (b >>> 2); - b = a; - a = t; - } - - // Intermediate hash value - H[0] = (H[0] + a) | 0; - H[1] = (H[1] + b) | 0; - H[2] = (H[2] + c) | 0; - H[3] = (H[3] + d) | 0; - H[4] = (H[4] + e) | 0; - }, - - _doFinalize: function () { - // Shortcuts - var data = this._data; - var dataWords = data.words; - - var nBitsTotal = this._nDataBytes * 8; - var nBitsLeft = data.sigBytes * 8; - - // Add padding - dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); - dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = Math.floor(nBitsTotal / 0x100000000); - dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = nBitsTotal; - data.sigBytes = dataWords.length * 4; - - // Hash final blocks - this._process(); - - // Return final computed hash - return this._hash; - }, - - clone: function () { - var clone = Hasher.clone.call(this); - clone._hash = this._hash.clone(); - - return clone; - } - }); - - /** - * Shortcut function to the hasher's object interface. - * - * @param {WordArray|string} message The message to hash. - * - * @return {WordArray} The hash. - * - * @static - * - * @example - * - * var hash = CryptoJS.SHA1('message'); - * var hash = CryptoJS.SHA1(wordArray); - */ - C.SHA1 = Hasher._createHelper(SHA1); - - /** - * Shortcut function to the HMAC's object interface. - * - * @param {WordArray|string} message The message to hash. - * @param {WordArray|string} key The secret key. - * - * @return {WordArray} The HMAC. - * - * @static - * - * @example - * - * var hmac = CryptoJS.HmacSHA1(message, key); - */ - C.HmacSHA1 = Hasher._createHmacHelper(SHA1); -}()); - -/* -CryptoJS v3.1.2 -code.google.com/p/crypto-js -(c) 2009-2013 by Jeff Mott. All rights reserved. -code.google.com/p/crypto-js/wiki/License -*/ -(function (Math) { - // Shortcuts - var C = CryptoJS; - var C_lib = C.lib; - var WordArray = C_lib.WordArray; - var Hasher = C_lib.Hasher; - var C_algo = C.algo; - - // Initialization and round constants tables - var H = []; - var K = []; - - // Compute constants - (function () { - function isPrime(n) { - var sqrtN = Math.sqrt(n); - for (var factor = 2; factor <= sqrtN; factor++) { - if (!(n % factor)) { - return false; - } - } - - return true; - } - - function getFractionalBits(n) { - return ((n - (n | 0)) * 0x100000000) | 0; - } - - var n = 2; - var nPrime = 0; - while (nPrime < 64) { - if (isPrime(n)) { - if (nPrime < 8) { - H[nPrime] = getFractionalBits(Math.pow(n, 1 / 2)); - } - K[nPrime] = getFractionalBits(Math.pow(n, 1 / 3)); - - nPrime++; - } - - n++; - } - }()); - - // Reusable object - var W = []; - - /** - * SHA-256 hash algorithm. - */ - var SHA256 = C_algo.SHA256 = Hasher.extend({ - _doReset: function () { - this._hash = new WordArray.init(H.slice(0)); - }, - - _doProcessBlock: function (M, offset) { - // Shortcut - var H = this._hash.words; - - // Working variables - var a = H[0]; - var b = H[1]; - var c = H[2]; - var d = H[3]; - var e = H[4]; - var f = H[5]; - var g = H[6]; - var h = H[7]; - - // Computation - for (var i = 0; i < 64; i++) { - if (i < 16) { - W[i] = M[offset + i] | 0; - } else { - var gamma0x = W[i - 15]; - var gamma0 = ((gamma0x << 25) | (gamma0x >>> 7)) ^ - ((gamma0x << 14) | (gamma0x >>> 18)) ^ - (gamma0x >>> 3); - - var gamma1x = W[i - 2]; - var gamma1 = ((gamma1x << 15) | (gamma1x >>> 17)) ^ - ((gamma1x << 13) | (gamma1x >>> 19)) ^ - (gamma1x >>> 10); - - W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16]; - } - - var ch = (e & f) ^ (~e & g); - var maj = (a & b) ^ (a & c) ^ (b & c); - - var sigma0 = ((a << 30) | (a >>> 2)) ^ ((a << 19) | (a >>> 13)) ^ ((a << 10) | (a >>> 22)); - var sigma1 = ((e << 26) | (e >>> 6)) ^ ((e << 21) | (e >>> 11)) ^ ((e << 7) | (e >>> 25)); - - var t1 = h + sigma1 + ch + K[i] + W[i]; - var t2 = sigma0 + maj; - - h = g; - g = f; - f = e; - e = (d + t1) | 0; - d = c; - c = b; - b = a; - a = (t1 + t2) | 0; - } - - // Intermediate hash value - H[0] = (H[0] + a) | 0; - H[1] = (H[1] + b) | 0; - H[2] = (H[2] + c) | 0; - H[3] = (H[3] + d) | 0; - H[4] = (H[4] + e) | 0; - H[5] = (H[5] + f) | 0; - H[6] = (H[6] + g) | 0; - H[7] = (H[7] + h) | 0; - }, - - _doFinalize: function () { - // Shortcuts - var data = this._data; - var dataWords = data.words; - - var nBitsTotal = this._nDataBytes * 8; - var nBitsLeft = data.sigBytes * 8; - - // Add padding - dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); - dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = Math.floor(nBitsTotal / 0x100000000); - dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = nBitsTotal; - data.sigBytes = dataWords.length * 4; - - // Hash final blocks - this._process(); - - // Return final computed hash - return this._hash; - }, - - clone: function () { - var clone = Hasher.clone.call(this); - clone._hash = this._hash.clone(); - - return clone; - } - }); - - /** - * Shortcut function to the hasher's object interface. - * - * @param {WordArray|string} message The message to hash. - * - * @return {WordArray} The hash. - * - * @static - * - * @example - * - * var hash = CryptoJS.SHA256('message'); - * var hash = CryptoJS.SHA256(wordArray); - */ - C.SHA256 = Hasher._createHelper(SHA256); - - /** - * Shortcut function to the HMAC's object interface. - * - * @param {WordArray|string} message The message to hash. - * @param {WordArray|string} key The secret key. - * - * @return {WordArray} The HMAC. - * - * @static - * - * @example - * - * var hmac = CryptoJS.HmacSHA256(message, key); - */ - C.HmacSHA256 = Hasher._createHmacHelper(SHA256); -}(Math)); - -/* -CryptoJS v3.1.2 -code.google.com/p/crypto-js -(c) 2009-2013 by Jeff Mott. All rights reserved. -code.google.com/p/crypto-js/wiki/License -*/ -(function (undefined) { - // Shortcuts - var C = CryptoJS; - var C_lib = C.lib; - var Base = C_lib.Base; - var X32WordArray = C_lib.WordArray; - - /** - * x64 namespace. - */ - var C_x64 = C.x64 = {}; - - /** - * A 64-bit word. - */ - var X64Word = C_x64.Word = Base.extend({ - /** - * Initializes a newly created 64-bit word. - * - * @param {number} high The high 32 bits. - * @param {number} low The low 32 bits. - * - * @example - * - * var x64Word = CryptoJS.x64.Word.create(0x00010203, 0x04050607); - */ - init: function (high, low) { - this.high = high; - this.low = low; - } - - /** - * Bitwise NOTs this word. - * - * @return {X64Word} A new x64-Word object after negating. - * - * @example - * - * var negated = x64Word.not(); - */ - // not: function () { - // var high = ~this.high; - // var low = ~this.low; - - // return X64Word.create(high, low); - // }, - - /** - * Bitwise ANDs this word with the passed word. - * - * @param {X64Word} word The x64-Word to AND with this word. - * - * @return {X64Word} A new x64-Word object after ANDing. - * - * @example - * - * var anded = x64Word.and(anotherX64Word); - */ - // and: function (word) { - // var high = this.high & word.high; - // var low = this.low & word.low; - - // return X64Word.create(high, low); - // }, - - /** - * Bitwise ORs this word with the passed word. - * - * @param {X64Word} word The x64-Word to OR with this word. - * - * @return {X64Word} A new x64-Word object after ORing. - * - * @example - * - * var ored = x64Word.or(anotherX64Word); - */ - // or: function (word) { - // var high = this.high | word.high; - // var low = this.low | word.low; - - // return X64Word.create(high, low); - // }, - - /** - * Bitwise XORs this word with the passed word. - * - * @param {X64Word} word The x64-Word to XOR with this word. - * - * @return {X64Word} A new x64-Word object after XORing. - * - * @example - * - * var xored = x64Word.xor(anotherX64Word); - */ - // xor: function (word) { - // var high = this.high ^ word.high; - // var low = this.low ^ word.low; - - // return X64Word.create(high, low); - // }, - - /** - * Shifts this word n bits to the left. - * - * @param {number} n The number of bits to shift. - * - * @return {X64Word} A new x64-Word object after shifting. - * - * @example - * - * var shifted = x64Word.shiftL(25); - */ - // shiftL: function (n) { - // if (n < 32) { - // var high = (this.high << n) | (this.low >>> (32 - n)); - // var low = this.low << n; - // } else { - // var high = this.low << (n - 32); - // var low = 0; - // } - - // return X64Word.create(high, low); - // }, - - /** - * Shifts this word n bits to the right. - * - * @param {number} n The number of bits to shift. - * - * @return {X64Word} A new x64-Word object after shifting. - * - * @example - * - * var shifted = x64Word.shiftR(7); - */ - // shiftR: function (n) { - // if (n < 32) { - // var low = (this.low >>> n) | (this.high << (32 - n)); - // var high = this.high >>> n; - // } else { - // var low = this.high >>> (n - 32); - // var high = 0; - // } - - // return X64Word.create(high, low); - // }, - - /** - * Rotates this word n bits to the left. - * - * @param {number} n The number of bits to rotate. - * - * @return {X64Word} A new x64-Word object after rotating. - * - * @example - * - * var rotated = x64Word.rotL(25); - */ - // rotL: function (n) { - // return this.shiftL(n).or(this.shiftR(64 - n)); - // }, - - /** - * Rotates this word n bits to the right. - * - * @param {number} n The number of bits to rotate. - * - * @return {X64Word} A new x64-Word object after rotating. - * - * @example - * - * var rotated = x64Word.rotR(7); - */ - // rotR: function (n) { - // return this.shiftR(n).or(this.shiftL(64 - n)); - // }, - - /** - * Adds this word with the passed word. - * - * @param {X64Word} word The x64-Word to add with this word. - * - * @return {X64Word} A new x64-Word object after adding. - * - * @example - * - * var added = x64Word.add(anotherX64Word); - */ - // add: function (word) { - // var low = (this.low + word.low) | 0; - // var carry = (low >>> 0) < (this.low >>> 0) ? 1 : 0; - // var high = (this.high + word.high + carry) | 0; - - // return X64Word.create(high, low); - // } - }); - - /** - * An array of 64-bit words. - * - * @property {Array} words The array of CryptoJS.x64.Word objects. - * @property {number} sigBytes The number of significant bytes in this word array. - */ - var X64WordArray = C_x64.WordArray = Base.extend({ - /** - * Initializes a newly created word array. - * - * @param {Array} words (Optional) An array of CryptoJS.x64.Word objects. - * @param {number} sigBytes (Optional) The number of significant bytes in the words. - * - * @example - * - * var wordArray = CryptoJS.x64.WordArray.create(); - * - * var wordArray = CryptoJS.x64.WordArray.create([ - * CryptoJS.x64.Word.create(0x00010203, 0x04050607), - * CryptoJS.x64.Word.create(0x18191a1b, 0x1c1d1e1f) - * ]); - * - * var wordArray = CryptoJS.x64.WordArray.create([ - * CryptoJS.x64.Word.create(0x00010203, 0x04050607), - * CryptoJS.x64.Word.create(0x18191a1b, 0x1c1d1e1f) - * ], 10); - */ - init: function (words, sigBytes) { - words = this.words = words || []; - - if (sigBytes != undefined) { - this.sigBytes = sigBytes; - } else { - this.sigBytes = words.length * 8; - } - }, - - /** - * Converts this 64-bit word array to a 32-bit word array. - * - * @return {CryptoJS.lib.WordArray} This word array's data as a 32-bit word array. - * - * @example - * - * var x32WordArray = x64WordArray.toX32(); - */ - toX32: function () { - // Shortcuts - var x64Words = this.words; - var x64WordsLength = x64Words.length; - - // Convert - var x32Words = []; - for (var i = 0; i < x64WordsLength; i++) { - var x64Word = x64Words[i]; - x32Words.push(x64Word.high); - x32Words.push(x64Word.low); - } - - return X32WordArray.create(x32Words, this.sigBytes); - }, - - /** - * Creates a copy of this word array. - * - * @return {X64WordArray} The clone. - * - * @example - * - * var clone = x64WordArray.clone(); - */ - clone: function () { - var clone = Base.clone.call(this); - - // Clone "words" array - var words = clone.words = this.words.slice(0); - - // Clone each X64Word object - var wordsLength = words.length; - for (var i = 0; i < wordsLength; i++) { - words[i] = words[i].clone(); - } - - return clone; - } - }); -}()); -/* -CryptoJS v3.1.2 -code.google.com/p/crypto-js -(c) 2009-2013 by Jeff Mott. All rights reserved. -code.google.com/p/crypto-js/wiki/License -*/ -(function () { - // Shortcuts - var C = CryptoJS; - var C_lib = C.lib; - var Hasher = C_lib.Hasher; - var C_x64 = C.x64; - var X64Word = C_x64.Word; - var X64WordArray = C_x64.WordArray; - var C_algo = C.algo; - - function X64Word_create() { - return X64Word.create.apply(X64Word, arguments); - } - - // Constants - var K = [ - X64Word_create(0x428a2f98, 0xd728ae22), X64Word_create(0x71374491, 0x23ef65cd), - X64Word_create(0xb5c0fbcf, 0xec4d3b2f), X64Word_create(0xe9b5dba5, 0x8189dbbc), - X64Word_create(0x3956c25b, 0xf348b538), X64Word_create(0x59f111f1, 0xb605d019), - X64Word_create(0x923f82a4, 0xaf194f9b), X64Word_create(0xab1c5ed5, 0xda6d8118), - X64Word_create(0xd807aa98, 0xa3030242), X64Word_create(0x12835b01, 0x45706fbe), - X64Word_create(0x243185be, 0x4ee4b28c), X64Word_create(0x550c7dc3, 0xd5ffb4e2), - X64Word_create(0x72be5d74, 0xf27b896f), X64Word_create(0x80deb1fe, 0x3b1696b1), - X64Word_create(0x9bdc06a7, 0x25c71235), X64Word_create(0xc19bf174, 0xcf692694), - X64Word_create(0xe49b69c1, 0x9ef14ad2), X64Word_create(0xefbe4786, 0x384f25e3), - X64Word_create(0x0fc19dc6, 0x8b8cd5b5), X64Word_create(0x240ca1cc, 0x77ac9c65), - X64Word_create(0x2de92c6f, 0x592b0275), X64Word_create(0x4a7484aa, 0x6ea6e483), - X64Word_create(0x5cb0a9dc, 0xbd41fbd4), X64Word_create(0x76f988da, 0x831153b5), - X64Word_create(0x983e5152, 0xee66dfab), X64Word_create(0xa831c66d, 0x2db43210), - X64Word_create(0xb00327c8, 0x98fb213f), X64Word_create(0xbf597fc7, 0xbeef0ee4), - X64Word_create(0xc6e00bf3, 0x3da88fc2), X64Word_create(0xd5a79147, 0x930aa725), - X64Word_create(0x06ca6351, 0xe003826f), X64Word_create(0x14292967, 0x0a0e6e70), - X64Word_create(0x27b70a85, 0x46d22ffc), X64Word_create(0x2e1b2138, 0x5c26c926), - X64Word_create(0x4d2c6dfc, 0x5ac42aed), X64Word_create(0x53380d13, 0x9d95b3df), - X64Word_create(0x650a7354, 0x8baf63de), X64Word_create(0x766a0abb, 0x3c77b2a8), - X64Word_create(0x81c2c92e, 0x47edaee6), X64Word_create(0x92722c85, 0x1482353b), - X64Word_create(0xa2bfe8a1, 0x4cf10364), X64Word_create(0xa81a664b, 0xbc423001), - X64Word_create(0xc24b8b70, 0xd0f89791), X64Word_create(0xc76c51a3, 0x0654be30), - X64Word_create(0xd192e819, 0xd6ef5218), X64Word_create(0xd6990624, 0x5565a910), - X64Word_create(0xf40e3585, 0x5771202a), X64Word_create(0x106aa070, 0x32bbd1b8), - X64Word_create(0x19a4c116, 0xb8d2d0c8), X64Word_create(0x1e376c08, 0x5141ab53), - X64Word_create(0x2748774c, 0xdf8eeb99), X64Word_create(0x34b0bcb5, 0xe19b48a8), - X64Word_create(0x391c0cb3, 0xc5c95a63), X64Word_create(0x4ed8aa4a, 0xe3418acb), - X64Word_create(0x5b9cca4f, 0x7763e373), X64Word_create(0x682e6ff3, 0xd6b2b8a3), - X64Word_create(0x748f82ee, 0x5defb2fc), X64Word_create(0x78a5636f, 0x43172f60), - X64Word_create(0x84c87814, 0xa1f0ab72), X64Word_create(0x8cc70208, 0x1a6439ec), - X64Word_create(0x90befffa, 0x23631e28), X64Word_create(0xa4506ceb, 0xde82bde9), - X64Word_create(0xbef9a3f7, 0xb2c67915), X64Word_create(0xc67178f2, 0xe372532b), - X64Word_create(0xca273ece, 0xea26619c), X64Word_create(0xd186b8c7, 0x21c0c207), - X64Word_create(0xeada7dd6, 0xcde0eb1e), X64Word_create(0xf57d4f7f, 0xee6ed178), - X64Word_create(0x06f067aa, 0x72176fba), X64Word_create(0x0a637dc5, 0xa2c898a6), - X64Word_create(0x113f9804, 0xbef90dae), X64Word_create(0x1b710b35, 0x131c471b), - X64Word_create(0x28db77f5, 0x23047d84), X64Word_create(0x32caab7b, 0x40c72493), - X64Word_create(0x3c9ebe0a, 0x15c9bebc), X64Word_create(0x431d67c4, 0x9c100d4c), - X64Word_create(0x4cc5d4be, 0xcb3e42b6), X64Word_create(0x597f299c, 0xfc657e2a), - X64Word_create(0x5fcb6fab, 0x3ad6faec), X64Word_create(0x6c44198c, 0x4a475817) - ]; - - // Reusable objects - var W = []; - (function () { - for (var i = 0; i < 80; i++) { - W[i] = X64Word_create(); - } - }()); - - /** - * SHA-512 hash algorithm. - */ - var SHA512 = C_algo.SHA512 = Hasher.extend({ - _doReset: function () { - this._hash = new X64WordArray.init([ - new X64Word.init(0x6a09e667, 0xf3bcc908), new X64Word.init(0xbb67ae85, 0x84caa73b), - new X64Word.init(0x3c6ef372, 0xfe94f82b), new X64Word.init(0xa54ff53a, 0x5f1d36f1), - new X64Word.init(0x510e527f, 0xade682d1), new X64Word.init(0x9b05688c, 0x2b3e6c1f), - new X64Word.init(0x1f83d9ab, 0xfb41bd6b), new X64Word.init(0x5be0cd19, 0x137e2179) - ]); - }, - - _doProcessBlock: function (M, offset) { - // Shortcuts - var H = this._hash.words; - - var H0 = H[0]; - var H1 = H[1]; - var H2 = H[2]; - var H3 = H[3]; - var H4 = H[4]; - var H5 = H[5]; - var H6 = H[6]; - var H7 = H[7]; - - var H0h = H0.high; - var H0l = H0.low; - var H1h = H1.high; - var H1l = H1.low; - var H2h = H2.high; - var H2l = H2.low; - var H3h = H3.high; - var H3l = H3.low; - var H4h = H4.high; - var H4l = H4.low; - var H5h = H5.high; - var H5l = H5.low; - var H6h = H6.high; - var H6l = H6.low; - var H7h = H7.high; - var H7l = H7.low; - - // Working variables - var ah = H0h; - var al = H0l; - var bh = H1h; - var bl = H1l; - var ch = H2h; - var cl = H2l; - var dh = H3h; - var dl = H3l; - var eh = H4h; - var el = H4l; - var fh = H5h; - var fl = H5l; - var gh = H6h; - var gl = H6l; - var hh = H7h; - var hl = H7l; - - // Rounds - for (var i = 0; i < 80; i++) { - // Shortcut - var Wi = W[i]; - - // Extend message - if (i < 16) { - var Wih = Wi.high = M[offset + i * 2] | 0; - var Wil = Wi.low = M[offset + i * 2 + 1] | 0; - } else { - // Gamma0 - var gamma0x = W[i - 15]; - var gamma0xh = gamma0x.high; - var gamma0xl = gamma0x.low; - var gamma0h = ((gamma0xh >>> 1) | (gamma0xl << 31)) ^ ((gamma0xh >>> 8) | (gamma0xl << 24)) ^ (gamma0xh >>> 7); - var gamma0l = ((gamma0xl >>> 1) | (gamma0xh << 31)) ^ ((gamma0xl >>> 8) | (gamma0xh << 24)) ^ ((gamma0xl >>> 7) | (gamma0xh << 25)); - - // Gamma1 - var gamma1x = W[i - 2]; - var gamma1xh = gamma1x.high; - var gamma1xl = gamma1x.low; - var gamma1h = ((gamma1xh >>> 19) | (gamma1xl << 13)) ^ ((gamma1xh << 3) | (gamma1xl >>> 29)) ^ (gamma1xh >>> 6); - var gamma1l = ((gamma1xl >>> 19) | (gamma1xh << 13)) ^ ((gamma1xl << 3) | (gamma1xh >>> 29)) ^ ((gamma1xl >>> 6) | (gamma1xh << 26)); - - // W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16] - var Wi7 = W[i - 7]; - var Wi7h = Wi7.high; - var Wi7l = Wi7.low; - - var Wi16 = W[i - 16]; - var Wi16h = Wi16.high; - var Wi16l = Wi16.low; - - var Wil = gamma0l + Wi7l; - var Wih = gamma0h + Wi7h + ((Wil >>> 0) < (gamma0l >>> 0) ? 1 : 0); - var Wil = Wil + gamma1l; - var Wih = Wih + gamma1h + ((Wil >>> 0) < (gamma1l >>> 0) ? 1 : 0); - var Wil = Wil + Wi16l; - var Wih = Wih + Wi16h + ((Wil >>> 0) < (Wi16l >>> 0) ? 1 : 0); - - Wi.high = Wih; - Wi.low = Wil; - } - - var chh = (eh & fh) ^ (~eh & gh); - var chl = (el & fl) ^ (~el & gl); - var majh = (ah & bh) ^ (ah & ch) ^ (bh & ch); - var majl = (al & bl) ^ (al & cl) ^ (bl & cl); - - var sigma0h = ((ah >>> 28) | (al << 4)) ^ ((ah << 30) | (al >>> 2)) ^ ((ah << 25) | (al >>> 7)); - var sigma0l = ((al >>> 28) | (ah << 4)) ^ ((al << 30) | (ah >>> 2)) ^ ((al << 25) | (ah >>> 7)); - var sigma1h = ((eh >>> 14) | (el << 18)) ^ ((eh >>> 18) | (el << 14)) ^ ((eh << 23) | (el >>> 9)); - var sigma1l = ((el >>> 14) | (eh << 18)) ^ ((el >>> 18) | (eh << 14)) ^ ((el << 23) | (eh >>> 9)); - - // t1 = h + sigma1 + ch + K[i] + W[i] - var Ki = K[i]; - var Kih = Ki.high; - var Kil = Ki.low; - - var t1l = hl + sigma1l; - var t1h = hh + sigma1h + ((t1l >>> 0) < (hl >>> 0) ? 1 : 0); - var t1l = t1l + chl; - var t1h = t1h + chh + ((t1l >>> 0) < (chl >>> 0) ? 1 : 0); - var t1l = t1l + Kil; - var t1h = t1h + Kih + ((t1l >>> 0) < (Kil >>> 0) ? 1 : 0); - var t1l = t1l + Wil; - var t1h = t1h + Wih + ((t1l >>> 0) < (Wil >>> 0) ? 1 : 0); - - // t2 = sigma0 + maj - var t2l = sigma0l + majl; - var t2h = sigma0h + majh + ((t2l >>> 0) < (sigma0l >>> 0) ? 1 : 0); - - // Update working variables - hh = gh; - hl = gl; - gh = fh; - gl = fl; - fh = eh; - fl = el; - el = (dl + t1l) | 0; - eh = (dh + t1h + ((el >>> 0) < (dl >>> 0) ? 1 : 0)) | 0; - dh = ch; - dl = cl; - ch = bh; - cl = bl; - bh = ah; - bl = al; - al = (t1l + t2l) | 0; - ah = (t1h + t2h + ((al >>> 0) < (t1l >>> 0) ? 1 : 0)) | 0; - } - - // Intermediate hash value - H0l = H0.low = (H0l + al); - H0.high = (H0h + ah + ((H0l >>> 0) < (al >>> 0) ? 1 : 0)); - H1l = H1.low = (H1l + bl); - H1.high = (H1h + bh + ((H1l >>> 0) < (bl >>> 0) ? 1 : 0)); - H2l = H2.low = (H2l + cl); - H2.high = (H2h + ch + ((H2l >>> 0) < (cl >>> 0) ? 1 : 0)); - H3l = H3.low = (H3l + dl); - H3.high = (H3h + dh + ((H3l >>> 0) < (dl >>> 0) ? 1 : 0)); - H4l = H4.low = (H4l + el); - H4.high = (H4h + eh + ((H4l >>> 0) < (el >>> 0) ? 1 : 0)); - H5l = H5.low = (H5l + fl); - H5.high = (H5h + fh + ((H5l >>> 0) < (fl >>> 0) ? 1 : 0)); - H6l = H6.low = (H6l + gl); - H6.high = (H6h + gh + ((H6l >>> 0) < (gl >>> 0) ? 1 : 0)); - H7l = H7.low = (H7l + hl); - H7.high = (H7h + hh + ((H7l >>> 0) < (hl >>> 0) ? 1 : 0)); - }, - - _doFinalize: function () { - // Shortcuts - var data = this._data; - var dataWords = data.words; - - var nBitsTotal = this._nDataBytes * 8; - var nBitsLeft = data.sigBytes * 8; - - // Add padding - dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); - dataWords[(((nBitsLeft + 128) >>> 10) << 5) + 30] = Math.floor(nBitsTotal / 0x100000000); - dataWords[(((nBitsLeft + 128) >>> 10) << 5) + 31] = nBitsTotal; - data.sigBytes = dataWords.length * 4; - - // Hash final blocks - this._process(); - - // Convert hash to 32-bit word array before returning - var hash = this._hash.toX32(); - - // Return final computed hash - return hash; - }, - - clone: function () { - var clone = Hasher.clone.call(this); - clone._hash = this._hash.clone(); - - return clone; - }, - - blockSize: 1024/32 - }); - - /** - * Shortcut function to the hasher's object interface. - * - * @param {WordArray|string} message The message to hash. - * - * @return {WordArray} The hash. - * - * @static - * - * @example - * - * var hash = CryptoJS.SHA512('message'); - * var hash = CryptoJS.SHA512(wordArray); - */ - C.SHA512 = Hasher._createHelper(SHA512); - - /** - * Shortcut function to the HMAC's object interface. - * - * @param {WordArray|string} message The message to hash. - * @param {WordArray|string} key The secret key. - * - * @return {WordArray} The HMAC. - * - * @static - * - * @example - * - * var hmac = CryptoJS.HmacSHA512(message, key); - */ - C.HmacSHA512 = Hasher._createHmacHelper(SHA512); -}()); - - -/*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/ - */ -var b64map="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -var b64pad="="; - -function hex2b64(h) { - var i; - var c; - var ret = ""; - for(i = 0; i+3 <= h.length; i+=3) { - c = parseInt(h.substring(i,i+3),16); - ret += b64map.charAt(c >> 6) + b64map.charAt(c & 63); - } - if(i+1 == h.length) { - c = parseInt(h.substring(i,i+1),16); - ret += b64map.charAt(c << 2); - } - else if(i+2 == h.length) { - c = parseInt(h.substring(i,i+2),16); - ret += b64map.charAt(c >> 2) + b64map.charAt((c & 3) << 4); - } - if (b64pad) while((ret.length & 3) > 0) ret += b64pad; - return ret; -} - -// convert a base64 string to hex -function b64tohex(s) { - var ret = "" - var i; - var k = 0; // b64 state, 0-3 - var slop; - var v; - for(i = 0; i < s.length; ++i) { - if(s.charAt(i) == b64pad) break; - v = b64map.indexOf(s.charAt(i)); - if(v < 0) continue; - if(k == 0) { - ret += int2char(v >> 2); - slop = v & 3; - k = 1; - } - else if(k == 1) { - ret += int2char((slop << 2) | (v >> 4)); - slop = v & 0xf; - k = 2; - } - else if(k == 2) { - ret += int2char(slop); - ret += int2char(v >> 2); - slop = v & 3; - k = 3; - } - else { - ret += int2char((slop << 2) | (v >> 4)); - ret += int2char(v & 0xf); - k = 0; - } - } - if(k == 1) - ret += int2char(slop << 2); - return ret; -} - -// convert a base64 string to a byte/number array -function b64toBA(s) { - //piggyback on b64tohex for now, optimize later - var h = b64tohex(s); - var i; - var a = new Array(); - for(i = 0; 2*i < h.length; ++i) { - a[i] = parseInt(h.substring(2*i,2*i+2),16); - } - return a; -} -/*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/ - */ -// Copyright (c) 2005 Tom Wu -// All Rights Reserved. -// See "LICENSE" for details. - -// Basic JavaScript BN library - subset useful for RSA encryption. - -// Bits per digit -var dbits; - -// JavaScript engine analysis -var canary = 0xdeadbeefcafe; -var j_lm = ((canary&0xffffff)==0xefcafe); - -// (public) Constructor -function BigInteger(a,b,c) { - if(a != null) - if("number" == typeof a) this.fromNumber(a,b,c); - else if(b == null && "string" != typeof a) this.fromString(a,256); - else this.fromString(a,b); -} - -// return new, unset BigInteger -function nbi() { return new BigInteger(null); } - -// am: Compute w_j += (x*this_i), propagate carries, -// c is initial carry, returns final carry. -// c < 3*dvalue, x < 2*dvalue, this_i < dvalue -// We need to select the fastest one that works in this environment. - -// am1: use a single mult and divide to get the high bits, -// max digit bits should be 26 because -// max internal value = 2*dvalue^2-2*dvalue (< 2^53) -function am1(i,x,w,j,c,n) { - while(--n >= 0) { - var v = x*this[i++]+w[j]+c; - c = Math.floor(v/0x4000000); - w[j++] = v&0x3ffffff; - } - return c; -} -// am2 avoids a big mult-and-extract completely. -// Max digit bits should be <= 30 because we do bitwise ops -// on values up to 2*hdvalue^2-hdvalue-1 (< 2^31) -function am2(i,x,w,j,c,n) { - var xl = x&0x7fff, xh = x>>15; - while(--n >= 0) { - var l = this[i]&0x7fff; - var h = this[i++]>>15; - var m = xh*l+h*xl; - l = xl*l+((m&0x7fff)<<15)+w[j]+(c&0x3fffffff); - c = (l>>>30)+(m>>>15)+xh*h+(c>>>30); - w[j++] = l&0x3fffffff; - } - return c; -} -// Alternately, set max digit bits to 28 since some -// browsers slow down when dealing with 32-bit numbers. -function am3(i,x,w,j,c,n) { - var xl = x&0x3fff, xh = x>>14; - while(--n >= 0) { - var l = this[i]&0x3fff; - var h = this[i++]>>14; - var m = xh*l+h*xl; - l = xl*l+((m&0x3fff)<<14)+w[j]+c; - c = (l>>28)+(m>>14)+xh*h; - w[j++] = l&0xfffffff; - } - return c; -} -if(j_lm && (navigator.appName == "Microsoft Internet Explorer")) { - BigInteger.prototype.am = am2; - dbits = 30; -} -else if(j_lm && (navigator.appName != "Netscape")) { - BigInteger.prototype.am = am1; - dbits = 26; -} -else { // Mozilla/Netscape seems to prefer am3 - BigInteger.prototype.am = am3; - dbits = 28; -} - -BigInteger.prototype.DB = dbits; -BigInteger.prototype.DM = ((1<= 0; --i) r[i] = this[i]; - r.t = this.t; - r.s = this.s; -} - -// (protected) set from integer value x, -DV <= x < DV -function bnpFromInt(x) { - this.t = 1; - this.s = (x<0)?-1:0; - if(x > 0) this[0] = x; - else if(x < -1) this[0] = x+this.DV; - else this.t = 0; -} - -// return bigint initialized to value -function nbv(i) { var r = nbi(); r.fromInt(i); return r; } - -// (protected) set from string and radix -function bnpFromString(s,b) { - var k; - if(b == 16) k = 4; - else if(b == 8) k = 3; - else if(b == 256) k = 8; // byte array - else if(b == 2) k = 1; - else if(b == 32) k = 5; - else if(b == 4) k = 2; - else { this.fromRadix(s,b); return; } - this.t = 0; - this.s = 0; - var i = s.length, mi = false, sh = 0; - while(--i >= 0) { - var x = (k==8)?s[i]&0xff:intAt(s,i); - if(x < 0) { - if(s.charAt(i) == "-") mi = true; - continue; - } - mi = false; - if(sh == 0) - this[this.t++] = x; - else if(sh+k > this.DB) { - this[this.t-1] |= (x&((1<<(this.DB-sh))-1))<>(this.DB-sh)); - } - else - this[this.t-1] |= x<= this.DB) sh -= this.DB; - } - if(k == 8 && (s[0]&0x80) != 0) { - this.s = -1; - if(sh > 0) this[this.t-1] |= ((1<<(this.DB-sh))-1)< 0 && this[this.t-1] == c) --this.t; -} - -// (public) return string representation in given radix -function bnToString(b) { - if(this.s < 0) return "-"+this.negate().toString(b); - var k; - if(b == 16) k = 4; - else if(b == 8) k = 3; - else if(b == 2) k = 1; - else if(b == 32) k = 5; - else if(b == 4) k = 2; - else return this.toRadix(b); - var km = (1< 0) { - if(p < this.DB && (d = this[i]>>p) > 0) { m = true; r = int2char(d); } - while(i >= 0) { - if(p < k) { - d = (this[i]&((1<>(p+=this.DB-k); - } - else { - d = (this[i]>>(p-=k))&km; - if(p <= 0) { p += this.DB; --i; } - } - if(d > 0) m = true; - if(m) r += int2char(d); - } - } - return m?r:"0"; -} - -// (public) -this -function bnNegate() { var r = nbi(); BigInteger.ZERO.subTo(this,r); return r; } - -// (public) |this| -function bnAbs() { return (this.s<0)?this.negate():this; } - -// (public) return + if this > a, - if this < a, 0 if equal -function bnCompareTo(a) { - var r = this.s-a.s; - if(r != 0) return r; - var i = this.t; - r = i-a.t; - if(r != 0) return (this.s<0)?-r:r; - while(--i >= 0) if((r=this[i]-a[i]) != 0) return r; - return 0; -} - -// returns bit length of the integer x -function nbits(x) { - var r = 1, t; - if((t=x>>>16) != 0) { x = t; r += 16; } - if((t=x>>8) != 0) { x = t; r += 8; } - if((t=x>>4) != 0) { x = t; r += 4; } - if((t=x>>2) != 0) { x = t; r += 2; } - if((t=x>>1) != 0) { x = t; r += 1; } - return r; -} - -// (public) return the number of bits in "this" -function bnBitLength() { - if(this.t <= 0) return 0; - return this.DB*(this.t-1)+nbits(this[this.t-1]^(this.s&this.DM)); -} - -// (protected) r = this << n*DB -function bnpDLShiftTo(n,r) { - var i; - for(i = this.t-1; i >= 0; --i) r[i+n] = this[i]; - for(i = n-1; i >= 0; --i) r[i] = 0; - r.t = this.t+n; - r.s = this.s; -} - -// (protected) r = this >> n*DB -function bnpDRShiftTo(n,r) { - for(var i = n; i < this.t; ++i) r[i-n] = this[i]; - r.t = Math.max(this.t-n,0); - r.s = this.s; -} - -// (protected) r = this << n -function bnpLShiftTo(n,r) { - var bs = n%this.DB; - var cbs = this.DB-bs; - var bm = (1<= 0; --i) { - r[i+ds+1] = (this[i]>>cbs)|c; - c = (this[i]&bm)<= 0; --i) r[i] = 0; - r[ds] = c; - r.t = this.t+ds+1; - r.s = this.s; - r.clamp(); -} - -// (protected) r = this >> n -function bnpRShiftTo(n,r) { - r.s = this.s; - var ds = Math.floor(n/this.DB); - if(ds >= this.t) { r.t = 0; return; } - var bs = n%this.DB; - var cbs = this.DB-bs; - var bm = (1<>bs; - for(var i = ds+1; i < this.t; ++i) { - r[i-ds-1] |= (this[i]&bm)<>bs; - } - if(bs > 0) r[this.t-ds-1] |= (this.s&bm)<>= this.DB; - } - if(a.t < this.t) { - c -= a.s; - while(i < this.t) { - c += this[i]; - r[i++] = c&this.DM; - c >>= this.DB; - } - c += this.s; - } - else { - c += this.s; - while(i < a.t) { - c -= a[i]; - r[i++] = c&this.DM; - c >>= this.DB; - } - c -= a.s; - } - r.s = (c<0)?-1:0; - if(c < -1) r[i++] = this.DV+c; - else if(c > 0) r[i++] = c; - r.t = i; - r.clamp(); -} - -// (protected) r = this * a, r != this,a (HAC 14.12) -// "this" should be the larger one if appropriate. -function bnpMultiplyTo(a,r) { - var x = this.abs(), y = a.abs(); - var i = x.t; - r.t = i+y.t; - while(--i >= 0) r[i] = 0; - for(i = 0; i < y.t; ++i) r[i+x.t] = x.am(0,y[i],r,i,0,x.t); - r.s = 0; - r.clamp(); - if(this.s != a.s) BigInteger.ZERO.subTo(r,r); -} - -// (protected) r = this^2, r != this (HAC 14.16) -function bnpSquareTo(r) { - var x = this.abs(); - var i = r.t = 2*x.t; - while(--i >= 0) r[i] = 0; - for(i = 0; i < x.t-1; ++i) { - var c = x.am(i,x[i],r,2*i,0,1); - if((r[i+x.t]+=x.am(i+1,2*x[i],r,2*i+1,c,x.t-i-1)) >= x.DV) { - r[i+x.t] -= x.DV; - r[i+x.t+1] = 1; - } - } - if(r.t > 0) r[r.t-1] += x.am(i,x[i],r,2*i,0,1); - r.s = 0; - r.clamp(); -} - -// (protected) divide this by m, quotient and remainder to q, r (HAC 14.20) -// r != q, this != m. q or r may be null. -function bnpDivRemTo(m,q,r) { - var pm = m.abs(); - if(pm.t <= 0) return; - var pt = this.abs(); - if(pt.t < pm.t) { - if(q != null) q.fromInt(0); - if(r != null) this.copyTo(r); - return; - } - if(r == null) r = nbi(); - var y = nbi(), ts = this.s, ms = m.s; - var nsh = this.DB-nbits(pm[pm.t-1]); // normalize modulus - if(nsh > 0) { pm.lShiftTo(nsh,y); pt.lShiftTo(nsh,r); } - else { pm.copyTo(y); pt.copyTo(r); } - var ys = y.t; - var y0 = y[ys-1]; - if(y0 == 0) return; - var yt = y0*(1<1)?y[ys-2]>>this.F2:0); - var d1 = this.FV/yt, d2 = (1<= 0) { - r[r.t++] = 1; - r.subTo(t,r); - } - BigInteger.ONE.dlShiftTo(ys,t); - t.subTo(y,y); // "negative" y so we can replace sub with am later - while(y.t < ys) y[y.t++] = 0; - while(--j >= 0) { - // Estimate quotient digit - var qd = (r[--i]==y0)?this.DM:Math.floor(r[i]*d1+(r[i-1]+e)*d2); - if((r[i]+=y.am(0,qd,r,j,0,ys)) < qd) { // Try it out - y.dlShiftTo(j,t); - r.subTo(t,r); - while(r[i] < --qd) r.subTo(t,r); - } - } - if(q != null) { - r.drShiftTo(ys,q); - if(ts != ms) BigInteger.ZERO.subTo(q,q); - } - r.t = ys; - r.clamp(); - if(nsh > 0) r.rShiftTo(nsh,r); // Denormalize remainder - if(ts < 0) BigInteger.ZERO.subTo(r,r); -} - -// (public) this mod a -function bnMod(a) { - var r = nbi(); - this.abs().divRemTo(a,null,r); - if(this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r,r); - return r; -} - -// Modular reduction using "classic" algorithm -function Classic(m) { this.m = m; } -function cConvert(x) { - if(x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m); - else return x; -} -function cRevert(x) { return x; } -function cReduce(x) { x.divRemTo(this.m,null,x); } -function cMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } -function cSqrTo(x,r) { x.squareTo(r); this.reduce(r); } - -Classic.prototype.convert = cConvert; -Classic.prototype.revert = cRevert; -Classic.prototype.reduce = cReduce; -Classic.prototype.mulTo = cMulTo; -Classic.prototype.sqrTo = cSqrTo; - -// (protected) return "-1/this % 2^DB"; useful for Mont. reduction -// justification: -// xy == 1 (mod m) -// xy = 1+km -// xy(2-xy) = (1+km)(1-km) -// x[y(2-xy)] = 1-k^2m^2 -// x[y(2-xy)] == 1 (mod m^2) -// if y is 1/x mod m, then y(2-xy) is 1/x mod m^2 -// should reduce x and y(2-xy) by m^2 at each step to keep size bounded. -// JS multiply "overflows" differently from C/C++, so care is needed here. -function bnpInvDigit() { - if(this.t < 1) return 0; - var x = this[0]; - if((x&1) == 0) return 0; - var y = x&3; // y == 1/x mod 2^2 - y = (y*(2-(x&0xf)*y))&0xf; // y == 1/x mod 2^4 - y = (y*(2-(x&0xff)*y))&0xff; // y == 1/x mod 2^8 - y = (y*(2-(((x&0xffff)*y)&0xffff)))&0xffff; // y == 1/x mod 2^16 - // last step - calculate inverse mod DV directly; - // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints - y = (y*(2-x*y%this.DV))%this.DV; // y == 1/x mod 2^dbits - // we really want the negative inverse, and -DV < y < DV - return (y>0)?this.DV-y:-y; -} - -// Montgomery reduction -function Montgomery(m) { - this.m = m; - this.mp = m.invDigit(); - this.mpl = this.mp&0x7fff; - this.mph = this.mp>>15; - this.um = (1<<(m.DB-15))-1; - this.mt2 = 2*m.t; -} - -// xR mod m -function montConvert(x) { - var r = nbi(); - x.abs().dlShiftTo(this.m.t,r); - r.divRemTo(this.m,null,r); - if(x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r,r); - return r; -} - -// x/R mod m -function montRevert(x) { - var r = nbi(); - x.copyTo(r); - this.reduce(r); - return r; -} - -// x = x/R mod m (HAC 14.32) -function montReduce(x) { - while(x.t <= this.mt2) // pad x so am has enough room later - x[x.t++] = 0; - for(var i = 0; i < this.m.t; ++i) { - // faster way of calculating u0 = x[i]*mp mod DV - var j = x[i]&0x7fff; - var u0 = (j*this.mpl+(((j*this.mph+(x[i]>>15)*this.mpl)&this.um)<<15))&x.DM; - // use am to combine the multiply-shift-add into one call - j = i+this.m.t; - x[j] += this.m.am(0,u0,x,i,0,this.m.t); - // propagate carry - while(x[j] >= x.DV) { x[j] -= x.DV; x[++j]++; } - } - x.clamp(); - x.drShiftTo(this.m.t,x); - if(x.compareTo(this.m) >= 0) x.subTo(this.m,x); -} - -// r = "x^2/R mod m"; x != r -function montSqrTo(x,r) { x.squareTo(r); this.reduce(r); } - -// r = "xy/R mod m"; x,y != r -function montMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } - -Montgomery.prototype.convert = montConvert; -Montgomery.prototype.revert = montRevert; -Montgomery.prototype.reduce = montReduce; -Montgomery.prototype.mulTo = montMulTo; -Montgomery.prototype.sqrTo = montSqrTo; - -// (protected) true iff this is even -function bnpIsEven() { return ((this.t>0)?(this[0]&1):this.s) == 0; } - -// (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79) -function bnpExp(e,z) { - if(e > 0xffffffff || e < 1) return BigInteger.ONE; - var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e)-1; - g.copyTo(r); - while(--i >= 0) { - z.sqrTo(r,r2); - if((e&(1< 0) z.mulTo(r2,g,r); - else { var t = r; r = r2; r2 = t; } - } - return z.revert(r); -} - -// (public) this^e % m, 0 <= e < 2^32 -function bnModPowInt(e,m) { - var z; - if(e < 256 || m.isEven()) z = new Classic(m); else z = new Montgomery(m); - return this.exp(e,z); -} - -// protected -BigInteger.prototype.copyTo = bnpCopyTo; -BigInteger.prototype.fromInt = bnpFromInt; -BigInteger.prototype.fromString = bnpFromString; -BigInteger.prototype.clamp = bnpClamp; -BigInteger.prototype.dlShiftTo = bnpDLShiftTo; -BigInteger.prototype.drShiftTo = bnpDRShiftTo; -BigInteger.prototype.lShiftTo = bnpLShiftTo; -BigInteger.prototype.rShiftTo = bnpRShiftTo; -BigInteger.prototype.subTo = bnpSubTo; -BigInteger.prototype.multiplyTo = bnpMultiplyTo; -BigInteger.prototype.squareTo = bnpSquareTo; -BigInteger.prototype.divRemTo = bnpDivRemTo; -BigInteger.prototype.invDigit = bnpInvDigit; -BigInteger.prototype.isEven = bnpIsEven; -BigInteger.prototype.exp = bnpExp; - -// public -BigInteger.prototype.toString = bnToString; -BigInteger.prototype.negate = bnNegate; -BigInteger.prototype.abs = bnAbs; -BigInteger.prototype.compareTo = bnCompareTo; -BigInteger.prototype.bitLength = bnBitLength; -BigInteger.prototype.mod = bnMod; -BigInteger.prototype.modPowInt = bnModPowInt; - -// "constants" -BigInteger.ZERO = nbv(0); -BigInteger.ONE = nbv(1); -/*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/ - */ -// Copyright (c) 2005-2009 Tom Wu -// All Rights Reserved. -// See "LICENSE" for details. - -// Extended JavaScript BN functions, required for RSA private ops. - -// Version 1.1: new BigInteger("0", 10) returns "proper" zero -// Version 1.2: square() API, isProbablePrime fix - -// (public) -function bnClone() { var r = nbi(); this.copyTo(r); return r; } - -// (public) return value as integer -function bnIntValue() { - if(this.s < 0) { - if(this.t == 1) return this[0]-this.DV; - else if(this.t == 0) return -1; - } - else if(this.t == 1) return this[0]; - else if(this.t == 0) return 0; - // assumes 16 < DB < 32 - return ((this[1]&((1<<(32-this.DB))-1))<>24; } - -// (public) return value as short (assumes DB>=16) -function bnShortValue() { return (this.t==0)?this.s:(this[0]<<16)>>16; } - -// (protected) return x s.t. r^x < DV -function bnpChunkSize(r) { return Math.floor(Math.LN2*this.DB/Math.log(r)); } - -// (public) 0 if this == 0, 1 if this > 0 -function bnSigNum() { - if(this.s < 0) return -1; - else if(this.t <= 0 || (this.t == 1 && this[0] <= 0)) return 0; - else return 1; -} - -// (protected) convert to radix string -function bnpToRadix(b) { - if(b == null) b = 10; - if(this.signum() == 0 || b < 2 || b > 36) return "0"; - var cs = this.chunkSize(b); - var a = Math.pow(b,cs); - var d = nbv(a), y = nbi(), z = nbi(), r = ""; - this.divRemTo(d,y,z); - while(y.signum() > 0) { - r = (a+z.intValue()).toString(b).substr(1) + r; - y.divRemTo(d,y,z); - } - return z.intValue().toString(b) + r; -} - -// (protected) convert from radix string -function bnpFromRadix(s,b) { - this.fromInt(0); - if(b == null) b = 10; - var cs = this.chunkSize(b); - var d = Math.pow(b,cs), mi = false, j = 0, w = 0; - for(var i = 0; i < s.length; ++i) { - var x = intAt(s,i); - if(x < 0) { - if(s.charAt(i) == "-" && this.signum() == 0) mi = true; - continue; - } - w = b*w+x; - if(++j >= cs) { - this.dMultiply(d); - this.dAddOffset(w,0); - j = 0; - w = 0; - } - } - if(j > 0) { - this.dMultiply(Math.pow(b,j)); - this.dAddOffset(w,0); - } - if(mi) BigInteger.ZERO.subTo(this,this); -} - -// (protected) alternate constructor -function bnpFromNumber(a,b,c) { - if("number" == typeof b) { - // new BigInteger(int,int,RNG) - if(a < 2) this.fromInt(1); - else { - this.fromNumber(a,c); - if(!this.testBit(a-1)) // force MSB set - this.bitwiseTo(BigInteger.ONE.shiftLeft(a-1),op_or,this); - if(this.isEven()) this.dAddOffset(1,0); // force odd - while(!this.isProbablePrime(b)) { - this.dAddOffset(2,0); - if(this.bitLength() > a) this.subTo(BigInteger.ONE.shiftLeft(a-1),this); - } - } - } - else { - // new BigInteger(int,RNG) - var x = new Array(), t = a&7; - x.length = (a>>3)+1; - b.nextBytes(x); - if(t > 0) x[0] &= ((1< 0) { - if(p < this.DB && (d = this[i]>>p) != (this.s&this.DM)>>p) - r[k++] = d|(this.s<<(this.DB-p)); - while(i >= 0) { - if(p < 8) { - d = (this[i]&((1<>(p+=this.DB-8); - } - else { - d = (this[i]>>(p-=8))&0xff; - if(p <= 0) { p += this.DB; --i; } - } - if((d&0x80) != 0) d |= -256; - if(k == 0 && (this.s&0x80) != (d&0x80)) ++k; - if(k > 0 || d != this.s) r[k++] = d; - } - } - return r; -} - -function bnEquals(a) { return(this.compareTo(a)==0); } -function bnMin(a) { return(this.compareTo(a)<0)?this:a; } -function bnMax(a) { return(this.compareTo(a)>0)?this:a; } - -// (protected) r = this op a (bitwise) -function bnpBitwiseTo(a,op,r) { - var i, f, m = Math.min(a.t,this.t); - for(i = 0; i < m; ++i) r[i] = op(this[i],a[i]); - if(a.t < this.t) { - f = a.s&this.DM; - for(i = m; i < this.t; ++i) r[i] = op(this[i],f); - r.t = this.t; - } - else { - f = this.s&this.DM; - for(i = m; i < a.t; ++i) r[i] = op(f,a[i]); - r.t = a.t; - } - r.s = op(this.s,a.s); - r.clamp(); -} - -// (public) this & a -function op_and(x,y) { return x&y; } -function bnAnd(a) { var r = nbi(); this.bitwiseTo(a,op_and,r); return r; } - -// (public) this | a -function op_or(x,y) { return x|y; } -function bnOr(a) { var r = nbi(); this.bitwiseTo(a,op_or,r); return r; } - -// (public) this ^ a -function op_xor(x,y) { return x^y; } -function bnXor(a) { var r = nbi(); this.bitwiseTo(a,op_xor,r); return r; } - -// (public) this & ~a -function op_andnot(x,y) { return x&~y; } -function bnAndNot(a) { var r = nbi(); this.bitwiseTo(a,op_andnot,r); return r; } - -// (public) ~this -function bnNot() { - var r = nbi(); - for(var i = 0; i < this.t; ++i) r[i] = this.DM&~this[i]; - r.t = this.t; - r.s = ~this.s; - return r; -} - -// (public) this << n -function bnShiftLeft(n) { - var r = nbi(); - if(n < 0) this.rShiftTo(-n,r); else this.lShiftTo(n,r); - return r; -} - -// (public) this >> n -function bnShiftRight(n) { - var r = nbi(); - if(n < 0) this.lShiftTo(-n,r); else this.rShiftTo(n,r); - return r; -} - -// return index of lowest 1-bit in x, x < 2^31 -function lbit(x) { - if(x == 0) return -1; - var r = 0; - if((x&0xffff) == 0) { x >>= 16; r += 16; } - if((x&0xff) == 0) { x >>= 8; r += 8; } - if((x&0xf) == 0) { x >>= 4; r += 4; } - if((x&3) == 0) { x >>= 2; r += 2; } - if((x&1) == 0) ++r; - return r; -} - -// (public) returns index of lowest 1-bit (or -1 if none) -function bnGetLowestSetBit() { - for(var i = 0; i < this.t; ++i) - if(this[i] != 0) return i*this.DB+lbit(this[i]); - if(this.s < 0) return this.t*this.DB; - return -1; -} - -// return number of 1 bits in x -function cbit(x) { - var r = 0; - while(x != 0) { x &= x-1; ++r; } - return r; -} - -// (public) return number of set bits -function bnBitCount() { - var r = 0, x = this.s&this.DM; - for(var i = 0; i < this.t; ++i) r += cbit(this[i]^x); - return r; -} - -// (public) true iff nth bit is set -function bnTestBit(n) { - var j = Math.floor(n/this.DB); - if(j >= this.t) return(this.s!=0); - return((this[j]&(1<<(n%this.DB)))!=0); -} - -// (protected) this op (1<>= this.DB; - } - if(a.t < this.t) { - c += a.s; - while(i < this.t) { - c += this[i]; - r[i++] = c&this.DM; - c >>= this.DB; - } - c += this.s; - } - else { - c += this.s; - while(i < a.t) { - c += a[i]; - r[i++] = c&this.DM; - c >>= this.DB; - } - c += a.s; - } - r.s = (c<0)?-1:0; - if(c > 0) r[i++] = c; - else if(c < -1) r[i++] = this.DV+c; - r.t = i; - r.clamp(); -} - -// (public) this + a -function bnAdd(a) { var r = nbi(); this.addTo(a,r); return r; } - -// (public) this - a -function bnSubtract(a) { var r = nbi(); this.subTo(a,r); return r; } - -// (public) this * a -function bnMultiply(a) { var r = nbi(); this.multiplyTo(a,r); return r; } - -// (public) this^2 -function bnSquare() { var r = nbi(); this.squareTo(r); return r; } - -// (public) this / a -function bnDivide(a) { var r = nbi(); this.divRemTo(a,r,null); return r; } - -// (public) this % a -function bnRemainder(a) { var r = nbi(); this.divRemTo(a,null,r); return r; } - -// (public) [this/a,this%a] -function bnDivideAndRemainder(a) { - var q = nbi(), r = nbi(); - this.divRemTo(a,q,r); - return new Array(q,r); -} - -// (protected) this *= n, this >= 0, 1 < n < DV -function bnpDMultiply(n) { - this[this.t] = this.am(0,n-1,this,0,0,this.t); - ++this.t; - this.clamp(); -} - -// (protected) this += n << w words, this >= 0 -function bnpDAddOffset(n,w) { - if(n == 0) return; - while(this.t <= w) this[this.t++] = 0; - this[w] += n; - while(this[w] >= this.DV) { - this[w] -= this.DV; - if(++w >= this.t) this[this.t++] = 0; - ++this[w]; - } -} - -// A "null" reducer -function NullExp() {} -function nNop(x) { return x; } -function nMulTo(x,y,r) { x.multiplyTo(y,r); } -function nSqrTo(x,r) { x.squareTo(r); } - -NullExp.prototype.convert = nNop; -NullExp.prototype.revert = nNop; -NullExp.prototype.mulTo = nMulTo; -NullExp.prototype.sqrTo = nSqrTo; - -// (public) this^e -function bnPow(e) { return this.exp(e,new NullExp()); } - -// (protected) r = lower n words of "this * a", a.t <= n -// "this" should be the larger one if appropriate. -function bnpMultiplyLowerTo(a,n,r) { - var i = Math.min(this.t+a.t,n); - r.s = 0; // assumes a,this >= 0 - r.t = i; - while(i > 0) r[--i] = 0; - var j; - for(j = r.t-this.t; i < j; ++i) r[i+this.t] = this.am(0,a[i],r,i,0,this.t); - for(j = Math.min(a.t,n); i < j; ++i) this.am(0,a[i],r,i,0,n-i); - r.clamp(); -} - -// (protected) r = "this * a" without lower n words, n > 0 -// "this" should be the larger one if appropriate. -function bnpMultiplyUpperTo(a,n,r) { - --n; - var i = r.t = this.t+a.t-n; - r.s = 0; // assumes a,this >= 0 - while(--i >= 0) r[i] = 0; - for(i = Math.max(n-this.t,0); i < a.t; ++i) - r[this.t+i-n] = this.am(n-i,a[i],r,0,0,this.t+i-n); - r.clamp(); - r.drShiftTo(1,r); -} - -// Barrett modular reduction -function Barrett(m) { - // setup Barrett - this.r2 = nbi(); - this.q3 = nbi(); - BigInteger.ONE.dlShiftTo(2*m.t,this.r2); - this.mu = this.r2.divide(m); - this.m = m; -} - -function barrettConvert(x) { - if(x.s < 0 || x.t > 2*this.m.t) return x.mod(this.m); - else if(x.compareTo(this.m) < 0) return x; - else { var r = nbi(); x.copyTo(r); this.reduce(r); return r; } -} - -function barrettRevert(x) { return x; } - -// x = x mod m (HAC 14.42) -function barrettReduce(x) { - x.drShiftTo(this.m.t-1,this.r2); - if(x.t > this.m.t+1) { x.t = this.m.t+1; x.clamp(); } - this.mu.multiplyUpperTo(this.r2,this.m.t+1,this.q3); - this.m.multiplyLowerTo(this.q3,this.m.t+1,this.r2); - while(x.compareTo(this.r2) < 0) x.dAddOffset(1,this.m.t+1); - x.subTo(this.r2,x); - while(x.compareTo(this.m) >= 0) x.subTo(this.m,x); -} - -// r = x^2 mod m; x != r -function barrettSqrTo(x,r) { x.squareTo(r); this.reduce(r); } - -// r = x*y mod m; x,y != r -function barrettMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } - -Barrett.prototype.convert = barrettConvert; -Barrett.prototype.revert = barrettRevert; -Barrett.prototype.reduce = barrettReduce; -Barrett.prototype.mulTo = barrettMulTo; -Barrett.prototype.sqrTo = barrettSqrTo; - -// (public) this^e % m (HAC 14.85) -function bnModPow(e,m) { - var i = e.bitLength(), k, r = nbv(1), z; - if(i <= 0) return r; - else if(i < 18) k = 1; - else if(i < 48) k = 3; - else if(i < 144) k = 4; - else if(i < 768) k = 5; - else k = 6; - if(i < 8) - z = new Classic(m); - else if(m.isEven()) - z = new Barrett(m); - else - z = new Montgomery(m); - - // precomputation - var g = new Array(), n = 3, k1 = k-1, km = (1< 1) { - var g2 = nbi(); - z.sqrTo(g[1],g2); - while(n <= km) { - g[n] = nbi(); - z.mulTo(g2,g[n-2],g[n]); - n += 2; - } - } - - var j = e.t-1, w, is1 = true, r2 = nbi(), t; - i = nbits(e[j])-1; - while(j >= 0) { - if(i >= k1) w = (e[j]>>(i-k1))&km; - else { - w = (e[j]&((1<<(i+1))-1))<<(k1-i); - if(j > 0) w |= e[j-1]>>(this.DB+i-k1); - } - - n = k; - while((w&1) == 0) { w >>= 1; --n; } - if((i -= n) < 0) { i += this.DB; --j; } - if(is1) { // ret == 1, don't bother squaring or multiplying it - g[w].copyTo(r); - is1 = false; - } - else { - while(n > 1) { z.sqrTo(r,r2); z.sqrTo(r2,r); n -= 2; } - if(n > 0) z.sqrTo(r,r2); else { t = r; r = r2; r2 = t; } - z.mulTo(r2,g[w],r); - } - - while(j >= 0 && (e[j]&(1< 0) { - x.rShiftTo(g,x); - y.rShiftTo(g,y); - } - while(x.signum() > 0) { - if((i = x.getLowestSetBit()) > 0) x.rShiftTo(i,x); - if((i = y.getLowestSetBit()) > 0) y.rShiftTo(i,y); - if(x.compareTo(y) >= 0) { - x.subTo(y,x); - x.rShiftTo(1,x); - } - else { - y.subTo(x,y); - y.rShiftTo(1,y); - } - } - if(g > 0) y.lShiftTo(g,y); - return y; -} - -// (protected) this % n, n < 2^26 -function bnpModInt(n) { - if(n <= 0) return 0; - var d = this.DV%n, r = (this.s<0)?n-1:0; - if(this.t > 0) - if(d == 0) r = this[0]%n; - else for(var i = this.t-1; i >= 0; --i) r = (d*r+this[i])%n; - return r; -} - -// (public) 1/this % m (HAC 14.61) -function bnModInverse(m) { - var ac = m.isEven(); - if((this.isEven() && ac) || m.signum() == 0) return BigInteger.ZERO; - var u = m.clone(), v = this.clone(); - var a = nbv(1), b = nbv(0), c = nbv(0), d = nbv(1); - while(u.signum() != 0) { - while(u.isEven()) { - u.rShiftTo(1,u); - if(ac) { - if(!a.isEven() || !b.isEven()) { a.addTo(this,a); b.subTo(m,b); } - a.rShiftTo(1,a); - } - else if(!b.isEven()) b.subTo(m,b); - b.rShiftTo(1,b); - } - while(v.isEven()) { - v.rShiftTo(1,v); - if(ac) { - if(!c.isEven() || !d.isEven()) { c.addTo(this,c); d.subTo(m,d); } - c.rShiftTo(1,c); - } - else if(!d.isEven()) d.subTo(m,d); - d.rShiftTo(1,d); - } - if(u.compareTo(v) >= 0) { - u.subTo(v,u); - if(ac) a.subTo(c,a); - b.subTo(d,b); - } - else { - v.subTo(u,v); - if(ac) c.subTo(a,c); - d.subTo(b,d); - } - } - if(v.compareTo(BigInteger.ONE) != 0) return BigInteger.ZERO; - if(d.compareTo(m) >= 0) return d.subtract(m); - if(d.signum() < 0) d.addTo(m,d); else return d; - if(d.signum() < 0) return d.add(m); else return d; -} - -var lowprimes = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997]; -var lplim = (1<<26)/lowprimes[lowprimes.length-1]; - -// (public) test primality with certainty >= 1-.5^t -function bnIsProbablePrime(t) { - var i, x = this.abs(); - if(x.t == 1 && x[0] <= lowprimes[lowprimes.length-1]) { - for(i = 0; i < lowprimes.length; ++i) - if(x[0] == lowprimes[i]) return true; - return false; - } - if(x.isEven()) return false; - i = 1; - while(i < lowprimes.length) { - var m = lowprimes[i], j = i+1; - while(j < lowprimes.length && m < lplim) m *= lowprimes[j++]; - m = x.modInt(m); - while(i < j) if(m%lowprimes[i++] == 0) return false; - } - return x.millerRabin(t); -} - -// (protected) true if probably prime (HAC 4.24, Miller-Rabin) -function bnpMillerRabin(t) { - var n1 = this.subtract(BigInteger.ONE); - var k = n1.getLowestSetBit(); - if(k <= 0) return false; - var r = n1.shiftRight(k); - t = (t+1)>>1; - if(t > lowprimes.length) t = lowprimes.length; - var a = nbi(); - for(var i = 0; i < t; ++i) { - //Pick bases at random, instead of starting at 2 - a.fromInt(lowprimes[Math.floor(Math.random()*lowprimes.length)]); - var y = a.modPow(r,this); - if(y.compareTo(BigInteger.ONE) != 0 && y.compareTo(n1) != 0) { - var j = 1; - while(j++ < k && y.compareTo(n1) != 0) { - y = y.modPowInt(2,this); - if(y.compareTo(BigInteger.ONE) == 0) return false; - } - if(y.compareTo(n1) != 0) return false; - } - } - return true; -} - -// protected -BigInteger.prototype.chunkSize = bnpChunkSize; -BigInteger.prototype.toRadix = bnpToRadix; -BigInteger.prototype.fromRadix = bnpFromRadix; -BigInteger.prototype.fromNumber = bnpFromNumber; -BigInteger.prototype.bitwiseTo = bnpBitwiseTo; -BigInteger.prototype.changeBit = bnpChangeBit; -BigInteger.prototype.addTo = bnpAddTo; -BigInteger.prototype.dMultiply = bnpDMultiply; -BigInteger.prototype.dAddOffset = bnpDAddOffset; -BigInteger.prototype.multiplyLowerTo = bnpMultiplyLowerTo; -BigInteger.prototype.multiplyUpperTo = bnpMultiplyUpperTo; -BigInteger.prototype.modInt = bnpModInt; -BigInteger.prototype.millerRabin = bnpMillerRabin; - -// public -BigInteger.prototype.clone = bnClone; -BigInteger.prototype.intValue = bnIntValue; -BigInteger.prototype.byteValue = bnByteValue; -BigInteger.prototype.shortValue = bnShortValue; -BigInteger.prototype.signum = bnSigNum; -BigInteger.prototype.toByteArray = bnToByteArray; -BigInteger.prototype.equals = bnEquals; -BigInteger.prototype.min = bnMin; -BigInteger.prototype.max = bnMax; -BigInteger.prototype.and = bnAnd; -BigInteger.prototype.or = bnOr; -BigInteger.prototype.xor = bnXor; -BigInteger.prototype.andNot = bnAndNot; -BigInteger.prototype.not = bnNot; -BigInteger.prototype.shiftLeft = bnShiftLeft; -BigInteger.prototype.shiftRight = bnShiftRight; -BigInteger.prototype.getLowestSetBit = bnGetLowestSetBit; -BigInteger.prototype.bitCount = bnBitCount; -BigInteger.prototype.testBit = bnTestBit; -BigInteger.prototype.setBit = bnSetBit; -BigInteger.prototype.clearBit = bnClearBit; -BigInteger.prototype.flipBit = bnFlipBit; -BigInteger.prototype.add = bnAdd; -BigInteger.prototype.subtract = bnSubtract; -BigInteger.prototype.multiply = bnMultiply; -BigInteger.prototype.divide = bnDivide; -BigInteger.prototype.remainder = bnRemainder; -BigInteger.prototype.divideAndRemainder = bnDivideAndRemainder; -BigInteger.prototype.modPow = bnModPow; -BigInteger.prototype.modInverse = bnModInverse; -BigInteger.prototype.pow = bnPow; -BigInteger.prototype.gcd = bnGCD; -BigInteger.prototype.isProbablePrime = bnIsProbablePrime; - -// JSBN-specific extension -BigInteger.prototype.square = bnSquare; - -// BigInteger interfaces not implemented in jsbn: - -// BigInteger(int signum, byte[] magnitude) -// double doubleValue() -// float floatValue() -// int hashCode() -// long longValue() -// static BigInteger valueOf(long val) -/*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/ - */ -// Depends on jsbn.js and rng.js - -// Version 1.1: support utf-8 encoding in pkcs1pad2 - -// convert a (hex) string to a bignum object -function parseBigInt(str,r) { - return new BigInteger(str,r); -} - -function linebrk(s,n) { - var ret = ""; - var i = 0; - while(i + n < s.length) { - ret += s.substring(i,i+n) + "\n"; - i += n; - } - return ret + s.substring(i,s.length); -} - -function byte2Hex(b) { - if(b < 0x10) - return "0" + b.toString(16); - else - return b.toString(16); -} - -// PKCS#1 (type 2, random) pad input string s to n bytes, and return a bigint -function pkcs1pad2(s,n) { - if(n < s.length + 11) { // TODO: fix for utf-8 - alert("Message too long for RSA"); - return null; - } - var ba = new Array(); - var i = s.length - 1; - while(i >= 0 && n > 0) { - var c = s.charCodeAt(i--); - if(c < 128) { // encode using utf-8 - ba[--n] = c; - } - else if((c > 127) && (c < 2048)) { - ba[--n] = (c & 63) | 128; - ba[--n] = (c >> 6) | 192; - } - else { - ba[--n] = (c & 63) | 128; - ba[--n] = ((c >> 6) & 63) | 128; - ba[--n] = (c >> 12) | 224; - } - } - ba[--n] = 0; - var rng = new SecureRandom(); - var x = new Array(); - while(n > 2) { // random non-zero pad - x[0] = 0; - while(x[0] == 0) rng.nextBytes(x); - ba[--n] = x[0]; - } - ba[--n] = 2; - ba[--n] = 0; - return new BigInteger(ba); -} - -// PKCS#1 (OAEP) mask generation function -function oaep_mgf1_arr(seed, len, hash) -{ - var mask = '', i = 0; - - while (mask.length < len) - { - mask += hash(String.fromCharCode.apply(String, seed.concat([ - (i & 0xff000000) >> 24, - (i & 0x00ff0000) >> 16, - (i & 0x0000ff00) >> 8, - i & 0x000000ff]))); - i += 1; - } - - return mask; -} - -var SHA1_SIZE = 20; - -// PKCS#1 (OAEP) pad input string s to n bytes, and return a bigint -function oaep_pad(s, n, hash) -{ - if (s.length + 2 * SHA1_SIZE + 2 > n) - { - throw "Message too long for RSA"; - } - - var PS = '', i; - - for (i = 0; i < n - s.length - 2 * SHA1_SIZE - 2; i += 1) - { - PS += '\x00'; - } - - var DB = rstr_sha1('') + PS + '\x01' + s; - var seed = new Array(SHA1_SIZE); - new SecureRandom().nextBytes(seed); - - var dbMask = oaep_mgf1_arr(seed, DB.length, hash || rstr_sha1); - var maskedDB = []; - - for (i = 0; i < DB.length; i += 1) - { - maskedDB[i] = DB.charCodeAt(i) ^ dbMask.charCodeAt(i); - } - - var seedMask = oaep_mgf1_arr(maskedDB, seed.length, rstr_sha1); - var maskedSeed = [0]; - - for (i = 0; i < seed.length; i += 1) - { - maskedSeed[i + 1] = seed[i] ^ seedMask.charCodeAt(i); - } - - return new BigInteger(maskedSeed.concat(maskedDB)); -} - -// "empty" RSA key constructor -function RSAKey() { - this.n = null; - this.e = 0; - this.d = null; - this.p = null; - this.q = null; - this.dmp1 = null; - this.dmq1 = null; - this.coeff = null; -} - -// Set the public key fields N and e from hex strings -function RSASetPublic(N,E) { - this.isPublic = true; - if (typeof N !== "string") - { - this.n = N; - this.e = E; - } - else if(N != null && E != null && N.length > 0 && E.length > 0) { - this.n = parseBigInt(N,16); - this.e = parseInt(E,16); - } - else - alert("Invalid RSA public key"); -} - -// Perform raw public operation on "x": return x^e (mod n) -function RSADoPublic(x) { - return x.modPowInt(this.e, this.n); -} - -// Return the PKCS#1 RSA encryption of "text" as an even-length hex string -function RSAEncrypt(text) { - var m = pkcs1pad2(text,(this.n.bitLength()+7)>>3); - if(m == null) return null; - var c = this.doPublic(m); - if(c == null) return null; - var h = c.toString(16); - if((h.length & 1) == 0) return h; else return "0" + h; -} - -// Return the PKCS#1 OAEP RSA encryption of "text" as an even-length hex string -function RSAEncryptOAEP(text, hash) { - var m = oaep_pad(text, (this.n.bitLength()+7)>>3, hash); - if(m == null) return null; - var c = this.doPublic(m); - if(c == null) return null; - var h = c.toString(16); - if((h.length & 1) == 0) return h; else return "0" + h; -} - -// Return the PKCS#1 RSA encryption of "text" as a Base64-encoded string -//function RSAEncryptB64(text) { -// var h = this.encrypt(text); -// if(h) return hex2b64(h); else return null; -//} - -// protected -RSAKey.prototype.doPublic = RSADoPublic; - -// public -RSAKey.prototype.setPublic = RSASetPublic; -RSAKey.prototype.encrypt = RSAEncrypt; -RSAKey.prototype.encryptOAEP = RSAEncryptOAEP; -//RSAKey.prototype.encrypt_b64 = RSAEncryptB64; - -RSAKey.prototype.type = "RSA"; -/*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/ - */ -// Depends on rsa.js and jsbn2.js - -// Version 1.1: support utf-8 decoding in pkcs1unpad2 - -// Undo PKCS#1 (type 2, random) padding and, if valid, return the plaintext -function pkcs1unpad2(d,n) { - var b = d.toByteArray(); - var i = 0; - while(i < b.length && b[i] == 0) ++i; - if(b.length-i != n-1 || b[i] != 2) - return null; - ++i; - while(b[i] != 0) - if(++i >= b.length) return null; - var ret = ""; - while(++i < b.length) { - var c = b[i] & 255; - if(c < 128) { // utf-8 decode - ret += String.fromCharCode(c); - } - else if((c > 191) && (c < 224)) { - ret += String.fromCharCode(((c & 31) << 6) | (b[i+1] & 63)); - ++i; - } - else { - ret += String.fromCharCode(((c & 15) << 12) | ((b[i+1] & 63) << 6) | (b[i+2] & 63)); - i += 2; - } - } - return ret; -} - -// PKCS#1 (OAEP) mask generation function -function oaep_mgf1_str(seed, len, hash) -{ - var mask = '', i = 0; - - while (mask.length < len) - { - mask += hash(seed + String.fromCharCode.apply(String, [ - (i & 0xff000000) >> 24, - (i & 0x00ff0000) >> 16, - (i & 0x0000ff00) >> 8, - i & 0x000000ff])); - i += 1; - } - - return mask; -} - -var SHA1_SIZE = 20; - -// Undo PKCS#1 (OAEP) padding and, if valid, return the plaintext -function oaep_unpad(d, n, hash) -{ - d = d.toByteArray(); - - var i; - - for (i = 0; i < d.length; i += 1) - { - d[i] &= 0xff; - } - - while (d.length < n) - { - d.unshift(0); - } - - d = String.fromCharCode.apply(String, d); - - if (d.length < 2 * SHA1_SIZE + 2) - { - throw "Cipher too short"; - } - - var maskedSeed = d.substr(1, SHA1_SIZE) - var maskedDB = d.substr(SHA1_SIZE + 1); - - var seedMask = oaep_mgf1_str(maskedDB, SHA1_SIZE, hash || rstr_sha1); - var seed = [], i; - - for (i = 0; i < maskedSeed.length; i += 1) - { - seed[i] = maskedSeed.charCodeAt(i) ^ seedMask.charCodeAt(i); - } - - var dbMask = oaep_mgf1_str(String.fromCharCode.apply(String, seed), - d.length - SHA1_SIZE, rstr_sha1); - - var DB = []; - - for (i = 0; i < maskedDB.length; i += 1) - { - DB[i] = maskedDB.charCodeAt(i) ^ dbMask.charCodeAt(i); - } - - DB = String.fromCharCode.apply(String, DB); - - if (DB.substr(0, SHA1_SIZE) !== rstr_sha1('')) - { - throw "Hash mismatch"; - } - - DB = DB.substr(SHA1_SIZE); - - var first_one = DB.indexOf('\x01'); - var last_zero = (first_one != -1) ? DB.substr(0, first_one).lastIndexOf('\x00') : -1; - - if (last_zero + 1 != first_one) - { - throw "Malformed data"; - } - - return DB.substr(first_one + 1); -} - -// Set the private key fields N, e, and d from hex strings -function RSASetPrivate(N,E,D) { - this.isPrivate = true; - if (typeof N !== "string") - { - this.n = N; - this.e = E; - this.d = D; - } - else if(N != null && E != null && N.length > 0 && E.length > 0) { - this.n = parseBigInt(N,16); - this.e = parseInt(E,16); - this.d = parseBigInt(D,16); - } - else - alert("Invalid RSA private key"); -} - -// Set the private key fields N, e, d and CRT params from hex strings -function RSASetPrivateEx(N,E,D,P,Q,DP,DQ,C) { - this.isPrivate = true; - if (N == null) throw "RSASetPrivateEx N == null"; - if (E == null) throw "RSASetPrivateEx E == null"; - if (N.length == 0) throw "RSASetPrivateEx N.length == 0"; - if (E.length == 0) throw "RSASetPrivateEx E.length == 0"; - - if (N != null && E != null && N.length > 0 && E.length > 0) { - this.n = parseBigInt(N,16); - this.e = parseInt(E,16); - this.d = parseBigInt(D,16); - this.p = parseBigInt(P,16); - this.q = parseBigInt(Q,16); - this.dmp1 = parseBigInt(DP,16); - this.dmq1 = parseBigInt(DQ,16); - this.coeff = parseBigInt(C,16); - } else { - alert("Invalid RSA private key in RSASetPrivateEx"); - } -} - -// Generate a new random private key B bits long, using public expt E -function RSAGenerate(B,E) { - var rng = new SecureRandom(); - var qs = B>>1; - this.e = parseInt(E,16); - var ee = new BigInteger(E,16); - for(;;) { - for(;;) { - this.p = new BigInteger(B-qs,1,rng); - if(this.p.subtract(BigInteger.ONE).gcd(ee).compareTo(BigInteger.ONE) == 0 && this.p.isProbablePrime(10)) break; - } - for(;;) { - this.q = new BigInteger(qs,1,rng); - if(this.q.subtract(BigInteger.ONE).gcd(ee).compareTo(BigInteger.ONE) == 0 && this.q.isProbablePrime(10)) break; - } - if(this.p.compareTo(this.q) <= 0) { - var t = this.p; - this.p = this.q; - this.q = t; - } - var p1 = this.p.subtract(BigInteger.ONE); // p1 = p - 1 - var q1 = this.q.subtract(BigInteger.ONE); // q1 = q - 1 - var phi = p1.multiply(q1); - if(phi.gcd(ee).compareTo(BigInteger.ONE) == 0) { - this.n = this.p.multiply(this.q); // this.n = p * q - this.d = ee.modInverse(phi); // this.d = - this.dmp1 = this.d.mod(p1); // this.dmp1 = d mod (p - 1) - this.dmq1 = this.d.mod(q1); // this.dmq1 = d mod (q - 1) - this.coeff = this.q.modInverse(this.p); // this.coeff = (q ^ -1) mod p - break; - } - } - this.isPrivate = true; -} - -// Perform raw private operation on "x": return x^d (mod n) -function RSADoPrivate(x) { - if(this.p == null || this.q == null) - return x.modPow(this.d, this.n); - - // TODO: re-calculate any missing CRT params - var xp = x.mod(this.p).modPow(this.dmp1, this.p); // xp=cp? - var xq = x.mod(this.q).modPow(this.dmq1, this.q); // xq=cq? - - while(xp.compareTo(xq) < 0) - xp = xp.add(this.p); - // NOTE: - // xp.subtract(xq) => cp -cq - // xp.subtract(xq).multiply(this.coeff).mod(this.p) => (cp - cq) * u mod p = h - // xp.subtract(xq).multiply(this.coeff).mod(this.p).multiply(this.q).add(xq) => cq + (h * q) = M - return xp.subtract(xq).multiply(this.coeff).mod(this.p).multiply(this.q).add(xq); -} - -// Return the PKCS#1 RSA decryption of "ctext". -// "ctext" is an even-length hex string and the output is a plain string. -function RSADecrypt(ctext) { - var c = parseBigInt(ctext, 16); - var m = this.doPrivate(c); - if(m == null) return null; - return pkcs1unpad2(m, (this.n.bitLength()+7)>>3); -} - -// Return the PKCS#1 OAEP RSA decryption of "ctext". -// "ctext" is an even-length hex string and the output is a plain string. -function RSADecryptOAEP(ctext, hash) { - var c = parseBigInt(ctext, 16); - var m = this.doPrivate(c); - if(m == null) return null; - return oaep_unpad(m, (this.n.bitLength()+7)>>3, hash); -} - -// Return the PKCS#1 RSA decryption of "ctext". -// "ctext" is a Base64-encoded string and the output is a plain string. -//function RSAB64Decrypt(ctext) { -// var h = b64tohex(ctext); -// if(h) return this.decrypt(h); else return null; -//} - -// protected -RSAKey.prototype.doPrivate = RSADoPrivate; - -// public -RSAKey.prototype.setPrivate = RSASetPrivate; -RSAKey.prototype.setPrivateEx = RSASetPrivateEx; -RSAKey.prototype.generate = RSAGenerate; -RSAKey.prototype.decrypt = RSADecrypt; -RSAKey.prototype.decryptOAEP = RSADecryptOAEP; -//RSAKey.prototype.b64_decrypt = RSAB64Decrypt; -/*! rsapem-1.1.js (c) 2012 Kenji Urushima | kjur.github.com/jsrsasign/license - */ -// -// rsa-pem.js - adding function for reading/writing PKCS#1 PEM private key -// to RSAKey class. -// -// version: 1.1.1 (2013-Apr-12) -// -// Copyright (c) 2010-2013 Kenji Urushima (kenji.urushima@gmail.com) -// -// This software is licensed under the terms of the MIT License. -// http://kjur.github.com/jsrsasign/license/ -// -// The above copyright and license notice shall be -// included in all copies or substantial portions of the Software. -// -// -// Depends on: -// -// -// -// _RSApem_pemToBase64(sPEM) -// -// removing PEM header, PEM footer and space characters including -// new lines from PEM formatted RSA private key string. -// - -/** - * @fileOverview - * @name rsapem-1.1.js - * @author Kenji Urushima kenji.urushima@gmail.com - * @version 1.1 - * @license MIT License - */ -function _rsapem_pemToBase64(sPEMPrivateKey) { - var s = sPEMPrivateKey; - s = s.replace("-----BEGIN RSA PRIVATE KEY-----", ""); - s = s.replace("-----END RSA PRIVATE KEY-----", ""); - s = s.replace(/[ \n]+/g, ""); - return s; -} - -function _rsapem_getPosArrayOfChildrenFromHex(hPrivateKey) { - var a = new Array(); - var v1 = ASN1HEX.getStartPosOfV_AtObj(hPrivateKey, 0); - var n1 = ASN1HEX.getPosOfNextSibling_AtObj(hPrivateKey, v1); - var e1 = ASN1HEX.getPosOfNextSibling_AtObj(hPrivateKey, n1); - var d1 = ASN1HEX.getPosOfNextSibling_AtObj(hPrivateKey, e1); - var p1 = ASN1HEX.getPosOfNextSibling_AtObj(hPrivateKey, d1); - var q1 = ASN1HEX.getPosOfNextSibling_AtObj(hPrivateKey, p1); - var dp1 = ASN1HEX.getPosOfNextSibling_AtObj(hPrivateKey, q1); - var dq1 = ASN1HEX.getPosOfNextSibling_AtObj(hPrivateKey, dp1); - var co1 = ASN1HEX.getPosOfNextSibling_AtObj(hPrivateKey, dq1); - a.push(v1, n1, e1, d1, p1, q1, dp1, dq1, co1); - return a; -} - -function _rsapem_getHexValueArrayOfChildrenFromHex(hPrivateKey) { - var posArray = _rsapem_getPosArrayOfChildrenFromHex(hPrivateKey); - var v = ASN1HEX.getHexOfV_AtObj(hPrivateKey, posArray[0]); - var n = ASN1HEX.getHexOfV_AtObj(hPrivateKey, posArray[1]); - var e = ASN1HEX.getHexOfV_AtObj(hPrivateKey, posArray[2]); - var d = ASN1HEX.getHexOfV_AtObj(hPrivateKey, posArray[3]); - var p = ASN1HEX.getHexOfV_AtObj(hPrivateKey, posArray[4]); - var q = ASN1HEX.getHexOfV_AtObj(hPrivateKey, posArray[5]); - var dp = ASN1HEX.getHexOfV_AtObj(hPrivateKey, posArray[6]); - var dq = ASN1HEX.getHexOfV_AtObj(hPrivateKey, posArray[7]); - var co = ASN1HEX.getHexOfV_AtObj(hPrivateKey, posArray[8]); - var a = new Array(); - a.push(v, n, e, d, p, q, dp, dq, co); - return a; -} - -/** - * read RSA private key from a ASN.1 hexadecimal string - * @name readPrivateKeyFromASN1HexString - * @memberOf RSAKey# - * @function - * @param {String} keyHex ASN.1 hexadecimal string of PKCS#1 private key. - * @since 1.1.1 - */ -function _rsapem_readPrivateKeyFromASN1HexString(keyHex) { - var a = _rsapem_getHexValueArrayOfChildrenFromHex(keyHex); - this.setPrivateEx(a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8]); -} - -/** - * read PKCS#1 private key from a string - * @name readPrivateKeyFromPEMString - * @memberOf RSAKey# - * @function - * @param {String} keyPEM string of PKCS#1 private key. - */ -function _rsapem_readPrivateKeyFromPEMString(keyPEM) { - var keyB64 = _rsapem_pemToBase64(keyPEM); - var keyHex = b64tohex(keyB64) // depends base64.js - var a = _rsapem_getHexValueArrayOfChildrenFromHex(keyHex); - this.setPrivateEx(a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8]); -} - -RSAKey.prototype.readPrivateKeyFromPEMString = _rsapem_readPrivateKeyFromPEMString; -RSAKey.prototype.readPrivateKeyFromASN1HexString = _rsapem_readPrivateKeyFromASN1HexString; -/*! rsasign-1.2.7.js (c) 2012 Kenji Urushima | kjur.github.com/jsrsasign/license - */ -var _RE_HEXDECONLY=new RegExp("");_RE_HEXDECONLY.compile("[^0-9a-f]","gi");function _rsasign_getHexPaddedDigestInfoForString(d,e,a){var b=function(f){return KJUR.crypto.Util.hashString(f,a)};var c=b(d);return KJUR.crypto.Util.getPaddedDigestInfoHex(c,a,e)}function _zeroPaddingOfSignature(e,d){var c="";var a=d/4-e.length;for(var b=0;b>24,(d&16711680)>>16,(d&65280)>>8,d&255]))));d+=1}return b}function _rsasign_signStringPSS(e,a,d){var c=function(f){return KJUR.crypto.Util.hashHex(f,a)};var b=c(rstrtohex(e));if(d===undefined){d=-1}return this.signWithMessageHashPSS(b,a,d)}function _rsasign_signWithMessageHashPSS(l,a,k){var b=hextorstr(l);var g=b.length;var m=this.n.bitLength()-1;var c=Math.ceil(m/8);var d;var o=function(i){return KJUR.crypto.Util.hashHex(i,a)};if(k===-1||k===undefined){k=g}else{if(k===-2){k=c-g-2}else{if(k<-2){throw"invalid salt length"}}}if(c<(g+k+2)){throw"data too long"}var f="";if(k>0){f=new Array(k);new SecureRandom().nextBytes(f);f=String.fromCharCode.apply(String,f)}var n=hextorstr(o(rstrtohex("\x00\x00\x00\x00\x00\x00\x00\x00"+b+f)));var j=[];for(d=0;d>(8*c-m))&255;q[0]&=~p;for(d=0;dthis.n.bitLength()){return 0}var i=this.doPublic(b);var e=i.toString(16).replace(/^1f+00/,"");var g=_rsasign_getAlgNameAndHashFromHexDisgestInfo(e);if(g.length==0){return false}var d=g[0];var h=g[1];var a=function(k){return KJUR.crypto.Util.hashString(k,d)};var c=a(f);return(h==c)}function _rsasign_verifyWithMessageHash(e,a){a=a.replace(_RE_HEXDECONLY,"");a=a.replace(/[ \n]+/g,"");var b=parseBigInt(a,16);if(b.bitLength()>this.n.bitLength()){return 0}var h=this.doPublic(b);var g=h.toString(16).replace(/^1f+00/,"");var c=_rsasign_getAlgNameAndHashFromHexDisgestInfo(g);if(c.length==0){return false}var d=c[0];var f=c[1];return(f==e)}function _rsasign_verifyStringPSS(c,b,a,f){var e=function(g){return KJUR.crypto.Util.hashHex(g,a)};var d=e(rstrtohex(c));if(f===undefined){f=-1}return this.verifyWithMessageHashPSS(d,b,a,f)}function _rsasign_verifyWithMessageHashPSS(f,s,l,c){var k=new BigInteger(s,16);if(k.bitLength()>this.n.bitLength()){return false}var r=function(i){return KJUR.crypto.Util.hashHex(i,l)};var j=hextorstr(f);var h=j.length;var g=this.n.bitLength()-1;var m=Math.ceil(g/8);var q;if(c===-1||c===undefined){c=h}else{if(c===-2){c=m-h-2}else{if(c<-2){throw"invalid salt length"}}}if(m<(h+c+2)){throw"data too long"}var a=this.doPublic(k).toByteArray();for(q=0;q>(8*m-g))&255;if((d.charCodeAt(0)&p)!==0){throw"bits beyond keysize not zero"}var n=pss_mgf1_str(e,d.length,r);var o=[];for(q=0;qMIT License - */ - -/* - * MEMO: - * f('3082025b02...', 2) ... 82025b ... 3bytes - * f('020100', 2) ... 01 ... 1byte - * f('0203001...', 2) ... 03 ... 1byte - * f('02818003...', 2) ... 8180 ... 2bytes - * f('3080....0000', 2) ... 80 ... -1 - * - * Requirements: - * - ASN.1 type octet length MUST be 1. - * (i.e. ASN.1 primitives like SET, SEQUENCE, INTEGER, OCTETSTRING ...) - */ - -/** - * ASN.1 DER encoded hexadecimal string utility class - * @name ASN1HEX - * @class ASN.1 DER encoded hexadecimal string utility class - * @since jsrsasign 1.1 - */ -var ASN1HEX = new function() { - /** - * get byte length for ASN.1 L(length) bytes - * @name getByteLengthOfL_AtObj - * @memberOf ASN1HEX - * @function - * @param {String} s hexadecimal string of ASN.1 DER encoded data - * @param {Number} pos string index - * @return byte length for ASN.1 L(length) bytes - */ - this.getByteLengthOfL_AtObj = function(s, pos) { - if (s.substring(pos + 2, pos + 3) != '8') return 1; - var i = parseInt(s.substring(pos + 3, pos + 4)); - if (i == 0) return -1; // length octet '80' indefinite length - if (0 < i && i < 10) return i + 1; // including '8?' octet; - return -2; // malformed format - }; - - /** - * get hexadecimal string for ASN.1 L(length) bytes - * @name getHexOfL_AtObj - * @memberOf ASN1HEX - * @function - * @param {String} s hexadecimal string of ASN.1 DER encoded data - * @param {Number} pos string index - * @return {String} hexadecimal string for ASN.1 L(length) bytes - */ - this.getHexOfL_AtObj = function(s, pos) { - var len = this.getByteLengthOfL_AtObj(s, pos); - if (len < 1) return ''; - return s.substring(pos + 2, pos + 2 + len * 2); - }; - - // getting ASN.1 length value at the position 'idx' of - // hexa decimal string 's'. - // - // f('3082025b02...', 0) ... 82025b ... ??? - // f('020100', 0) ... 01 ... 1 - // f('0203001...', 0) ... 03 ... 3 - // f('02818003...', 0) ... 8180 ... 128 - /** - * get integer value of ASN.1 length for ASN.1 data - * @name getIntOfL_AtObj - * @memberOf ASN1HEX - * @function - * @param {String} s hexadecimal string of ASN.1 DER encoded data - * @param {Number} pos string index - * @return ASN.1 L(length) integer value - */ - this.getIntOfL_AtObj = function(s, pos) { - var hLength = this.getHexOfL_AtObj(s, pos); - if (hLength == '') return -1; - var bi; - if (parseInt(hLength.substring(0, 1)) < 8) { - bi = new BigInteger(hLength, 16); - } else { - bi = new BigInteger(hLength.substring(2), 16); - } - return bi.intValue(); - }; - - /** - * get ASN.1 value starting string position for ASN.1 object refered by index 'idx'. - * @name getStartPosOfV_AtObj - * @memberOf ASN1HEX - * @function - * @param {String} s hexadecimal string of ASN.1 DER encoded data - * @param {Number} pos string index - */ - this.getStartPosOfV_AtObj = function(s, pos) { - var l_len = this.getByteLengthOfL_AtObj(s, pos); - if (l_len < 0) return l_len; - return pos + (l_len + 1) * 2; - }; - - /** - * get hexadecimal string of ASN.1 V(value) - * @name getHexOfV_AtObj - * @memberOf ASN1HEX - * @function - * @param {String} s hexadecimal string of ASN.1 DER encoded data - * @param {Number} pos string index - * @return {String} hexadecimal string of ASN.1 value. - */ - this.getHexOfV_AtObj = function(s, pos) { - var pos1 = this.getStartPosOfV_AtObj(s, pos); - var len = this.getIntOfL_AtObj(s, pos); - return s.substring(pos1, pos1 + len * 2); - }; - - /** - * get hexadecimal string of ASN.1 TLV at - * @name getHexOfTLV_AtObj - * @memberOf ASN1HEX - * @function - * @param {String} s hexadecimal string of ASN.1 DER encoded data - * @param {Number} pos string index - * @return {String} hexadecimal string of ASN.1 TLV. - * @since 1.1 - */ - this.getHexOfTLV_AtObj = function(s, pos) { - var hT = s.substr(pos, 2); - var hL = this.getHexOfL_AtObj(s, pos); - var hV = this.getHexOfV_AtObj(s, pos); - return hT + hL + hV; - }; - - /** - * get next sibling starting index for ASN.1 object string - * @name getPosOfNextSibling_AtObj - * @memberOf ASN1HEX - * @function - * @param {String} s hexadecimal string of ASN.1 DER encoded data - * @param {Number} pos string index - * @return next sibling starting index for ASN.1 object string - */ - this.getPosOfNextSibling_AtObj = function(s, pos) { - var pos1 = this.getStartPosOfV_AtObj(s, pos); - var len = this.getIntOfL_AtObj(s, pos); - return pos1 + len * 2; - }; - - /** - * get array of indexes of child ASN.1 objects - * @name getPosArrayOfChildren_AtObj - * @memberOf ASN1HEX - * @function - * @param {String} s hexadecimal string of ASN.1 DER encoded data - * @param {Number} start string index of ASN.1 object - * @return {Array of Number} array of indexes for childen of ASN.1 objects - */ - this.getPosArrayOfChildren_AtObj = function(h, pos) { - var a = new Array(); - var p0 = this.getStartPosOfV_AtObj(h, pos); - a.push(p0); - - var len = this.getIntOfL_AtObj(h, pos); - var p = p0; - var k = 0; - while (1) { - var pNext = this.getPosOfNextSibling_AtObj(h, p); - if (pNext == null || (pNext - p0 >= (len * 2))) break; - if (k >= 200) break; - - a.push(pNext); - p = pNext; - - k++; - } - - return a; - }; - - /** - * get string index of nth child object of ASN.1 object refered by h, idx - * @name getNthChildIndex_AtObj - * @memberOf ASN1HEX - * @function - * @param {String} h hexadecimal string of ASN.1 DER encoded data - * @param {Number} idx start string index of ASN.1 object - * @param {Number} nth for child - * @return {Number} string index of nth child. - * @since 1.1 - */ - this.getNthChildIndex_AtObj = function(h, idx, nth) { - var a = this.getPosArrayOfChildren_AtObj(h, idx); - return a[nth]; - }; - - // ========== decendant methods ============================== - /** - * get string index of nth child object of ASN.1 object refered by h, idx - * @name getDecendantIndexByNthList - * @memberOf ASN1HEX - * @function - * @param {String} h hexadecimal string of ASN.1 DER encoded data - * @param {Number} currentIndex start string index of ASN.1 object - * @param {Array of Number} nthList array list of nth - * @return {Number} string index refered by nthList - * @since 1.1 - * @example - * The "nthList" is a index list of structured ASN.1 object - * reference. Here is a sample structure and "nthList"s which - * refers each objects. - * - * SQUENCE - - * SEQUENCE - [0] - * IA5STRING 000 - [0, 0] - * UTF8STRING 001 - [0, 1] - * SET - [1] - * IA5STRING 010 - [1, 0] - * UTF8STRING 011 - [1, 1] - */ - this.getDecendantIndexByNthList = function(h, currentIndex, nthList) { - if (nthList.length == 0) { - return currentIndex; - } - var firstNth = nthList.shift(); - var a = this.getPosArrayOfChildren_AtObj(h, currentIndex); - return this.getDecendantIndexByNthList(h, a[firstNth], nthList); - }; - - /** - * get hexadecimal string of ASN.1 TLV refered by current index and nth index list. - * @name getDecendantHexTLVByNthList - * @memberOf ASN1HEX - * @function - * @param {String} h hexadecimal string of ASN.1 DER encoded data - * @param {Number} currentIndex start string index of ASN.1 object - * @param {Array of Number} nthList array list of nth - * @return {Number} hexadecimal string of ASN.1 TLV refered by nthList - * @since 1.1 - */ - this.getDecendantHexTLVByNthList = function(h, currentIndex, nthList) { - var idx = this.getDecendantIndexByNthList(h, currentIndex, nthList); - return this.getHexOfTLV_AtObj(h, idx); - }; - - /** - * get hexadecimal string of ASN.1 V refered by current index and nth index list. - * @name getDecendantHexVByNthList - * @memberOf ASN1HEX - * @function - * @param {String} h hexadecimal string of ASN.1 DER encoded data - * @param {Number} currentIndex start string index of ASN.1 object - * @param {Array of Number} nthList array list of nth - * @return {Number} hexadecimal string of ASN.1 V refered by nthList - * @since 1.1 - */ - this.getDecendantHexVByNthList = function(h, currentIndex, nthList) { - var idx = this.getDecendantIndexByNthList(h, currentIndex, nthList); - return this.getHexOfV_AtObj(h, idx); - }; -}; - -/* - * @since asn1hex 1.1.4 - */ -ASN1HEX.getVbyList = function(h, currentIndex, nthList, checkingTag) { - var idx = this.getDecendantIndexByNthList(h, currentIndex, nthList); - if (idx === undefined) { - throw "can't find nthList object"; - } - if (checkingTag !== undefined) { - if (h.substr(idx, 2) != checkingTag) { - throw "checking tag doesn't match: " + - h.substr(idx,2) + "!=" + checkingTag; - } - } - return this.getHexOfV_AtObj(h, idx); -}; - -/** - * get OID string from hexadecimal encoded value - * @name hextooidstr - * @memberOf ASN1HEX - * @function - * @param {String} hex hexadecmal string of ASN.1 DER encoded OID value - * @return {String} OID string (ex. '1.2.3.4.567') - * @since asn1hex 1.1.5 - */ -ASN1HEX.hextooidstr = function(hex) { - var zeroPadding = function(s, len) { - if (s.length >= len) return s; - return new Array(len - s.length + 1).join('0') + s; - }; - - var a = []; - - // a[0], a[1] - var hex0 = hex.substr(0, 2); - var i0 = parseInt(hex0, 16); - a[0] = new String(Math.floor(i0 / 40)); - a[1] = new String(i0 % 40); - - // a[2]..a[n] - var hex1 = hex.substr(2); - var b = []; - for (var i = 0; i < hex1.length / 2; i++) { - b.push(parseInt(hex1.substr(i * 2, 2), 16)); - } - var c = []; - var cbin = ""; - for (var i = 0; i < b.length; i++) { - if (b[i] & 0x80) { - cbin = cbin + zeroPadding((b[i] & 0x7f).toString(2), 7); - } else { - cbin = cbin + zeroPadding((b[i] & 0x7f).toString(2), 7); - c.push(new String(parseInt(cbin, 2))); - cbin = ""; - } - } - - var s = a.join("."); - if (c.length > 0) s = s + "." + c.join("."); - return s; -}; - -/*! x509-1.1.3.js (c) 2012-2014 Kenji Urushima | kjur.github.com/jsrsasign/license - */ -/* - * x509.js - X509 class to read subject public key from certificate. - * - * Copyright (c) 2010-2014 Kenji Urushima (kenji.urushima@gmail.com) - * - * This software is licensed under the terms of the MIT License. - * http://kjur.github.com/jsrsasign/license - * - * The above copyright and license notice shall be - * included in all copies or substantial portions of the Software. - */ - -/** - * @fileOverview - * @name x509-1.1.js - * @author Kenji Urushima kenji.urushima@gmail.com - * @version x509 1.1.3 (2014-May-17) - * @since jsrsasign 1.x.x - * @license MIT License - */ - -/* - * Depends: - * base64.js - * rsa.js - * asn1hex.js - */ - -/** - * X.509 certificate class.
- * @class X.509 certificate class - * @property {RSAKey} subjectPublicKeyRSA Tom Wu's RSAKey object - * @property {String} subjectPublicKeyRSA_hN hexadecimal string for modulus of RSA public key - * @property {String} subjectPublicKeyRSA_hE hexadecimal string for public exponent of RSA public key - * @property {String} hex hexacedimal string for X.509 certificate. - * @author Kenji Urushima - * @version 1.0.1 (08 May 2012) - * @see 'jwrsasign'(RSA Sign JavaScript Library) home page http://kjur.github.com/jsrsasign/ - */ -function X509() { - this.subjectPublicKeyRSA = null; - this.subjectPublicKeyRSA_hN = null; - this.subjectPublicKeyRSA_hE = null; - this.hex = null; - - // ===== get basic fields from hex ===================================== - - /** - * get hexadecimal string of serialNumber field of certificate.
- * @name getSerialNumberHex - * @memberOf X509# - * @function - */ - this.getSerialNumberHex = function() { - return ASN1HEX.getDecendantHexVByNthList(this.hex, 0, [0, 1]); - }; - - /** - * get hexadecimal string of issuer field TLV of certificate.
- * @name getIssuerHex - * @memberOf X509# - * @function - */ - this.getIssuerHex = function() { - return ASN1HEX.getDecendantHexTLVByNthList(this.hex, 0, [0, 3]); - }; - - /** - * get string of issuer field of certificate.
- * @name getIssuerString - * @memberOf X509# - * @function - */ - this.getIssuerString = function() { - return X509.hex2dn(ASN1HEX.getDecendantHexTLVByNthList(this.hex, 0, [0, 3])); - }; - - /** - * get hexadecimal string of subject field of certificate.
- * @name getSubjectHex - * @memberOf X509# - * @function - */ - this.getSubjectHex = function() { - return ASN1HEX.getDecendantHexTLVByNthList(this.hex, 0, [0, 5]); - }; - - /** - * get string of subject field of certificate.
- * @name getSubjectString - * @memberOf X509# - * @function - */ - this.getSubjectString = function() { - return X509.hex2dn(ASN1HEX.getDecendantHexTLVByNthList(this.hex, 0, [0, 5])); - }; - - /** - * get notBefore field string of certificate.
- * @name getNotBefore - * @memberOf X509# - * @function - */ - this.getNotBefore = function() { - var s = ASN1HEX.getDecendantHexVByNthList(this.hex, 0, [0, 4, 0]); - s = s.replace(/(..)/g, "%$1"); - s = decodeURIComponent(s); - return s; - }; - - /** - * get notAfter field string of certificate.
- * @name getNotAfter - * @memberOf X509# - * @function - */ - this.getNotAfter = function() { - var s = ASN1HEX.getDecendantHexVByNthList(this.hex, 0, [0, 4, 1]); - s = s.replace(/(..)/g, "%$1"); - s = decodeURIComponent(s); - return s; - }; - - // ===== read certificate public key ========================== - - // ===== read certificate ===================================== - /** - * read PEM formatted X.509 certificate from string.
- * @name readCertPEM - * @memberOf X509# - * @function - * @param {String} sCertPEM string for PEM formatted X.509 certificate - */ - this.readCertPEM = function(sCertPEM) { - var hCert = X509.pemToHex(sCertPEM); - var a = X509.getPublicKeyHexArrayFromCertHex(hCert); - var rsa = new RSAKey(); - rsa.setPublic(a[0], a[1]); - this.subjectPublicKeyRSA = rsa; - this.subjectPublicKeyRSA_hN = a[0]; - this.subjectPublicKeyRSA_hE = a[1]; - this.hex = hCert; - }; - - this.readCertPEMWithoutRSAInit = function(sCertPEM) { - var hCert = X509.pemToHex(sCertPEM); - var a = X509.getPublicKeyHexArrayFromCertHex(hCert); - this.subjectPublicKeyRSA.setPublic(a[0], a[1]); - this.subjectPublicKeyRSA_hN = a[0]; - this.subjectPublicKeyRSA_hE = a[1]; - this.hex = hCert; - }; -}; - -X509.pemToBase64 = function(sCertPEM) { - var s = sCertPEM; - s = s.replace("-----BEGIN CERTIFICATE-----", ""); - s = s.replace("-----END CERTIFICATE-----", ""); - s = s.replace(/[ \n]+/g, ""); - return s; -}; - -X509.pemToHex = function(sCertPEM) { - var b64Cert = X509.pemToBase64(sCertPEM); - var hCert = b64tohex(b64Cert); - return hCert; -}; - -// NOTE: Without BITSTRING encapsulation. -X509.getSubjectPublicKeyPosFromCertHex = function(hCert) { - var pInfo = X509.getSubjectPublicKeyInfoPosFromCertHex(hCert); - if (pInfo == -1) return -1; - var a = ASN1HEX.getPosArrayOfChildren_AtObj(hCert, pInfo); - if (a.length != 2) return -1; - var pBitString = a[1]; - if (hCert.substring(pBitString, pBitString + 2) != '03') return -1; - var pBitStringV = ASN1HEX.getStartPosOfV_AtObj(hCert, pBitString); - - if (hCert.substring(pBitStringV, pBitStringV + 2) != '00') return -1; - return pBitStringV + 2; -}; - -// NOTE: privateKeyUsagePeriod field of X509v2 not supported. -// NOTE: v1 and v3 supported -X509.getSubjectPublicKeyInfoPosFromCertHex = function(hCert) { - var pTbsCert = ASN1HEX.getStartPosOfV_AtObj(hCert, 0); - var a = ASN1HEX.getPosArrayOfChildren_AtObj(hCert, pTbsCert); - if (a.length < 1) return -1; - if (hCert.substring(a[0], a[0] + 10) == "a003020102") { // v3 - if (a.length < 6) return -1; - return a[6]; - } else { - if (a.length < 5) return -1; - return a[5]; - } -}; - -X509.getPublicKeyHexArrayFromCertHex = function(hCert) { - var p = X509.getSubjectPublicKeyPosFromCertHex(hCert); - var a = ASN1HEX.getPosArrayOfChildren_AtObj(hCert, p); - if (a.length != 2) return []; - var hN = ASN1HEX.getHexOfV_AtObj(hCert, a[0]); - var hE = ASN1HEX.getHexOfV_AtObj(hCert, a[1]); - if (hN != null && hE != null) { - return [hN, hE]; - } else { - return []; - } -}; - -X509.getHexTbsCertificateFromCert = function(hCert) { - var pTbsCert = ASN1HEX.getStartPosOfV_AtObj(hCert, 0); - return pTbsCert; -}; - -X509.getPublicKeyHexArrayFromCertPEM = function(sCertPEM) { - var hCert = X509.pemToHex(sCertPEM); - var a = X509.getPublicKeyHexArrayFromCertHex(hCert); - return a; -}; - -X509.hex2dn = function(hDN) { - var s = ""; - var a = ASN1HEX.getPosArrayOfChildren_AtObj(hDN, 0); - for (var i = 0; i < a.length; i++) { - var hRDN = ASN1HEX.getHexOfTLV_AtObj(hDN, a[i]); - s = s + "/" + X509.hex2rdn(hRDN); - } - return s; -}; - -X509.hex2rdn = function(hRDN) { - var hType = ASN1HEX.getDecendantHexTLVByNthList(hRDN, 0, [0, 0]); - var hValue = ASN1HEX.getDecendantHexVByNthList(hRDN, 0, [0, 1]); - var type = ""; - try { type = X509.DN_ATTRHEX[hType]; } catch (ex) { type = hType; } - hValue = hValue.replace(/(..)/g, "%$1"); - var value = decodeURIComponent(hValue); - return type + "=" + value; -}; - -X509.DN_ATTRHEX = { - "0603550406": "C", - "060355040a": "O", - "060355040b": "OU", - "0603550403": "CN", - "0603550405": "SN", - "0603550408": "ST", - "0603550407": "L", -}; - -/** - * get RSAKey/ECDSA public key object from PEM certificate string - * @name getPublicKeyFromCertPEM - * @memberOf X509 - * @function - * @param {String} sCertPEM PEM formatted RSA/ECDSA/DSA X.509 certificate - * @return returns RSAKey/KJUR.crypto.{ECDSA,DSA} object of public key - * @since x509 1.1.1 - * @description - * NOTE: DSA is also supported since x509 1.1.2. - */ -X509.getPublicKeyFromCertPEM = function(sCertPEM) { - var info = X509.getPublicKeyInfoPropOfCertPEM(sCertPEM); - - if (info.algoid == "2a864886f70d010101") { // RSA - var aRSA = KEYUTIL.parsePublicRawRSAKeyHex(info.keyhex); - var key = new RSAKey(); - key.setPublic(aRSA.n, aRSA.e); - return key; - } else if (info.algoid == "2a8648ce3d0201") { // ECC - var curveName = KJUR.crypto.OID.oidhex2name[info.algparam]; - var key = new KJUR.crypto.ECDSA({'curve': curveName, 'info': info.keyhex}); - key.setPublicKeyHex(info.keyhex); - return key; - } else if (info.algoid == "2a8648ce380401") { // DSA 1.2.840.10040.4.1 - var p = ASN1HEX.getVbyList(info.algparam, 0, [0], "02"); - var q = ASN1HEX.getVbyList(info.algparam, 0, [1], "02"); - var g = ASN1HEX.getVbyList(info.algparam, 0, [2], "02"); - var y = ASN1HEX.getHexOfV_AtObj(info.keyhex, 0); - y = y.substr(2); - var key = new KJUR.crypto.DSA(); - key.setPublic(new BigInteger(p, 16), - new BigInteger(q, 16), - new BigInteger(g, 16), - new BigInteger(y, 16)); - return key; - } else { - throw "unsupported key"; - } -}; - -/** - * get public key information from PEM certificate - * @name getPublicKeyInfoPropOfCertPEM - * @memberOf X509 - * @function - * @param {String} sCertPEM string of PEM formatted certificate - * @return {Hash} hash of information for public key - * @since x509 1.1.1 - * @description - * Resulted associative array has following properties: - *
    - *
  • algoid - hexadecimal string of OID of asymmetric key algorithm
  • - *
  • algparam - hexadecimal string of OID of ECC curve name or null
  • - *
  • keyhex - hexadecimal string of key in the certificate
  • - *
- * @since x509 1.1.1 - */ -X509.getPublicKeyInfoPropOfCertPEM = function(sCertPEM) { - var result = {}; - result.algparam = null; - var hCert = X509.pemToHex(sCertPEM); - - // 1. Certificate ASN.1 - var a1 = ASN1HEX.getPosArrayOfChildren_AtObj(hCert, 0); - if (a1.length != 3) - throw "malformed X.509 certificate PEM (code:001)"; // not 3 item of seq Cert - - // 2. tbsCertificate - if (hCert.substr(a1[0], 2) != "30") - throw "malformed X.509 certificate PEM (code:002)"; // tbsCert not seq - - var a2 = ASN1HEX.getPosArrayOfChildren_AtObj(hCert, a1[0]); - - // 3. subjectPublicKeyInfo - if (a2.length < 7) - throw "malformed X.509 certificate PEM (code:003)"; // no subjPubKeyInfo - - var a3 = ASN1HEX.getPosArrayOfChildren_AtObj(hCert, a2[6]); - - if (a3.length != 2) - throw "malformed X.509 certificate PEM (code:004)"; // not AlgId and PubKey - - // 4. AlgId - var a4 = ASN1HEX.getPosArrayOfChildren_AtObj(hCert, a3[0]); - - if (a4.length != 2) - throw "malformed X.509 certificate PEM (code:005)"; // not 2 item in AlgId - - result.algoid = ASN1HEX.getHexOfV_AtObj(hCert, a4[0]); - - if (hCert.substr(a4[1], 2) == "06") { // EC - result.algparam = ASN1HEX.getHexOfV_AtObj(hCert, a4[1]); - } else if (hCert.substr(a4[1], 2) == "30") { // DSA - result.algparam = ASN1HEX.getHexOfTLV_AtObj(hCert, a4[1]); - } - - // 5. Public Key Hex - if (hCert.substr(a3[1], 2) != "03") - throw "malformed X.509 certificate PEM (code:006)"; // not bitstring - - var unusedBitAndKeyHex = ASN1HEX.getHexOfV_AtObj(hCert, a3[1]); - result.keyhex = unusedBitAndKeyHex.substr(2); - - return result; -}; - -/* - X509.prototype.readCertPEM = _x509_readCertPEM; - X509.prototype.readCertPEMWithoutRSAInit = _x509_readCertPEMWithoutRSAInit; - X509.prototype.getSerialNumberHex = _x509_getSerialNumberHex; - X509.prototype.getIssuerHex = _x509_getIssuerHex; - X509.prototype.getSubjectHex = _x509_getSubjectHex; - X509.prototype.getIssuerString = _x509_getIssuerString; - X509.prototype.getSubjectString = _x509_getSubjectString; - X509.prototype.getNotBefore = _x509_getNotBefore; - X509.prototype.getNotAfter = _x509_getNotAfter; -*/ -/*! crypto-1.1.5.js (c) 2013 Kenji Urushima | kjur.github.com/jsrsasign/license - */ -/* - * crypto.js - Cryptographic Algorithm Provider class - * - * Copyright (c) 2013 Kenji Urushima (kenji.urushima@gmail.com) - * - * This software is licensed under the terms of the MIT License. - * http://kjur.github.com/jsrsasign/license - * - * The above copyright and license notice shall be - * included in all copies or substantial portions of the Software. - */ - -/** - * @fileOverview - * @name crypto-1.1.js - * @author Kenji Urushima kenji.urushima@gmail.com - * @version 1.1.5 (2013-Oct-06) - * @since jsrsasign 2.2 - * @license MIT License - */ - -/** - * kjur's class library name space - * @name KJUR - * @namespace kjur's class library name space - */ -if (typeof KJUR == "undefined" || !KJUR) KJUR = {}; -/** - * kjur's cryptographic algorithm provider library name space - *

- * This namespace privides following crytpgrahic classes. - *

    - *
  • {@link KJUR.crypto.MessageDigest} - Java JCE(cryptograhic extension) style MessageDigest class
  • - *
  • {@link KJUR.crypto.Signature} - Java JCE(cryptograhic extension) style Signature class
  • - *
  • {@link KJUR.crypto.Util} - cryptographic utility functions and properties
  • - *
- * NOTE: Please ignore method summary and document of this namespace. This caused by a bug of jsdoc2. - *

- * @name KJUR.crypto - * @namespace - */ -if (typeof KJUR.crypto == "undefined" || !KJUR.crypto) KJUR.crypto = {}; - -/** - * static object for cryptographic function utilities - * @name KJUR.crypto.Util - * @class static object for cryptographic function utilities - * @property {Array} DIGESTINFOHEAD PKCS#1 DigestInfo heading hexadecimal bytes for each hash algorithms - * @property {Array} DEFAULTPROVIDER associative array of default provider name for each hash and signature algorithms - * @description - */ -KJUR.crypto.Util = new function() { - this.DIGESTINFOHEAD = { - 'sha1': "3021300906052b0e03021a05000414", - 'sha224': "302d300d06096086480165030402040500041c", - 'sha256': "3031300d060960864801650304020105000420", - 'sha384': "3041300d060960864801650304020205000430", - 'sha512': "3051300d060960864801650304020305000440", - 'md2': "3020300c06082a864886f70d020205000410", - 'md5': "3020300c06082a864886f70d020505000410", - 'ripemd160': "3021300906052b2403020105000414", - }; - - /* - * @since crypto 1.1.1 - */ - this.DEFAULTPROVIDER = { - 'md5': 'cryptojs', - 'sha1': 'cryptojs', - 'sha224': 'cryptojs', - 'sha256': 'cryptojs', - 'sha384': 'cryptojs', - 'sha512': 'cryptojs', - 'ripemd160': 'cryptojs', - 'hmacmd5': 'cryptojs', - 'hmacsha1': 'cryptojs', - 'hmacsha224': 'cryptojs', - 'hmacsha256': 'cryptojs', - 'hmacsha384': 'cryptojs', - 'hmacsha512': 'cryptojs', - 'hmacripemd160': 'cryptojs', - - 'MD5withRSA': 'cryptojs/jsrsa', - 'SHA1withRSA': 'cryptojs/jsrsa', - 'SHA224withRSA': 'cryptojs/jsrsa', - 'SHA256withRSA': 'cryptojs/jsrsa', - 'SHA384withRSA': 'cryptojs/jsrsa', - 'SHA512withRSA': 'cryptojs/jsrsa', - 'RIPEMD160withRSA': 'cryptojs/jsrsa', - - 'MD5withECDSA': 'cryptojs/jsrsa', - 'SHA1withECDSA': 'cryptojs/jsrsa', - 'SHA224withECDSA': 'cryptojs/jsrsa', - 'SHA256withECDSA': 'cryptojs/jsrsa', - 'SHA384withECDSA': 'cryptojs/jsrsa', - 'SHA512withECDSA': 'cryptojs/jsrsa', - 'RIPEMD160withECDSA': 'cryptojs/jsrsa', - - 'SHA1withDSA': 'cryptojs/jsrsa', - 'SHA224withDSA': 'cryptojs/jsrsa', - 'SHA256withDSA': 'cryptojs/jsrsa', - - 'MD5withRSAandMGF1': 'cryptojs/jsrsa', - 'SHA1withRSAandMGF1': 'cryptojs/jsrsa', - 'SHA224withRSAandMGF1': 'cryptojs/jsrsa', - 'SHA256withRSAandMGF1': 'cryptojs/jsrsa', - 'SHA384withRSAandMGF1': 'cryptojs/jsrsa', - 'SHA512withRSAandMGF1': 'cryptojs/jsrsa', - 'RIPEMD160withRSAandMGF1': 'cryptojs/jsrsa', - }; - - /* - * @since crypto 1.1.2 - */ - this.CRYPTOJSMESSAGEDIGESTNAME = { - 'md5': 'CryptoJS.algo.MD5', - 'sha1': 'CryptoJS.algo.SHA1', - 'sha224': 'CryptoJS.algo.SHA224', - 'sha256': 'CryptoJS.algo.SHA256', - 'sha384': 'CryptoJS.algo.SHA384', - 'sha512': 'CryptoJS.algo.SHA512', - 'ripemd160': 'CryptoJS.algo.RIPEMD160' - }; - - /** - * get hexadecimal DigestInfo - * @name getDigestInfoHex - * @memberOf KJUR.crypto.Util - * @function - * @param {String} hHash hexadecimal hash value - * @param {String} alg hash algorithm name (ex. 'sha1') - * @return {String} hexadecimal string DigestInfo ASN.1 structure - */ - this.getDigestInfoHex = function(hHash, alg) { - if (typeof this.DIGESTINFOHEAD[alg] == "undefined") - throw "alg not supported in Util.DIGESTINFOHEAD: " + alg; - return this.DIGESTINFOHEAD[alg] + hHash; - }; - - /** - * get PKCS#1 padded hexadecimal DigestInfo - * @name getPaddedDigestInfoHex - * @memberOf KJUR.crypto.Util - * @function - * @param {String} hHash hexadecimal hash value of message to be signed - * @param {String} alg hash algorithm name (ex. 'sha1') - * @param {Integer} keySize key bit length (ex. 1024) - * @return {String} hexadecimal string of PKCS#1 padded DigestInfo - */ - this.getPaddedDigestInfoHex = function(hHash, alg, keySize) { - var hDigestInfo = this.getDigestInfoHex(hHash, alg); - var pmStrLen = keySize / 4; // minimum PM length - - if (hDigestInfo.length + 22 > pmStrLen) // len(0001+ff(*8)+00+hDigestInfo)=22 - throw "key is too short for SigAlg: keylen=" + keySize + "," + alg; - - var hHead = "0001"; - var hTail = "00" + hDigestInfo; - var hMid = ""; - var fLen = pmStrLen - hHead.length - hTail.length; - for (var i = 0; i < fLen; i += 2) { - hMid += "ff"; - } - var hPaddedMessage = hHead + hMid + hTail; - return hPaddedMessage; - }; - - /** - * get hexadecimal hash of string with specified algorithm - * @name hashString - * @memberOf KJUR.crypto.Util - * @function - * @param {String} s input string to be hashed - * @param {String} alg hash algorithm name - * @return {String} hexadecimal string of hash value - * @since 1.1.1 - */ - this.hashString = function(s, alg) { - var md = new KJUR.crypto.MessageDigest({'alg': alg}); - return md.digestString(s); - }; - - /** - * get hexadecimal hash of hexadecimal string with specified algorithm - * @name hashHex - * @memberOf KJUR.crypto.Util - * @function - * @param {String} sHex input hexadecimal string to be hashed - * @param {String} alg hash algorithm name - * @return {String} hexadecimal string of hash value - * @since 1.1.1 - */ - this.hashHex = function(sHex, alg) { - var md = new KJUR.crypto.MessageDigest({'alg': alg}); - return md.digestHex(sHex); - }; - - /** - * get hexadecimal SHA1 hash of string - * @name sha1 - * @memberOf KJUR.crypto.Util - * @function - * @param {String} s input string to be hashed - * @return {String} hexadecimal string of hash value - * @since 1.0.3 - */ - this.sha1 = function(s) { - var md = new KJUR.crypto.MessageDigest({'alg':'sha1', 'prov':'cryptojs'}); - return md.digestString(s); - }; - - /** - * get hexadecimal SHA256 hash of string - * @name sha256 - * @memberOf KJUR.crypto.Util - * @function - * @param {String} s input string to be hashed - * @return {String} hexadecimal string of hash value - * @since 1.0.3 - */ - this.sha256 = function(s) { - var md = new KJUR.crypto.MessageDigest({'alg':'sha256', 'prov':'cryptojs'}); - return md.digestString(s); - }; - - this.sha256Hex = function(s) { - var md = new KJUR.crypto.MessageDigest({'alg':'sha256', 'prov':'cryptojs'}); - return md.digestHex(s); - }; - - /** - * get hexadecimal SHA512 hash of string - * @name sha512 - * @memberOf KJUR.crypto.Util - * @function - * @param {String} s input string to be hashed - * @return {String} hexadecimal string of hash value - * @since 1.0.3 - */ - this.sha512 = function(s) { - var md = new KJUR.crypto.MessageDigest({'alg':'sha512', 'prov':'cryptojs'}); - return md.digestString(s); - }; - - this.sha512Hex = function(s) { - var md = new KJUR.crypto.MessageDigest({'alg':'sha512', 'prov':'cryptojs'}); - return md.digestHex(s); - }; - - /** - * get hexadecimal MD5 hash of string - * @name md5 - * @memberOf KJUR.crypto.Util - * @function - * @param {String} s input string to be hashed - * @return {String} hexadecimal string of hash value - * @since 1.0.3 - */ - this.md5 = function(s) { - var md = new KJUR.crypto.MessageDigest({'alg':'md5', 'prov':'cryptojs'}); - return md.digestString(s); - }; - - /** - * get hexadecimal RIPEMD160 hash of string - * @name ripemd160 - * @memberOf KJUR.crypto.Util - * @function - * @param {String} s input string to be hashed - * @return {String} hexadecimal string of hash value - * @since 1.0.3 - */ - this.ripemd160 = function(s) { - var md = new KJUR.crypto.MessageDigest({'alg':'ripemd160', 'prov':'cryptojs'}); - return md.digestString(s); - }; - - /* - * @since 1.1.2 - */ - this.getCryptoJSMDByName = function(s) { - - }; -}; - -/** - * MessageDigest class which is very similar to java.security.MessageDigest class - * @name KJUR.crypto.MessageDigest - * @class MessageDigest class which is very similar to java.security.MessageDigest class - * @param {Array} params parameters for constructor - * @description - *
- * Currently this supports following algorithm and providers combination: - *
    - *
  • md5 - cryptojs
  • - *
  • sha1 - cryptojs
  • - *
  • sha224 - cryptojs
  • - *
  • sha256 - cryptojs
  • - *
  • sha384 - cryptojs
  • - *
  • sha512 - cryptojs
  • - *
  • ripemd160 - cryptojs
  • - *
  • sha256 - sjcl (NEW from crypto.js 1.0.4)
  • - *
- * @example - * // CryptoJS provider sample - * <script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/components/core.js"></script> - * <script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/components/sha1.js"></script> - * <script src="crypto-1.0.js"></script> - * var md = new KJUR.crypto.MessageDigest({alg: "sha1", prov: "cryptojs"}); - * md.updateString('aaa') - * var mdHex = md.digest() - * - * // SJCL(Stanford JavaScript Crypto Library) provider sample - * <script src="http://bitwiseshiftleft.github.io/sjcl/sjcl.js"></script> - * <script src="crypto-1.0.js"></script> - * var md = new KJUR.crypto.MessageDigest({alg: "sha256", prov: "sjcl"}); // sjcl supports sha256 only - * md.updateString('aaa') - * var mdHex = md.digest() - */ -KJUR.crypto.MessageDigest = function(params) { - var md = null; - var algName = null; - var provName = null; - - /** - * set hash algorithm and provider - * @name setAlgAndProvider - * @memberOf KJUR.crypto.MessageDigest - * @function - * @param {String} alg hash algorithm name - * @param {String} prov provider name - * @description - * @example - * // for SHA1 - * md.setAlgAndProvider('sha1', 'cryptojs'); - * // for RIPEMD160 - * md.setAlgAndProvider('ripemd160', 'cryptojs'); - */ - this.setAlgAndProvider = function(alg, prov) { - if (alg != null && prov === undefined) prov = KJUR.crypto.Util.DEFAULTPROVIDER[alg]; - - // for cryptojs - if (':md5:sha1:sha224:sha256:sha384:sha512:ripemd160:'.indexOf(alg) != -1 && - prov == 'cryptojs') { - try { - this.md = eval(KJUR.crypto.Util.CRYPTOJSMESSAGEDIGESTNAME[alg]).create(); - } catch (ex) { - throw "setAlgAndProvider hash alg set fail alg=" + alg + "/" + ex; - } - this.updateString = function(str) { - this.md.update(str); - }; - this.updateHex = function(hex) { - var wHex = CryptoJS.enc.Hex.parse(hex); - this.md.update(wHex); - }; - this.digest = function() { - var hash = this.md.finalize(); - return hash.toString(CryptoJS.enc.Hex); - }; - this.digestString = function(str) { - this.updateString(str); - return this.digest(); - }; - this.digestHex = function(hex) { - this.updateHex(hex); - return this.digest(); - }; - } - if (':sha256:'.indexOf(alg) != -1 && - prov == 'sjcl') { - try { - this.md = new sjcl.hash.sha256(); - } catch (ex) { - throw "setAlgAndProvider hash alg set fail alg=" + alg + "/" + ex; - } - this.updateString = function(str) { - this.md.update(str); - }; - this.updateHex = function(hex) { - var baHex = sjcl.codec.hex.toBits(hex); - this.md.update(baHex); - }; - this.digest = function() { - var hash = this.md.finalize(); - return sjcl.codec.hex.fromBits(hash); - }; - this.digestString = function(str) { - this.updateString(str); - return this.digest(); - }; - this.digestHex = function(hex) { - this.updateHex(hex); - return this.digest(); - }; - } - }; - - /** - * update digest by specified string - * @name updateString - * @memberOf KJUR.crypto.MessageDigest - * @function - * @param {String} str string to update - * @description - * @example - * md.updateString('New York'); - */ - this.updateString = function(str) { - throw "updateString(str) not supported for this alg/prov: " + this.algName + "/" + this.provName; - }; - - /** - * update digest by specified hexadecimal string - * @name updateHex - * @memberOf KJUR.crypto.MessageDigest - * @function - * @param {String} hex hexadecimal string to update - * @description - * @example - * md.updateHex('0afe36'); - */ - this.updateHex = function(hex) { - throw "updateHex(hex) not supported for this alg/prov: " + this.algName + "/" + this.provName; - }; - - /** - * completes hash calculation and returns hash result - * @name digest - * @memberOf KJUR.crypto.MessageDigest - * @function - * @description - * @example - * md.digest() - */ - this.digest = function() { - throw "digest() not supported for this alg/prov: " + this.algName + "/" + this.provName; - }; - - /** - * performs final update on the digest using string, then completes the digest computation - * @name digestString - * @memberOf KJUR.crypto.MessageDigest - * @function - * @param {String} str string to final update - * @description - * @example - * md.digestString('aaa') - */ - this.digestString = function(str) { - throw "digestString(str) not supported for this alg/prov: " + this.algName + "/" + this.provName; - }; - - /** - * performs final update on the digest using hexadecimal string, then completes the digest computation - * @name digestHex - * @memberOf KJUR.crypto.MessageDigest - * @function - * @param {String} hex hexadecimal string to final update - * @description - * @example - * md.digestHex('0f2abd') - */ - this.digestHex = function(hex) { - throw "digestHex(hex) not supported for this alg/prov: " + this.algName + "/" + this.provName; - }; - - if (params !== undefined) { - if (params['alg'] !== undefined) { - this.algName = params['alg']; - if (params['prov'] === undefined) - this.provName = KJUR.crypto.Util.DEFAULTPROVIDER[this.algName]; - this.setAlgAndProvider(this.algName, this.provName); - } - } -}; - -/** - * Mac(Message Authentication Code) class which is very similar to java.security.Mac class - * @name KJUR.crypto.Mac - * @class Mac class which is very similar to java.security.Mac class - * @param {Array} params parameters for constructor - * @description - *
- * Currently this supports following algorithm and providers combination: - *
    - *
  • hmacmd5 - cryptojs
  • - *
  • hmacsha1 - cryptojs
  • - *
  • hmacsha224 - cryptojs
  • - *
  • hmacsha256 - cryptojs
  • - *
  • hmacsha384 - cryptojs
  • - *
  • hmacsha512 - cryptojs
  • - *
- * NOTE: HmacSHA224 and HmacSHA384 issue was fixed since jsrsasign 4.1.4. - * Please use 'ext/cryptojs-312-core-fix*.js' instead of 'core.js' of original CryptoJS - * to avoid those issue. - * @example - * var mac = new KJUR.crypto.Mac({alg: "HmacSHA1", prov: "cryptojs", "pass": "pass"}); - * mac.updateString('aaa') - * var macHex = md.doFinal() - */ -KJUR.crypto.Mac = function(params) { - var mac = null; - var pass = null; - var algName = null; - var provName = null; - var algProv = null; - - this.setAlgAndProvider = function(alg, prov) { - if (alg == null) alg = "hmacsha1"; - - alg = alg.toLowerCase(); - if (alg.substr(0, 4) != "hmac") { - throw "setAlgAndProvider unsupported HMAC alg: " + alg; - } - - if (prov === undefined) prov = KJUR.crypto.Util.DEFAULTPROVIDER[alg]; - this.algProv = alg + "/" + prov; - - var hashAlg = alg.substr(4); - - // for cryptojs - if (':md5:sha1:sha224:sha256:sha384:sha512:ripemd160:'.indexOf(hashAlg) != -1 && - prov == 'cryptojs') { - try { - var mdObj = eval(KJUR.crypto.Util.CRYPTOJSMESSAGEDIGESTNAME[hashAlg]); - this.mac = CryptoJS.algo.HMAC.create(mdObj, this.pass); - } catch (ex) { - throw "setAlgAndProvider hash alg set fail hashAlg=" + hashAlg + "/" + ex; - } - this.updateString = function(str) { - this.mac.update(str); - }; - this.updateHex = function(hex) { - var wHex = CryptoJS.enc.Hex.parse(hex); - this.mac.update(wHex); - }; - this.doFinal = function() { - var hash = this.mac.finalize(); - return hash.toString(CryptoJS.enc.Hex); - }; - this.doFinalString = function(str) { - this.updateString(str); - return this.doFinal(); - }; - this.doFinalHex = function(hex) { - this.updateHex(hex); - return this.doFinal(); - }; - } - }; - - /** - * update digest by specified string - * @name updateString - * @memberOf KJUR.crypto.Mac - * @function - * @param {String} str string to update - * @description - * @example - * md.updateString('New York'); - */ - this.updateString = function(str) { - throw "updateString(str) not supported for this alg/prov: " + this.algProv; - }; - - /** - * update digest by specified hexadecimal string - * @name updateHex - * @memberOf KJUR.crypto.Mac - * @function - * @param {String} hex hexadecimal string to update - * @description - * @example - * md.updateHex('0afe36'); - */ - this.updateHex = function(hex) { - throw "updateHex(hex) not supported for this alg/prov: " + this.algProv; - }; - - /** - * completes hash calculation and returns hash result - * @name doFinal - * @memberOf KJUR.crypto.Mac - * @function - * @description - * @example - * md.digest() - */ - this.doFinal = function() { - throw "digest() not supported for this alg/prov: " + this.algProv; - }; - - /** - * performs final update on the digest using string, then completes the digest computation - * @name doFinalString - * @memberOf KJUR.crypto.Mac - * @function - * @param {String} str string to final update - * @description - * @example - * md.digestString('aaa') - */ - this.doFinalString = function(str) { - throw "digestString(str) not supported for this alg/prov: " + this.algProv; - }; - - /** - * performs final update on the digest using hexadecimal string, - * then completes the digest computation - * @name doFinalHex - * @memberOf KJUR.crypto.Mac - * @function - * @param {String} hex hexadecimal string to final update - * @description - * @example - * md.digestHex('0f2abd') - */ - this.doFinalHex = function(hex) { - throw "digestHex(hex) not supported for this alg/prov: " + this.algProv; - }; - - if (params !== undefined) { - if (params['pass'] !== undefined) { - this.pass = params['pass']; - } - if (params['alg'] !== undefined) { - this.algName = params['alg']; - if (params['prov'] === undefined) - this.provName = KJUR.crypto.Util.DEFAULTPROVIDER[this.algName]; - this.setAlgAndProvider(this.algName, this.provName); - } - } -}; - -/** - * Signature class which is very similar to java.security.Signature class - * @name KJUR.crypto.Signature - * @class Signature class which is very similar to java.security.Signature class - * @param {Array} params parameters for constructor - * @property {String} state Current state of this signature object whether 'SIGN', 'VERIFY' or null - * @description - *
- * As for params of constructor's argument, it can be specify following attributes: - *
    - *
  • alg - signature algorithm name (ex. {MD5,SHA1,SHA224,SHA256,SHA384,SHA512,RIPEMD160}with{RSA,ECDSA,DSA})
  • - *
  • provider - currently 'cryptojs/jsrsa' only
  • - *
- *

SUPPORTED ALGORITHMS AND PROVIDERS

- * This Signature class supports following signature algorithm and provider names: - *
    - *
  • MD5withRSA - cryptojs/jsrsa
  • - *
  • SHA1withRSA - cryptojs/jsrsa
  • - *
  • SHA224withRSA - cryptojs/jsrsa
  • - *
  • SHA256withRSA - cryptojs/jsrsa
  • - *
  • SHA384withRSA - cryptojs/jsrsa
  • - *
  • SHA512withRSA - cryptojs/jsrsa
  • - *
  • RIPEMD160withRSA - cryptojs/jsrsa
  • - *
  • MD5withECDSA - cryptojs/jsrsa
  • - *
  • SHA1withECDSA - cryptojs/jsrsa
  • - *
  • SHA224withECDSA - cryptojs/jsrsa
  • - *
  • SHA256withECDSA - cryptojs/jsrsa
  • - *
  • SHA384withECDSA - cryptojs/jsrsa
  • - *
  • SHA512withECDSA - cryptojs/jsrsa
  • - *
  • RIPEMD160withECDSA - cryptojs/jsrsa
  • - *
  • MD5withRSAandMGF1 - cryptojs/jsrsa
  • - *
  • SHA1withRSAandMGF1 - cryptojs/jsrsa
  • - *
  • SHA224withRSAandMGF1 - cryptojs/jsrsa
  • - *
  • SHA256withRSAandMGF1 - cryptojs/jsrsa
  • - *
  • SHA384withRSAandMGF1 - cryptojs/jsrsa
  • - *
  • SHA512withRSAandMGF1 - cryptojs/jsrsa
  • - *
  • RIPEMD160withRSAandMGF1 - cryptojs/jsrsa
  • - *
  • SHA1withDSA - cryptojs/jsrsa
  • - *
  • SHA224withDSA - cryptojs/jsrsa
  • - *
  • SHA256withDSA - cryptojs/jsrsa
  • - *
- * Here are supported elliptic cryptographic curve names and their aliases for ECDSA: - *
    - *
  • secp256k1
  • - *
  • secp256r1, NIST P-256, P-256, prime256v1
  • - *
  • secp384r1, NIST P-384, P-384
  • - *
- * NOTE1: DSA signing algorithm is also supported since crypto 1.1.5. - *

EXAMPLES

- * @example - * // RSA signature generation - * var sig = new KJUR.crypto.Signature({"alg": "SHA1withRSA"}); - * sig.init(prvKeyPEM); - * sig.updateString('aaa'); - * var hSigVal = sig.sign(); - * - * // DSA signature validation - * var sig2 = new KJUR.crypto.Signature({"alg": "SHA1withDSA"}); - * sig2.init(certPEM); - * sig.updateString('aaa'); - * var isValid = sig2.verify(hSigVal); - * - * // ECDSA signing - * var sig = new KJUR.crypto.Signature({'alg':'SHA1withECDSA'}); - * sig.init(prvKeyPEM); - * sig.updateString('aaa'); - * var sigValueHex = sig.sign(); - * - * // ECDSA verifying - * var sig2 = new KJUR.crypto.Signature({'alg':'SHA1withECDSA'}); - * sig.init(certPEM); - * sig.updateString('aaa'); - * var isValid = sig.verify(sigValueHex); - */ -KJUR.crypto.Signature = function(params) { - var prvKey = null; // RSAKey/KJUR.crypto.{ECDSA,DSA} object for signing - var pubKey = null; // RSAKey/KJUR.crypto.{ECDSA,DSA} object for verifying - - var md = null; // KJUR.crypto.MessageDigest object - var sig = null; - var algName = null; - var provName = null; - var algProvName = null; - var mdAlgName = null; - var pubkeyAlgName = null; // rsa,ecdsa,rsaandmgf1(=rsapss) - var state = null; - var pssSaltLen = -1; - var initParams = null; - - var sHashHex = null; // hex hash value for hex - var hDigestInfo = null; - var hPaddedDigestInfo = null; - var hSign = null; - - this._setAlgNames = function() { - if (this.algName.match(/^(.+)with(.+)$/)) { - this.mdAlgName = RegExp.$1.toLowerCase(); - this.pubkeyAlgName = RegExp.$2.toLowerCase(); - } - }; - - this._zeroPaddingOfSignature = function(hex, bitLength) { - var s = ""; - var nZero = bitLength / 4 - hex.length; - for (var i = 0; i < nZero; i++) { - s = s + "0"; - } - return s + hex; - }; - - /** - * set signature algorithm and provider - * @name setAlgAndProvider - * @memberOf KJUR.crypto.Signature - * @function - * @param {String} alg signature algorithm name - * @param {String} prov provider name - * @description - * @example - * md.setAlgAndProvider('SHA1withRSA', 'cryptojs/jsrsa'); - */ - this.setAlgAndProvider = function(alg, prov) { - this._setAlgNames(); - if (prov != 'cryptojs/jsrsa') - throw "provider not supported: " + prov; - - if (':md5:sha1:sha224:sha256:sha384:sha512:ripemd160:'.indexOf(this.mdAlgName) != -1) { - try { - this.md = new KJUR.crypto.MessageDigest({'alg':this.mdAlgName}); - } catch (ex) { - throw "setAlgAndProvider hash alg set fail alg=" + - this.mdAlgName + "/" + ex; - } - - this.init = function(keyparam, pass) { - var keyObj = null; - try { - if (pass === undefined) { - keyObj = KEYUTIL.getKey(keyparam); - } else { - keyObj = KEYUTIL.getKey(keyparam, pass); - } - } catch (ex) { - throw "init failed:" + ex; - } - - if (keyObj.isPrivate === true) { - this.prvKey = keyObj; - this.state = "SIGN"; - } else if (keyObj.isPublic === true) { - this.pubKey = keyObj; - this.state = "VERIFY"; - } else { - throw "init failed.:" + keyObj; - } - }; - - this.initSign = function(params) { - if (typeof params['ecprvhex'] == 'string' && - typeof params['eccurvename'] == 'string') { - this.ecprvhex = params['ecprvhex']; - this.eccurvename = params['eccurvename']; - } else { - this.prvKey = params; - } - this.state = "SIGN"; - }; - - this.initVerifyByPublicKey = function(params) { - if (typeof params['ecpubhex'] == 'string' && - typeof params['eccurvename'] == 'string') { - this.ecpubhex = params['ecpubhex']; - this.eccurvename = params['eccurvename']; - } else if (params instanceof KJUR.crypto.ECDSA) { - this.pubKey = params; - } else if (params instanceof RSAKey) { - this.pubKey = params; - } - this.state = "VERIFY"; - }; - - this.initVerifyByCertificatePEM = function(certPEM) { - var x509 = new X509(); - x509.readCertPEM(certPEM); - this.pubKey = x509.subjectPublicKeyRSA; - this.state = "VERIFY"; - }; - - this.updateString = function(str) { - this.md.updateString(str); - }; - this.updateHex = function(hex) { - this.md.updateHex(hex); - }; - - this.sign = function() { - this.sHashHex = this.md.digest(); - if (typeof this.ecprvhex != "undefined" && - typeof this.eccurvename != "undefined") { - var ec = new KJUR.crypto.ECDSA({'curve': this.eccurvename}); - this.hSign = ec.signHex(this.sHashHex, this.ecprvhex); - } else if (this.pubkeyAlgName == "rsaandmgf1") { - this.hSign = this.prvKey.signWithMessageHashPSS(this.sHashHex, - this.mdAlgName, - this.pssSaltLen); - } else if (this.pubkeyAlgName == "rsa") { - this.hSign = this.prvKey.signWithMessageHash(this.sHashHex, - this.mdAlgName); - } else if (this.prvKey instanceof KJUR.crypto.ECDSA) { - this.hSign = this.prvKey.signWithMessageHash(this.sHashHex); - } else if (this.prvKey instanceof KJUR.crypto.DSA) { - this.hSign = this.prvKey.signWithMessageHash(this.sHashHex); - } else { - throw "Signature: unsupported public key alg: " + this.pubkeyAlgName; - } - return this.hSign; - }; - this.signString = function(str) { - this.updateString(str); - return this.sign(); - }; - this.signHex = function(hex) { - this.updateHex(hex); - return this.sign(); - }; - this.verify = function(hSigVal) { - this.sHashHex = this.md.digest(); - if (typeof this.ecpubhex != "undefined" && - typeof this.eccurvename != "undefined") { - var ec = new KJUR.crypto.ECDSA({curve: this.eccurvename}); - return ec.verifyHex(this.sHashHex, hSigVal, this.ecpubhex); - } else if (this.pubkeyAlgName == "rsaandmgf1") { - return this.pubKey.verifyWithMessageHashPSS(this.sHashHex, hSigVal, - this.mdAlgName, - this.pssSaltLen); - } else if (this.pubkeyAlgName == "rsa") { - return this.pubKey.verifyWithMessageHash(this.sHashHex, hSigVal); - } else if (this.pubKey instanceof KJUR.crypto.ECDSA) { - return this.pubKey.verifyWithMessageHash(this.sHashHex, hSigVal); - } else if (this.pubKey instanceof KJUR.crypto.DSA) { - return this.pubKey.verifyWithMessageHash(this.sHashHex, hSigVal); - } else { - throw "Signature: unsupported public key alg: " + this.pubkeyAlgName; - } - }; - } - }; - - /** - * Initialize this object for signing or verifying depends on key - * @name init - * @memberOf KJUR.crypto.Signature - * @function - * @param {Object} key specifying public or private key as plain/encrypted PKCS#5/8 PEM file, certificate PEM or {@link RSAKey}, {@link KJUR.crypto.DSA} or {@link KJUR.crypto.ECDSA} object - * @param {String} pass (OPTION) passcode for encrypted private key - * @since crypto 1.1.3 - * @description - * This method is very useful initialize method for Signature class since - * you just specify key then this method will automatically initialize it - * using {@link KEYUTIL.getKey} method. - * As for 'key', following argument type are supported: - *
signing
- *
    - *
  • PEM formatted PKCS#8 encrypted RSA/ECDSA private key concluding "BEGIN ENCRYPTED PRIVATE KEY"
  • - *
  • PEM formatted PKCS#5 encrypted RSA/DSA private key concluding "BEGIN RSA/DSA PRIVATE KEY" and ",ENCRYPTED"
  • - *
  • PEM formatted PKCS#8 plain RSA/ECDSA private key concluding "BEGIN PRIVATE KEY"
  • - *
  • PEM formatted PKCS#5 plain RSA/DSA private key concluding "BEGIN RSA/DSA PRIVATE KEY" without ",ENCRYPTED"
  • - *
  • RSAKey object of private key
  • - *
  • KJUR.crypto.ECDSA object of private key
  • - *
  • KJUR.crypto.DSA object of private key
  • - *
- *
verification
- *
    - *
  • PEM formatted PKCS#8 RSA/EC/DSA public key concluding "BEGIN PUBLIC KEY"
  • - *
  • PEM formatted X.509 certificate with RSA/EC/DSA public key concluding - * "BEGIN CERTIFICATE", "BEGIN X509 CERTIFICATE" or "BEGIN TRUSTED CERTIFICATE".
  • - *
  • RSAKey object of public key
  • - *
  • KJUR.crypto.ECDSA object of public key
  • - *
  • KJUR.crypto.DSA object of public key
  • - *
- * @example - * sig.init(sCertPEM) - */ - this.init = function(key, pass) { - throw "init(key, pass) not supported for this alg:prov=" + - this.algProvName; - }; - - /** - * Initialize this object for verifying with a public key - * @name initVerifyByPublicKey - * @memberOf KJUR.crypto.Signature - * @function - * @param {Object} param RSAKey object of public key or associative array for ECDSA - * @since 1.0.2 - * @deprecated from crypto 1.1.5. please use init() method instead. - * @description - * Public key information will be provided as 'param' parameter and the value will be - * following: - *
    - *
  • {@link RSAKey} object for RSA verification
  • - *
  • associative array for ECDSA verification - * (ex. {'ecpubhex': '041f..', 'eccurvename': 'secp256r1'}) - *
  • - *
- * @example - * sig.initVerifyByPublicKey(rsaPrvKey) - */ - this.initVerifyByPublicKey = function(rsaPubKey) { - throw "initVerifyByPublicKey(rsaPubKeyy) not supported for this alg:prov=" + - this.algProvName; - }; - - /** - * Initialize this object for verifying with a certficate - * @name initVerifyByCertificatePEM - * @memberOf KJUR.crypto.Signature - * @function - * @param {String} certPEM PEM formatted string of certificate - * @since 1.0.2 - * @deprecated from crypto 1.1.5. please use init() method instead. - * @description - * @example - * sig.initVerifyByCertificatePEM(certPEM) - */ - this.initVerifyByCertificatePEM = function(certPEM) { - throw "initVerifyByCertificatePEM(certPEM) not supported for this alg:prov=" + - this.algProvName; - }; - - /** - * Initialize this object for signing - * @name initSign - * @memberOf KJUR.crypto.Signature - * @function - * @param {Object} param RSAKey object of public key or associative array for ECDSA - * @deprecated from crypto 1.1.5. please use init() method instead. - * @description - * Private key information will be provided as 'param' parameter and the value will be - * following: - *
    - *
  • {@link RSAKey} object for RSA signing
  • - *
  • associative array for ECDSA signing - * (ex. {'ecprvhex': '1d3f..', 'eccurvename': 'secp256r1'})
  • - *
- * @example - * sig.initSign(prvKey) - */ - this.initSign = function(prvKey) { - throw "initSign(prvKey) not supported for this alg:prov=" + this.algProvName; - }; - - /** - * Updates the data to be signed or verified by a string - * @name updateString - * @memberOf KJUR.crypto.Signature - * @function - * @param {String} str string to use for the update - * @description - * @example - * sig.updateString('aaa') - */ - this.updateString = function(str) { - throw "updateString(str) not supported for this alg:prov=" + this.algProvName; - }; - - /** - * Updates the data to be signed or verified by a hexadecimal string - * @name updateHex - * @memberOf KJUR.crypto.Signature - * @function - * @param {String} hex hexadecimal string to use for the update - * @description - * @example - * sig.updateHex('1f2f3f') - */ - this.updateHex = function(hex) { - throw "updateHex(hex) not supported for this alg:prov=" + this.algProvName; - }; - - /** - * Returns the signature bytes of all data updates as a hexadecimal string - * @name sign - * @memberOf KJUR.crypto.Signature - * @function - * @return the signature bytes as a hexadecimal string - * @description - * @example - * var hSigValue = sig.sign() - */ - this.sign = function() { - throw "sign() not supported for this alg:prov=" + this.algProvName; - }; - - /** - * performs final update on the sign using string, then returns the signature bytes of all data updates as a hexadecimal string - * @name signString - * @memberOf KJUR.crypto.Signature - * @function - * @param {String} str string to final update - * @return the signature bytes of a hexadecimal string - * @description - * @example - * var hSigValue = sig.signString('aaa') - */ - this.signString = function(str) { - throw "digestString(str) not supported for this alg:prov=" + this.algProvName; - }; - - /** - * performs final update on the sign using hexadecimal string, then returns the signature bytes of all data updates as a hexadecimal string - * @name signHex - * @memberOf KJUR.crypto.Signature - * @function - * @param {String} hex hexadecimal string to final update - * @return the signature bytes of a hexadecimal string - * @description - * @example - * var hSigValue = sig.signHex('1fdc33') - */ - this.signHex = function(hex) { - throw "digestHex(hex) not supported for this alg:prov=" + this.algProvName; - }; - - /** - * verifies the passed-in signature. - * @name verify - * @memberOf KJUR.crypto.Signature - * @function - * @param {String} str string to final update - * @return {Boolean} true if the signature was verified, otherwise false - * @description - * @example - * var isValid = sig.verify('1fbcefdca4823a7(snip)') - */ - this.verify = function(hSigVal) { - throw "verify(hSigVal) not supported for this alg:prov=" + this.algProvName; - }; - - this.initParams = params; - - if (params !== undefined) { - if (params['alg'] !== undefined) { - this.algName = params['alg']; - if (params['prov'] === undefined) { - this.provName = KJUR.crypto.Util.DEFAULTPROVIDER[this.algName]; - } else { - this.provName = params['prov']; - } - this.algProvName = this.algName + ":" + this.provName; - this.setAlgAndProvider(this.algName, this.provName); - this._setAlgNames(); - } - - if (params['psssaltlen'] !== undefined) this.pssSaltLen = params['psssaltlen']; - - if (params['prvkeypem'] !== undefined) { - if (params['prvkeypas'] !== undefined) { - throw "both prvkeypem and prvkeypas parameters not supported"; - } else { - try { - var prvKey = new RSAKey(); - prvKey.readPrivateKeyFromPEMString(params['prvkeypem']); - this.initSign(prvKey); - } catch (ex) { - throw "fatal error to load pem private key: " + ex; - } - } - } - } -}; - -/** - * static object for cryptographic function utilities - * @name KJUR.crypto.OID - * @class static object for cryptography related OIDs - * @property {Array} oidhex2name key value of hexadecimal OID and its name - * (ex. '2a8648ce3d030107' and 'secp256r1') - * @since crypto 1.1.3 - * @description - */ - - -KJUR.crypto.OID = new function() { - this.oidhex2name = { - '2a864886f70d010101': 'rsaEncryption', - '2a8648ce3d0201': 'ecPublicKey', - '2a8648ce380401': 'dsa', - '2a8648ce3d030107': 'secp256r1', - '2b8104001f': 'secp192k1', - '2b81040021': 'secp224r1', - '2b8104000a': 'secp256k1', - '2b81040023': 'secp521r1', - '2b81040022': 'secp384r1', - '2a8648ce380403': 'SHA1withDSA', // 1.2.840.10040.4.3 - '608648016503040301': 'SHA224withDSA', // 2.16.840.1.101.3.4.3.1 - '608648016503040302': 'SHA256withDSA', // 2.16.840.1.101.3.4.3.2 - }; -}; - -/*! base64x-1.1.3 (c) 2012-2014 Kenji Urushima | kjur.github.com/jsjws/license - */ -/* - * base64x.js - Base64url and supplementary functions for Tom Wu's base64.js library - * - * version: 1.1.3 (2014 May 25) - * - * Copyright (c) 2012-2014 Kenji Urushima (kenji.urushima@gmail.com) - * - * This software is licensed under the terms of the MIT License. - * http://kjur.github.com/jsjws/license/ - * - * The above copyright and license notice shall be - * included in all copies or substantial portions of the Software. - * - * DEPENDS ON: - * - base64.js - Tom Wu's Base64 library - */ - -/** - * Base64URL and supplementary functions for Tom Wu's base64.js library.
- * This class is just provide information about global functions - * defined in 'base64x.js'. The 'base64x.js' script file provides - * global functions for converting following data each other. - *
    - *
  • (ASCII) String
  • - *
  • UTF8 String including CJK, Latin and other characters
  • - *
  • byte array
  • - *
  • hexadecimal encoded String
  • - *
  • Full URIComponent encoded String (such like "%69%94")
  • - *
  • Base64 encoded String
  • - *
  • Base64URL encoded String
  • - *
- * All functions in 'base64x.js' are defined in {@link _global_} and not - * in this class. - * - * @class Base64URL and supplementary functions for Tom Wu's base64.js library - * @author Kenji Urushima - * @version 1.1 (07 May 2012) - * @requires base64.js - * @see 'jwjws'(JWS JavaScript Library) home page http://kjur.github.com/jsjws/ - * @see 'jwrsasign'(RSA Sign JavaScript Library) home page http://kjur.github.com/jsrsasign/ - */ -function Base64x() { -} - -// ==== string / byte array ================================ -/** - * convert a string to an array of character codes - * @param {String} s - * @return {Array of Numbers} - */ -function stoBA(s) { - var a = new Array(); - for (var i = 0; i < s.length; i++) { - a[i] = s.charCodeAt(i); - } - return a; -} - -/** - * convert an array of character codes to a string - * @param {Array of Numbers} a array of character codes - * @return {String} s - */ -function BAtos(a) { - var s = ""; - for (var i = 0; i < a.length; i++) { - s = s + String.fromCharCode(a[i]); - } - return s; -} - -// ==== byte array / hex ================================ -/** - * convert an array of bytes(Number) to hexadecimal string.
- * @param {Array of Numbers} a array of bytes - * @return {String} hexadecimal string - */ -function BAtohex(a) { - var s = ""; - for (var i = 0; i < a.length; i++) { - var hex1 = a[i].toString(16); - if (hex1.length == 1) hex1 = "0" + hex1; - s = s + hex1; - } - return s; -} - -// ==== string / hex ================================ -/** - * convert a ASCII string to a hexadecimal string of ASCII codes.
- * NOTE: This can't be used for non ASCII characters. - * @param {s} s ASCII string - * @return {String} hexadecimal string - */ -function stohex(s) { - return BAtohex(stoBA(s)); -} - -// ==== string / base64 ================================ -/** - * convert a ASCII string to a Base64 encoded string.
- * NOTE: This can't be used for non ASCII characters. - * @param {s} s ASCII string - * @return {String} Base64 encoded string - */ -function stob64(s) { - return hex2b64(stohex(s)); -} - -// ==== string / base64url ================================ -/** - * convert a ASCII string to a Base64URL encoded string.
- * NOTE: This can't be used for non ASCII characters. - * @param {s} s ASCII string - * @return {String} Base64URL encoded string - */ -function stob64u(s) { - return b64tob64u(hex2b64(stohex(s))); -} - -/** - * convert a Base64URL encoded string to a ASCII string.
- * NOTE: This can't be used for Base64URL encoded non ASCII characters. - * @param {s} s Base64URL encoded string - * @return {String} ASCII string - */ -function b64utos(s) { - return BAtos(b64toBA(b64utob64(s))); -} - -// ==== base64 / base64url ================================ -/** - * convert a Base64 encoded string to a Base64URL encoded string.
- * Example: "ab+c3f/==" → "ab-c3f_" - * @param {String} s Base64 encoded string - * @return {String} Base64URL encoded string - */ -function b64tob64u(s) { - s = s.replace(/\=/g, ""); - s = s.replace(/\+/g, "-"); - s = s.replace(/\//g, "_"); - return s; -} - -/** - * convert a Base64URL encoded string to a Base64 encoded string.
- * Example: "ab-c3f_" → "ab+c3f/==" - * @param {String} s Base64URL encoded string - * @return {String} Base64 encoded string - */ -function b64utob64(s) { - if (s.length % 4 == 2) s = s + "=="; - else if (s.length % 4 == 3) s = s + "="; - s = s.replace(/-/g, "+"); - s = s.replace(/_/g, "/"); - return s; -} - -// ==== hex / base64url ================================ -/** - * convert a hexadecimal string to a Base64URL encoded string.
- * @param {String} s hexadecimal string - * @return {String} Base64URL encoded string - */ -function hextob64u(s) { - return b64tob64u(hex2b64(s)); -} - -/** - * convert a Base64URL encoded string to a hexadecimal string.
- * @param {String} s Base64URL encoded string - * @return {String} hexadecimal string - */ -function b64utohex(s) { - return b64tohex(b64utob64(s)); -} - -var utf8tob64u, b64utoutf8; - -if (typeof Buffer === 'function') -{ - utf8tob64u = function (s) - { - return b64tob64u(new Buffer(s, 'utf8').toString('base64')); - }; - - b64utoutf8 = function (s) - { - return new Buffer(b64utob64(s), 'base64').toString('utf8'); - }; -} -else -{ -// ==== utf8 / base64url ================================ -/** - * convert a UTF-8 encoded string including CJK or Latin to a Base64URL encoded string.
- * @param {String} s UTF-8 encoded string - * @return {String} Base64URL encoded string - * @since 1.1 - */ - utf8tob64u = function (s) - { - return hextob64u(uricmptohex(encodeURIComponentAll(s))); - }; - -/** - * convert a Base64URL encoded string to a UTF-8 encoded string including CJK or Latin.
- * @param {String} s Base64URL encoded string - * @return {String} UTF-8 encoded string - * @since 1.1 - */ - b64utoutf8 = function (s) - { - return decodeURIComponent(hextouricmp(b64utohex(s))); - }; -} - -// ==== utf8 / base64url ================================ -/** - * convert a UTF-8 encoded string including CJK or Latin to a Base64 encoded string.
- * @param {String} s UTF-8 encoded string - * @return {String} Base64 encoded string - * @since 1.1.1 - */ -function utf8tob64(s) { - return hex2b64(uricmptohex(encodeURIComponentAll(s))); -} - -/** - * convert a Base64 encoded string to a UTF-8 encoded string including CJK or Latin.
- * @param {String} s Base64 encoded string - * @return {String} UTF-8 encoded string - * @since 1.1.1 - */ -function b64toutf8(s) { - return decodeURIComponent(hextouricmp(b64tohex(s))); -} - -// ==== utf8 / hex ================================ -/** - * convert a UTF-8 encoded string including CJK or Latin to a hexadecimal encoded string.
- * @param {String} s UTF-8 encoded string - * @return {String} hexadecimal encoded string - * @since 1.1.1 - */ -function utf8tohex(s) { - return uricmptohex(encodeURIComponentAll(s)); -} - -/** - * convert a hexadecimal encoded string to a UTF-8 encoded string including CJK or Latin.
- * Note that when input is improper hexadecimal string as UTF-8 string, this function returns - * 'null'. - * @param {String} s hexadecimal encoded string - * @return {String} UTF-8 encoded string or null - * @since 1.1.1 - */ -function hextoutf8(s) { - return decodeURIComponent(hextouricmp(s)); -} - -/** - * convert a hexadecimal encoded string to raw string including non printable characters.
- * @param {String} s hexadecimal encoded string - * @return {String} raw string - * @since 1.1.2 - * @example - * hextorstr("610061") → "a\x00a" - */ -function hextorstr(sHex) { - var s = ""; - for (var i = 0; i < sHex.length - 1; i += 2) { - s += String.fromCharCode(parseInt(sHex.substr(i, 2), 16)); - } - return s; -} - -/** - * convert a raw string including non printable characters to hexadecimal encoded string.
- * @param {String} s raw string - * @return {String} hexadecimal encoded string - * @since 1.1.2 - * @example - * rstrtohex("a\x00a") → "610061" - */ -function rstrtohex(s) { - var result = ""; - for (var i = 0; i < s.length; i++) { - result += ("0" + s.charCodeAt(i).toString(16)).slice(-2); - } - return result; -} - -// ==== hex / b64nl ======================================= - -/* - * since base64x 1.1.3 - */ -function hextob64(s) { - return hex2b64(s); -} - -/* - * since base64x 1.1.3 - */ -function hextob64nl(s) { - var b64 = hextob64(s); - var b64nl = b64.replace(/(.{64})/g, "$1\r\n"); - b64nl = b64nl.replace(/\r\n$/, ''); - return b64nl; -} - -/* - * since base64x 1.1.3 - */ -function b64nltohex(s) { - var b64 = s.replace(/[^0-9A-Za-z\/+=]*/g, ''); - var hex = b64tohex(b64); - return hex; -} - -// ==== URIComponent / hex ================================ -/** - * convert a URLComponent string such like "%67%68" to a hexadecimal string.
- * @param {String} s URIComponent string such like "%67%68" - * @return {String} hexadecimal string - * @since 1.1 - */ -function uricmptohex(s) { - return s.replace(/%/g, ""); -} - -/** - * convert a hexadecimal string to a URLComponent string such like "%67%68".
- * @param {String} s hexadecimal string - * @return {String} URIComponent string such like "%67%68" - * @since 1.1 - */ -function hextouricmp(s) { - return s.replace(/(..)/g, "%$1"); -} - -// ==== URIComponent ================================ -/** - * convert UTFa hexadecimal string to a URLComponent string such like "%67%68".
- * Note that these "0-9A-Za-z!'()*-._~" characters will not - * converted to "%xx" format by builtin 'encodeURIComponent()' function. - * However this 'encodeURIComponentAll()' function will convert - * all of characters into "%xx" format. - * @param {String} s hexadecimal string - * @return {String} URIComponent string such like "%67%68" - * @since 1.1 - */ -function encodeURIComponentAll(u8) { - var s = encodeURIComponent(u8); - var s2 = ""; - for (var i = 0; i < s.length; i++) { - if (s[i] == "%") { - s2 = s2 + s.substr(i, 3); - i = i + 2; - } else { - s2 = s2 + "%" + stohex(s[i]); - } - } - return s2; -} - -// ==== new lines ================================ -/** - * convert all DOS new line("\r\n") to UNIX new line("\n") in - * a String "s". - * @param {String} s string - * @return {String} converted string - */ -function newline_toUnix(s) { - s = s.replace(/\r\n/mg, "\n"); - return s; -} - -/** - * convert all UNIX new line("\r\n") to DOS new line("\n") in - * a String "s". - * @param {String} s string - * @return {String} converted string - */ -function newline_toDos(s) { - s = s.replace(/\r\n/mg, "\n"); - s = s.replace(/\n/mg, "\r\n"); - return s; -} - -/*! Mike Samuel (c) 2009 | code.google.com/p/json-sans-eval - */ -// This source code is free for use in the public domain. -// NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. - -// http://code.google.com/p/json-sans-eval/ - -/** - * Parses a string of well-formed JSON text. - * - * If the input is not well-formed, then behavior is undefined, but it is - * deterministic and is guaranteed not to modify any object other than its - * return value. - * - * This does not use `eval` so is less likely to have obscure security bugs than - * json2.js. - * It is optimized for speed, so is much faster than json_parse.js. - * - * This library should be used whenever security is a concern (when JSON may - * come from an untrusted source), speed is a concern, and erroring on malformed - * JSON is *not* a concern. - * - * Pros Cons - * +-----------------------+-----------------------+ - * json_sans_eval.js | Fast, secure | Not validating | - * +-----------------------+-----------------------+ - * json_parse.js | Validating, secure | Slow | - * +-----------------------+-----------------------+ - * json2.js | Fast, some validation | Potentially insecure | - * +-----------------------+-----------------------+ - * - * json2.js is very fast, but potentially insecure since it calls `eval` to - * parse JSON data, so an attacker might be able to supply strange JS that - * looks like JSON, but that executes arbitrary javascript. - * If you do have to use json2.js with untrusted data, make sure you keep - * your version of json2.js up to date so that you get patches as they're - * released. - * - * @param {string} json per RFC 4627 - * @param {function (this:Object, string, *):*} opt_reviver optional function - * that reworks JSON objects post-parse per Chapter 15.12 of EcmaScript3.1. - * If supplied, the function is called with a string key, and a value. - * The value is the property of 'this'. The reviver should return - * the value to use in its place. So if dates were serialized as - * {@code { "type": "Date", "time": 1234 }}, then a reviver might look like - * {@code - * function (key, value) { - * if (value && typeof value === 'object' && 'Date' === value.type) { - * return new Date(value.time); - * } else { - * return value; - * } - * }}. - * If the reviver returns {@code undefined} then the property named by key - * will be deleted from its container. - * {@code this} is bound to the object containing the specified property. - * @return {Object|Array} - * @author Mike Samuel - */ -var jsonParse = (function () { - var number - = '(?:-?\\b(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\\b)'; - var oneChar = '(?:[^\\0-\\x08\\x0a-\\x1f\"\\\\]' - + '|\\\\(?:[\"/\\\\bfnrt]|u[0-9A-Fa-f]{4}))'; - var string = '(?:\"' + oneChar + '*\")'; - - // Will match a value in a well-formed JSON file. - // If the input is not well-formed, may match strangely, but not in an unsafe - // way. - // Since this only matches value tokens, it does not match whitespace, colons, - // or commas. - var jsonToken = new RegExp( - '(?:false|true|null|[\\{\\}\\[\\]]' - + '|' + number - + '|' + string - + ')', 'g'); - - // Matches escape sequences in a string literal - var escapeSequence = new RegExp('\\\\(?:([^u])|u(.{4}))', 'g'); - - // Decodes escape sequences in object literals - var escapes = { - '"': '"', - '/': '/', - '\\': '\\', - 'b': '\b', - 'f': '\f', - 'n': '\n', - 'r': '\r', - 't': '\t' - }; - function unescapeOne(_, ch, hex) { - return ch ? escapes[ch] : String.fromCharCode(parseInt(hex, 16)); - } - - // A non-falsy value that coerces to the empty string when used as a key. - var EMPTY_STRING = new String(''); - var SLASH = '\\'; - - // Constructor to use based on an open token. - var firstTokenCtors = { '{': Object, '[': Array }; - - var hop = Object.hasOwnProperty; - - return function (json, opt_reviver) { - // Split into tokens - var toks = json.match(jsonToken); - // Construct the object to return - var result; - var tok = toks[0]; - var topLevelPrimitive = false; - if ('{' === tok) { - result = {}; - } else if ('[' === tok) { - result = []; - } else { - // The RFC only allows arrays or objects at the top level, but the JSON.parse - // defined by the EcmaScript 5 draft does allow strings, booleans, numbers, and null - // at the top level. - result = []; - topLevelPrimitive = true; - } - - // If undefined, the key in an object key/value record to use for the next - // value parsed. - var key; - // Loop over remaining tokens maintaining a stack of uncompleted objects and - // arrays. - var stack = [result]; - for (var i = 1 - topLevelPrimitive, n = toks.length; i < n; ++i) { - tok = toks[i]; - - var cont; - switch (tok.charCodeAt(0)) { - default: // sign or digit - cont = stack[0]; - cont[key || cont.length] = +(tok); - key = void 0; - break; - case 0x22: // '"' - tok = tok.substring(1, tok.length - 1); - if (tok.indexOf(SLASH) !== -1) { - tok = tok.replace(escapeSequence, unescapeOne); - } - cont = stack[0]; - if (!key) { - if (cont instanceof Array) { - key = cont.length; - } else { - key = tok || EMPTY_STRING; // Use as key for next value seen. - break; - } - } - cont[key] = tok; - key = void 0; - break; - case 0x5b: // '[' - cont = stack[0]; - stack.unshift(cont[key || cont.length] = []); - key = void 0; - break; - case 0x5d: // ']' - stack.shift(); - break; - case 0x66: // 'f' - cont = stack[0]; - cont[key || cont.length] = false; - key = void 0; - break; - case 0x6e: // 'n' - cont = stack[0]; - cont[key || cont.length] = null; - key = void 0; - break; - case 0x74: // 't' - cont = stack[0]; - cont[key || cont.length] = true; - key = void 0; - break; - case 0x7b: // '{' - cont = stack[0]; - stack.unshift(cont[key || cont.length] = {}); - key = void 0; - break; - case 0x7d: // '}' - stack.shift(); - break; - } - } - // Fail if we've got an uncompleted object. - if (topLevelPrimitive) { - if (stack.length !== 1) { throw new Error(); } - result = result[0]; - } else { - if (stack.length) { throw new Error(); } - } - - if (opt_reviver) { - // Based on walk as implemented in http://www.json.org/json2.js - var walk = function (holder, key) { - var value = holder[key]; - if (value && typeof value === 'object') { - var toDelete = null; - for (var k in value) { - if (hop.call(value, k) && value !== holder) { - // Recurse to properties first. This has the effect of causing - // the reviver to be called on the object graph depth-first. - - // Since 'this' is bound to the holder of the property, the - // reviver can access sibling properties of k including ones - // that have not yet been revived. - - // The value returned by the reviver is used in place of the - // current value of property k. - // If it returns undefined then the property is deleted. - var v = walk(value, k); - if (v !== void 0) { - value[k] = v; - } else { - // Deleting properties inside the loop has vaguely defined - // semantics in ES3 and ES3.1. - if (!toDelete) { toDelete = []; } - toDelete.push(k); - } - } - } - if (toDelete) { - for (var i = toDelete.length; --i >= 0;) { - delete value[toDelete[i]]; - } - } - } - return opt_reviver.call(holder, key, value); - }; - result = walk({ '': result }, ''); - } - - return result; - }; -})(); - -/*! jws-3.0.2 (c) 2013 Kenji Urushima | kjur.github.com/jsjws/license - */ -/* - * jws.js - JSON Web Signature Class - * - * version: 3.0.2 (2013 Sep 24) - * - * Copyright (c) 2010-2013 Kenji Urushima (kenji.urushima@gmail.com) - * - * This software is licensed under the terms of the MIT License. - * http://kjur.github.com/jsjws/license/ - * - * The above copyright and license notice shall be - * included in all copies or substantial portions of the Software. - */ - -/** - * @fileOverview - * @name jws-3.0.js - * @author Kenji Urushima kenji.urushima@gmail.com - * @version 3.0.1 (2013-Sep-24) - * @since jsjws 1.0 - * @license MIT License - */ - -if (typeof KJUR == "undefined" || !KJUR) KJUR = {}; -if (typeof KJUR.jws == "undefined" || !KJUR.jws) KJUR.jws = {}; - -/** - * JSON Web Signature(JWS) class.
- * @name KJUR.jws.JWS - * @class JSON Web Signature(JWS) class - * @property {Dictionary} parsedJWS This property is set after JWS signature verification.
- * Following "parsedJWS_*" properties can be accessed as "parsedJWS.*" because of - * JsDoc restriction. - * @property {String} parsedJWS_headB64U string of Encrypted JWS Header - * @property {String} parsedJWS_payloadB64U string of Encrypted JWS Payload - * @property {String} parsedJWS_sigvalB64U string of Encrypted JWS signature value - * @property {String} parsedJWS_si string of Signature Input - * @property {String} parsedJWS_sigvalH hexadecimal string of JWS signature value - * @property {String} parsedJWS_sigvalBI BigInteger(defined in jsbn.js) object of JWS signature value - * @property {String} parsedJWS_headS string of decoded JWS Header - * @property {String} parsedJWS_headS string of decoded JWS Payload - * @requires base64x.js, json-sans-eval.js and jsrsasign library - * @see 'jwjws'(JWS JavaScript Library) home page http://kjur.github.com/jsjws/ - * @see 'jwrsasign'(RSA Sign JavaScript Library) home page http://kjur.github.com/jsrsasign/ - * @see IETF I-D JSON Web Algorithms (JWA) - * @since jsjws 1.0 - * @description - *

Supported Algorithms

- * Here is supported algorithm names for {@link KJUR.jws.JWS.sign} and {@link KJUR.jws.JWS.verify} - * methods. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
alg valuespec requirementjsjws support
HS256REQUIREDSUPPORTED
HS384OPTIONALSUPPORTED
HS512OPTIONALSUPPORTED
RS256RECOMMENDEDSUPPORTED
RS384OPTIONALSUPPORTED
RS512OPTIONALSUPPORTED
ES256RECOMMENDED+SUPPORTED
ES384OPTIONALSUPPORTED
ES512OPTIONAL-
PS256OPTIONALSUPPORTED
PS384OPTIONALSUPPORTED
PS512OPTIONALSUPPORTED
noneREQUIREDSUPPORTED
- * NOTE: HS384 is supported since jsjws 3.0.2 with jsrsasign 4.1.4. - */ -KJUR.jws.JWS = function() { - - // === utility ============================================================= - - /** - * parse JWS string and set public property 'parsedJWS' dictionary.
- * @name parseJWS - * @memberOf KJUR.jws.JWS - * @function - * @param {String} sJWS JWS signature string to be parsed. - * @throws if sJWS is not comma separated string such like "Header.Payload.Signature". - * @throws if JWS Header is a malformed JSON string. - * @since jws 1.1 - */ - this.parseJWS = function(sJWS, sigValNotNeeded) { - if ((this.parsedJWS !== undefined) && - (sigValNotNeeded || (this.parsedJWS.sigvalH !== undefined))) { - return; - } - if (sJWS.match(/^([^.]+)\.([^.]+)\.([^.]+)$/) == null) { - throw "JWS signature is not a form of 'Head.Payload.SigValue'."; - } - var b6Head = RegExp.$1; - var b6Payload = RegExp.$2; - var b6SigVal = RegExp.$3; - var sSI = b6Head + "." + b6Payload; - this.parsedJWS = {}; - this.parsedJWS.headB64U = b6Head; - this.parsedJWS.payloadB64U = b6Payload; - this.parsedJWS.sigvalB64U = b6SigVal; - this.parsedJWS.si = sSI; - - if (!sigValNotNeeded) { - var hSigVal = b64utohex(b6SigVal); - var biSigVal = parseBigInt(hSigVal, 16); - this.parsedJWS.sigvalH = hSigVal; - this.parsedJWS.sigvalBI = biSigVal; - } - - var sHead = b64utoutf8(b6Head); - var sPayload = b64utoutf8(b6Payload); - this.parsedJWS.headS = sHead; - this.parsedJWS.payloadS = sPayload; - - if (!KJUR.jws.JWS.isSafeJSONString(sHead, this.parsedJWS, 'headP')) - throw "malformed JSON string for JWS Head: " + sHead; - }; - - // ==== JWS Validation ========================================================= - function _getSignatureInputByString(sHead, sPayload) { - return utf8tob64u(sHead) + "." + utf8tob64u(sPayload); - }; - - function _getHashBySignatureInput(sSignatureInput, sHashAlg) { - var hashfunc = function(s) { return KJUR.crypto.Util.hashString(s, sHashAlg); }; - if (hashfunc == null) throw "hash function not defined in jsrsasign: " + sHashAlg; - return hashfunc(sSignatureInput); - }; - - function _jws_verifySignature(sHead, sPayload, hSig, hN, hE) { - var sSignatureInput = _getSignatureInputByString(sHead, sPayload); - var biSig = parseBigInt(hSig, 16); - return _rsasign_verifySignatureWithArgs(sSignatureInput, biSig, hN, hE); - }; - - /** - * verify JWS signature with naked RSA public key.
- * This only supports "RS256" and "RS512" algorithm. - * @name verifyJWSByNE - * @memberOf KJUR.jws.JWS - * @function - * @param {String} sJWS JWS signature string to be verified - * @param {String} hN hexadecimal string for modulus of RSA public key - * @param {String} hE hexadecimal string for public exponent of RSA public key - * @return {String} returns 1 when JWS signature is valid, otherwise returns 0 - * @throws if sJWS is not comma separated string such like "Header.Payload.Signature". - * @throws if JWS Header is a malformed JSON string. - * @deprecated from 3.0.0 please move to {@link KJUR.jws.JWS.verify} - */ - this.verifyJWSByNE = function(sJWS, hN, hE) { - this.parseJWS(sJWS); - return _rsasign_verifySignatureWithArgs(this.parsedJWS.si, this.parsedJWS.sigvalBI, hN, hE); - }; - - /** - * verify JWS signature with RSA public key.
- * This only supports "RS256", "RS512", "PS256" and "PS512" algorithms. - * @name verifyJWSByKey - * @memberOf KJUR.jws.JWS - * @function - * @param {String} sJWS JWS signature string to be verified - * @param {RSAKey} key RSA public key - * @return {Boolean} returns true when JWS signature is valid, otherwise returns false - * @throws if sJWS is not comma separated string such like "Header.Payload.Signature". - * @throws if JWS Header is a malformed JSON string. - * @deprecated from 3.0.0 please move to {@link KJUR.jws.JWS.verify} - */ - this.verifyJWSByKey = function(sJWS, key) { - this.parseJWS(sJWS); - var hashAlg = _jws_getHashAlgFromParsedHead(this.parsedJWS.headP); - var isPSS = this.parsedJWS.headP['alg'].substr(0, 2) == "PS"; - - if (key.hashAndVerify) { - return key.hashAndVerify(hashAlg, - new Buffer(this.parsedJWS.si, 'utf8').toString('base64'), - b64utob64(this.parsedJWS.sigvalB64U), - 'base64', - isPSS); - } else if (isPSS) { - return key.verifyStringPSS(this.parsedJWS.si, - this.parsedJWS.sigvalH, hashAlg); - } else { - return key.verifyString(this.parsedJWS.si, - this.parsedJWS.sigvalH); - } - }; - - /** - * verify JWS signature by PEM formatted X.509 certificate.
- * This only supports "RS256" and "RS512" algorithm. - * @name verifyJWSByPemX509Cert - * @memberOf KJUR.jws.JWS - * @function - * @param {String} sJWS JWS signature string to be verified - * @param {String} sPemX509Cert string of PEM formatted X.509 certificate - * @return {String} returns 1 when JWS signature is valid, otherwise returns 0 - * @throws if sJWS is not comma separated string such like "Header.Payload.Signature". - * @throws if JWS Header is a malformed JSON string. - * @since 1.1 - * @deprecated from 3.0.0 please move to {@link KJUR.jws.JWS.verify} - */ - this.verifyJWSByPemX509Cert = function(sJWS, sPemX509Cert) { - this.parseJWS(sJWS); - var x509 = new X509(); - x509.readCertPEM(sPemX509Cert); - return x509.subjectPublicKeyRSA.verifyString(this.parsedJWS.si, this.parsedJWS.sigvalH); - }; - - // ==== JWS Generation ========================================================= - function _jws_getHashAlgFromParsedHead(head) { - var sigAlg = head["alg"]; - var hashAlg = ""; - - if (sigAlg != "RS256" && sigAlg != "RS512" && - sigAlg != "PS256" && sigAlg != "PS512") - throw "JWS signature algorithm not supported: " + sigAlg; - if (sigAlg.substr(2) == "256") hashAlg = "sha256"; - if (sigAlg.substr(2) == "512") hashAlg = "sha512"; - return hashAlg; - }; - - function _jws_getHashAlgFromHead(sHead) { - return _jws_getHashAlgFromParsedHead(jsonParse(sHead)); - }; - - function _jws_generateSignatureValueBySI_NED(sHead, sPayload, sSI, hN, hE, hD) { - var rsa = new RSAKey(); - rsa.setPrivate(hN, hE, hD); - - var hashAlg = _jws_getHashAlgFromHead(sHead); - var sigValue = rsa.signString(sSI, hashAlg); - return sigValue; - }; - - function _jws_generateSignatureValueBySI_Key(sHead, sPayload, sSI, key, head) { - var hashAlg = null; - if (typeof head == "undefined") { - hashAlg = _jws_getHashAlgFromHead(sHead); - } else { - hashAlg = _jws_getHashAlgFromParsedHead(head); - } - - var isPSS = head['alg'].substr(0, 2) == "PS"; - - if (key.hashAndSign) { - return b64tob64u(key.hashAndSign(hashAlg, sSI, 'binary', 'base64', isPSS)); - } else if (isPSS) { - return hextob64u(key.signStringPSS(sSI, hashAlg)); - } else { - return hextob64u(key.signString(sSI, hashAlg)); - } - }; - - function _jws_generateSignatureValueByNED(sHead, sPayload, hN, hE, hD) { - var sSI = _getSignatureInputByString(sHead, sPayload); - return _jws_generateSignatureValueBySI_NED(sHead, sPayload, sSI, hN, hE, hD); - }; - - /** - * generate JWS signature by Header, Payload and a naked RSA private key.
- * This only supports "RS256" and "RS512" algorithm. - * @name generateJWSByNED - * @memberOf KJUR.jws.JWS - * @function - * @param {String} sHead string of JWS Header - * @param {String} sPayload string of JWS Payload - * @param {String} hN hexadecimal string for modulus of RSA public key - * @param {String} hE hexadecimal string for public exponent of RSA public key - * @param {String} hD hexadecimal string for private exponent of RSA private key - * @return {String} JWS signature string - * @throws if sHead is a malformed JSON string. - * @throws if supported signature algorithm was not specified in JSON Header. - * @deprecated from 3.0.0 please move to {@link KJUR.jws.JWS.sign} - */ - this.generateJWSByNED = function(sHead, sPayload, hN, hE, hD) { - if (!KJUR.jws.JWS.isSafeJSONString(sHead)) throw "JWS Head is not safe JSON string: " + sHead; - var sSI = _getSignatureInputByString(sHead, sPayload); - var hSigValue = _jws_generateSignatureValueBySI_NED(sHead, sPayload, sSI, hN, hE, hD); - var b64SigValue = hextob64u(hSigValue); - - this.parsedJWS = {}; - this.parsedJWS.headB64U = sSI.split(".")[0]; - this.parsedJWS.payloadB64U = sSI.split(".")[1]; - this.parsedJWS.sigvalB64U = b64SigValue; - - return sSI + "." + b64SigValue; - }; - - /** - * generate JWS signature by Header, Payload and a RSA private key.
- * This only supports "RS256", "RS512", "PS256" and "PS512" algorithms. - * @name generateJWSByKey - * @memberOf KJUR.jws.JWS - * @function - * @param {String} sHead string of JWS Header - * @param {String} sPayload string of JWS Payload - * @param {RSAKey} RSA private key - * @return {String} JWS signature string - * @throws if sHead is a malformed JSON string. - * @throws if supported signature algorithm was not specified in JSON Header. - * @deprecated from 3.0.0 please move to {@link KJUR.jws.JWS.sign} - */ - this.generateJWSByKey = function(sHead, sPayload, key) { - var obj = {}; - if (!KJUR.jws.JWS.isSafeJSONString(sHead, obj, 'headP')) - throw "JWS Head is not safe JSON string: " + sHead; - var sSI = _getSignatureInputByString(sHead, sPayload); - var b64SigValue = _jws_generateSignatureValueBySI_Key(sHead, sPayload, sSI, key, obj.headP); - - this.parsedJWS = {}; - this.parsedJWS.headB64U = sSI.split(".")[0]; - this.parsedJWS.payloadB64U = sSI.split(".")[1]; - this.parsedJWS.sigvalB64U = b64SigValue; - - return sSI + "." + b64SigValue; - }; - - // === sign with PKCS#1 RSA private key ===================================================== - function _jws_generateSignatureValueBySI_PemPrvKey(sHead, sPayload, sSI, sPemPrvKey) { - var rsa = new RSAKey(); - rsa.readPrivateKeyFromPEMString(sPemPrvKey); - var hashAlg = _jws_getHashAlgFromHead(sHead); - var sigValue = rsa.signString(sSI, hashAlg); - return sigValue; - }; - - /** - * generate JWS signature by Header, Payload and a PEM formatted PKCS#1 RSA private key.
- * This only supports "RS256" and "RS512" algorithm. - * @name generateJWSByP1PrvKey - * @memberOf KJUR.jws.JWS - * @function - * @param {String} sHead string of JWS Header - * @param {String} sPayload string of JWS Payload - * @param {String} string for sPemPrvKey PEM formatted PKCS#1 RSA private key
- * Heading and trailing space characters in PEM key will be ignored. - * @return {String} JWS signature string - * @throws if sHead is a malformed JSON string. - * @throws if supported signature algorithm was not specified in JSON Header. - * @since 1.1 - * @deprecated from 3.0.0 please move to {@link KJUR.jws.JWS.sign} - */ - this.generateJWSByP1PrvKey = function(sHead, sPayload, sPemPrvKey) { - if (!KJUR.jws.JWS.isSafeJSONString(sHead)) throw "JWS Head is not safe JSON string: " + sHead; - var sSI = _getSignatureInputByString(sHead, sPayload); - var hSigValue = _jws_generateSignatureValueBySI_PemPrvKey(sHead, sPayload, sSI, sPemPrvKey); - var b64SigValue = hextob64u(hSigValue); - - this.parsedJWS = {}; - this.parsedJWS.headB64U = sSI.split(".")[0]; - this.parsedJWS.payloadB64U = sSI.split(".")[1]; - this.parsedJWS.sigvalB64U = b64SigValue; - - return sSI + "." + b64SigValue; - }; -}; - -// === major static method ======================================================== - -/** - * generate JWS signature by specified key
- * @name sign - * @memberOf KJUR.jws.JWS - * @function - * @static - * @param {String} alg JWS algorithm name to sign and force set to sHead or null - * @param {String} sHead string of JWS Header - * @param {String} sPayload string of JWS Payload - * @param {String} key string of private key or key object to sign - * @param {String} pass (OPTION)passcode to use encrypted private key - * @return {String} JWS signature string - * @since jws 3.0.0 - * @see jsrsasign KJUR.crypto.Signature method - * @see jsrsasign KJUR.crypto.Mac method - * @description - * This method supports following algorithms. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
alg valuespec requirementjsjws support
HS256REQUIREDSUPPORTED
HS384OPTIONAL-
HS512OPTIONALSUPPORTED
RS256RECOMMENDEDSUPPORTED
RS384OPTIONALSUPPORTED
RS512OPTIONALSUPPORTED
ES256RECOMMENDED+SUPPORTED
ES384OPTIONALSUPPORTED
ES512OPTIONAL-
PS256OPTIONALSUPPORTED
PS384OPTIONALSUPPORTED
PS512OPTIONALSUPPORTED
noneREQUIREDSUPPORTED
- *
- *
NOTE1: - *
salt length of RSAPSS signature is the same as the hash algorithm length - * because of IETF JOSE ML discussion. - *
NOTE2: - *
The reason of HS384 unsupport is - * CryptoJS HmacSHA384 bug. - *
- */ -KJUR.jws.JWS.sign = function(alg, sHeader, sPayload, key, pass) { - var ns1 = KJUR.jws.JWS; - - if (! ns1.isSafeJSONString(sHeader)) - throw "JWS Head is not safe JSON string: " + sHead; - - var pHeader = ns1.readSafeJSONString(sHeader); - - // 1. use alg if defined in sHeader - if ((alg == '' || alg == null) && - pHeader['alg'] !== undefined) { - alg = pHeader['alg']; - } - - // 2. set alg in sHeader if undefined - if ((alg != '' && alg != null) && - pHeader['alg'] === undefined) { - pHeader['alg'] = alg; - sHeader = JSON.stringify(pHeader); - } - - // 3. set signature algorithm like SHA1withRSA - var sigAlg = null; - if (ns1.jwsalg2sigalg[alg] === undefined) { - throw "unsupported alg name: " + alg; - } else { - sigAlg = ns1.jwsalg2sigalg[alg]; - } - - var uHeader = utf8tob64u(sHeader); - var uPayload = utf8tob64u(sPayload); - var uSignatureInput = uHeader + "." + uPayload - - // 4. sign - var hSig = ""; - if (sigAlg.substr(0, 4) == "Hmac") { - if (key === undefined) - throw "hexadecimal key shall be specified for HMAC"; - var mac = new KJUR.crypto.Mac({'alg': sigAlg, 'pass': hextorstr(key)}); - mac.updateString(uSignatureInput); - hSig = mac.doFinal(); - } else if (sigAlg.indexOf("withECDSA") != -1) { - var sig = new KJUR.crypto.Signature({'alg': sigAlg}); - sig.init(key, pass); - sig.updateString(uSignatureInput); - hASN1Sig = sig.sign(); - hSig = KJUR.crypto.ECDSA.asn1SigToConcatSig(hASN1Sig); - } else if (sigAlg != "none") { - var sig = new KJUR.crypto.Signature({'alg': sigAlg}); - sig.init(key, pass); - sig.updateString(uSignatureInput); - hSig = sig.sign(); - } - - var uSig = hextob64u(hSig); - return uSignatureInput + "." + uSig; -}; - -/** - * verify JWS signature by specified key or certificate
- * @name verify - * @memberOf KJUR.jws.JWS - * @function - * @static - * @param {String} sJWS string of JWS signature to verify - * @param {String} key string of public key, certificate or key object to verify - * @return {Boolean} true if the signature is valid otherwise false - * @since jws 3.0.0 - * @see jsrsasign KJUR.crypto.Signature method - * @see jsrsasign KJUR.crypto.Mac method - */ -KJUR.jws.JWS.verify = function(sJWS, key) { - var jws = KJUR.jws.JWS; - var a = sJWS.split("."); - var uHeader = a[0]; - var uPayload = a[1]; - var uSignatureInput = uHeader + "." + uPayload; - var hSig = b64utohex(a[2]); - - var pHeader = jws.readSafeJSONString(b64utoutf8(a[0])); - var alg = null; - if (pHeader.alg === undefined) { - throw "algorithm not specified in header"; - } else { - alg = pHeader.alg; - } - - var sigAlg = null; - if (jws.jwsalg2sigalg[pHeader.alg] === undefined) { - throw "unsupported alg name: " + alg; - } else { - sigAlg = jws.jwsalg2sigalg[alg]; - } - - // x. verify - if (sigAlg == "none") { - return true; - } else if (sigAlg.substr(0, 4) == "Hmac") { - if (key === undefined) - throw "hexadecimal key shall be specified for HMAC"; - var mac = new KJUR.crypto.Mac({'alg': sigAlg, 'pass': hextorstr(key)}); - mac.updateString(uSignatureInput); - hSig2 = mac.doFinal(); - return hSig == hSig2; - } else if (sigAlg.indexOf("withECDSA") != -1) { - var hASN1Sig = null; - try { - hASN1Sig = KJUR.crypto.ECDSA.concatSigToASN1Sig(hSig); - } catch (ex) { - return false; - } - var sig = new KJUR.crypto.Signature({'alg': sigAlg}); - sig.init(key) - sig.updateString(uSignatureInput); - return sig.verify(hASN1Sig); - } else { - var sig = new KJUR.crypto.Signature({'alg': sigAlg}); - sig.init(key) - sig.updateString(uSignatureInput); - return sig.verify(hSig); - } -}; - -/* - * @since jws 3.0.0 - */ -KJUR.jws.JWS.jwsalg2sigalg = { - "HS256": "HmacSHA256", - //"HS384": "HmacSHA384", // unsupported because of CryptoJS bug - "HS512": "HmacSHA512", - "RS256": "SHA256withRSA", - "RS384": "SHA384withRSA", - "RS512": "SHA512withRSA", - "ES256": "SHA256withECDSA", - "ES384": "SHA384withECDSA", - //"ES512": "SHA512withECDSA", // unsupported because of jsrsasign's bug - "PS256": "SHA256withRSAandMGF1", - "PS384": "SHA384withRSAandMGF1", - "PS512": "SHA512withRSAandMGF1", - "none": "none", -}; - -// === utility static method ====================================================== - -/** - * check whether a String "s" is a safe JSON string or not.
- * If a String "s" is a malformed JSON string or an other object type - * this returns 0, otherwise this returns 1. - * @name isSafeJSONString - * @memberOf KJUR.jws.JWS - * @function - * @static - * @param {String} s JSON string - * @return {Number} 1 or 0 - */ -KJUR.jws.JWS.isSafeJSONString = function(s, h, p) { - var o = null; - try { - o = jsonParse(s); - if (typeof o != "object") return 0; - if (o.constructor === Array) return 0; - if (h) h[p] = o; - return 1; - } catch (ex) { - return 0; - } -}; - -/** - * read a String "s" as JSON object if it is safe.
- * If a String "s" is a malformed JSON string or not JSON string, - * this returns null, otherwise returns JSON object. - * @name readSafeJSONString - * @memberOf KJUR.jws.JWS - * @function - * @static - * @param {String} s JSON string - * @return {Object} JSON object or null - * @since 1.1.1 - */ -KJUR.jws.JWS.readSafeJSONString = function(s) { - var o = null; - try { - o = jsonParse(s); - if (typeof o != "object") return null; - if (o.constructor === Array) return null; - return o; - } catch (ex) { - return null; - } -}; - -/** - * get Encoed Signature Value from JWS string.
- * @name getEncodedSignatureValueFromJWS - * @memberOf KJUR.jws.JWS - * @function - * @static - * @param {String} sJWS JWS signature string to be verified - * @return {String} string of Encoded Signature Value - * @throws if sJWS is not comma separated string such like "Header.Payload.Signature". - */ -KJUR.jws.JWS.getEncodedSignatureValueFromJWS = function(sJWS) { - if (sJWS.match(/^[^.]+\.[^.]+\.([^.]+)$/) == null) { - throw "JWS signature is not a form of 'Head.Payload.SigValue'."; - } - return RegExp.$1; -}; - -/** - * IntDate class for time representation for JSON Web Token(JWT) - * @class KJUR.jws.IntDate class - * @name KJUR.jws.IntDate - * @since jws 3.0.1 - * @description - * Utility class for IntDate which is integer representation of UNIX origin time - * used in JSON Web Token(JWT). - */ -KJUR.jws.IntDate = function() { -}; - -/** - * @name get - * @memberOf KJUR.jws.IntDate - * @function - * @static - * @param {String} s string of time representation - * @return {Integer} UNIX origin time in seconds for argument 's' - * @since jws 3.0.1 - * @throws "unsupported format: s" when malformed format - * @description - * This method will accept following representation of time. - *
    - *
  • now - current time
  • - *
  • now + 1hour - after 1 hour from now
  • - *
  • now + 1day - after 1 day from now
  • - *
  • now + 1month - after 30 days from now
  • - *
  • now + 1year - after 365 days from now
  • - *
  • YYYYmmDDHHMMSSZ - UTC time (ex. 20130828235959Z)
  • - *
  • number - UNIX origin time (seconds from 1970-01-01 00:00:00) (ex. 1377714748)
  • - *
- */ -KJUR.jws.IntDate.get = function(s) { - if (s == "now") { - return KJUR.jws.IntDate.getNow(); - } else if (s == "now + 1hour") { - return KJUR.jws.IntDate.getNow() + 60 * 60; - } else if (s == "now + 1day") { - return KJUR.jws.IntDate.getNow() + 60 * 60 * 24; - } else if (s == "now + 1month") { - return KJUR.jws.IntDate.getNow() + 60 * 60 * 24 * 30; - } else if (s == "now + 1year") { - return KJUR.jws.IntDate.getNow() + 60 * 60 * 24 * 365; - } else if (s.match(/Z$/)) { - return KJUR.jws.IntDate.getZulu(s); - } else if (s.match(/^[0-9]+$/)) { - return parseInt(s); - } - throw "unsupported format: " + s; -}; - -KJUR.jws.IntDate.getZulu = function(s) { - if (a = s.match(/(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)Z/)) { - var year = parseInt(RegExp.$1); - var month = parseInt(RegExp.$2) - 1; - var day = parseInt(RegExp.$3); - var hour = parseInt(RegExp.$4); - var min = parseInt(RegExp.$5); - var sec = parseInt(RegExp.$6); - var d = new Date(Date.UTC(year, month, day, hour, min, sec)); - return ~~(d / 1000); - } - throw "unsupported format: " + s; -}; - -/* - * @since jws 3.0.1 - */ -KJUR.jws.IntDate.getNow = function() { - var d = ~~(new Date() / 1000); - return d; -}; - -/* - * @since jws 3.0.1 - */ -KJUR.jws.IntDate.intDate2UTCString = function(intDate) { - var d = new Date(intDate * 1000); - return d.toUTCString(); -}; - -/* - * @since jws 3.0.1 - */ -KJUR.jws.IntDate.intDate2Zulu = function(intDate) { - var d = new Date(intDate * 1000); - var year = ("0000" + d.getUTCFullYear()).slice(-4); - var mon = ("00" + (d.getUTCMonth() + 1)).slice(-2); - var day = ("00" + d.getUTCDate()).slice(-2); - var hour = ("00" + d.getUTCHours()).slice(-2); - var min = ("00" + d.getUTCMinutes()).slice(-2); - var sec = ("00" + d.getUTCSeconds()).slice(-2); - return year + mon + day + hour + min + sec + "Z"; -}; - -/*! - * @overview es6-promise - a tiny implementation of Promises/A+. - * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald) - * @license Licensed under MIT license - * See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE - * @version 3.0.2 - */ - -(function() { - "use strict"; - function lib$es6$promise$utils$$objectOrFunction(x) { - return typeof x === 'function' || (typeof x === 'object' && x !== null); - } - - function lib$es6$promise$utils$$isFunction(x) { - return typeof x === 'function'; - } - - function lib$es6$promise$utils$$isMaybeThenable(x) { - return typeof x === 'object' && x !== null; - } - - var lib$es6$promise$utils$$_isArray; - if (!Array.isArray) { - lib$es6$promise$utils$$_isArray = function (x) { - return Object.prototype.toString.call(x) === '[object Array]'; - }; - } else { - lib$es6$promise$utils$$_isArray = Array.isArray; - } - - var lib$es6$promise$utils$$isArray = lib$es6$promise$utils$$_isArray; - var lib$es6$promise$asap$$len = 0; - var lib$es6$promise$asap$$toString = {}.toString; - var lib$es6$promise$asap$$vertxNext; - var lib$es6$promise$asap$$customSchedulerFn; - - var lib$es6$promise$asap$$asap = function asap(callback, arg) { - lib$es6$promise$asap$$queue[lib$es6$promise$asap$$len] = callback; - lib$es6$promise$asap$$queue[lib$es6$promise$asap$$len + 1] = arg; - lib$es6$promise$asap$$len += 2; - if (lib$es6$promise$asap$$len === 2) { - // If len is 2, that means that we need to schedule an async flush. - // If additional callbacks are queued before the queue is flushed, they - // will be processed by this flush that we are scheduling. - if (lib$es6$promise$asap$$customSchedulerFn) { - lib$es6$promise$asap$$customSchedulerFn(lib$es6$promise$asap$$flush); - } else { - lib$es6$promise$asap$$scheduleFlush(); - } - } - } - - function lib$es6$promise$asap$$setScheduler(scheduleFn) { - lib$es6$promise$asap$$customSchedulerFn = scheduleFn; - } - - function lib$es6$promise$asap$$setAsap(asapFn) { - lib$es6$promise$asap$$asap = asapFn; - } - - var lib$es6$promise$asap$$browserWindow = (typeof window !== 'undefined') ? window : undefined; - var lib$es6$promise$asap$$browserGlobal = lib$es6$promise$asap$$browserWindow || {}; - var lib$es6$promise$asap$$BrowserMutationObserver = lib$es6$promise$asap$$browserGlobal.MutationObserver || lib$es6$promise$asap$$browserGlobal.WebKitMutationObserver; - var lib$es6$promise$asap$$isNode = typeof process !== 'undefined' && {}.toString.call(process) === '[object process]'; - - // test for web worker but not in IE10 - var lib$es6$promise$asap$$isWorker = typeof Uint8ClampedArray !== 'undefined' && - typeof importScripts !== 'undefined' && - typeof MessageChannel !== 'undefined'; - - // node - function lib$es6$promise$asap$$useNextTick() { - // node version 0.10.x displays a deprecation warning when nextTick is used recursively - // see https://github.com/cujojs/when/issues/410 for details - return function() { - process.nextTick(lib$es6$promise$asap$$flush); - }; - } - - // vertx - function lib$es6$promise$asap$$useVertxTimer() { - return function() { - lib$es6$promise$asap$$vertxNext(lib$es6$promise$asap$$flush); - }; - } - - function lib$es6$promise$asap$$useMutationObserver() { - var iterations = 0; - var observer = new lib$es6$promise$asap$$BrowserMutationObserver(lib$es6$promise$asap$$flush); - var node = document.createTextNode(''); - observer.observe(node, { characterData: true }); - - return function() { - node.data = (iterations = ++iterations % 2); - }; - } - - // web worker - function lib$es6$promise$asap$$useMessageChannel() { - var channel = new MessageChannel(); - channel.port1.onmessage = lib$es6$promise$asap$$flush; - return function () { - channel.port2.postMessage(0); - }; - } - - function lib$es6$promise$asap$$useSetTimeout() { - return function() { - setTimeout(lib$es6$promise$asap$$flush, 1); - }; - } - - var lib$es6$promise$asap$$queue = new Array(1000); - function lib$es6$promise$asap$$flush() { - for (var i = 0; i < lib$es6$promise$asap$$len; i+=2) { - var callback = lib$es6$promise$asap$$queue[i]; - var arg = lib$es6$promise$asap$$queue[i+1]; - - callback(arg); - - lib$es6$promise$asap$$queue[i] = undefined; - lib$es6$promise$asap$$queue[i+1] = undefined; - } - - lib$es6$promise$asap$$len = 0; - } - - function lib$es6$promise$asap$$attemptVertx() { - try { - var r = require; - var vertx = r('vertx'); - lib$es6$promise$asap$$vertxNext = vertx.runOnLoop || vertx.runOnContext; - return lib$es6$promise$asap$$useVertxTimer(); - } catch(e) { - return lib$es6$promise$asap$$useSetTimeout(); - } - } - - var lib$es6$promise$asap$$scheduleFlush; - // Decide what async method to use to triggering processing of queued callbacks: - if (lib$es6$promise$asap$$isNode) { - lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useNextTick(); - } else if (lib$es6$promise$asap$$BrowserMutationObserver) { - lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useMutationObserver(); - } else if (lib$es6$promise$asap$$isWorker) { - lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useMessageChannel(); - } else if (lib$es6$promise$asap$$browserWindow === undefined && typeof require === 'function') { - lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$attemptVertx(); - } else { - lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useSetTimeout(); - } - - function lib$es6$promise$$internal$$noop() {} - - var lib$es6$promise$$internal$$PENDING = void 0; - var lib$es6$promise$$internal$$FULFILLED = 1; - var lib$es6$promise$$internal$$REJECTED = 2; - - var lib$es6$promise$$internal$$GET_THEN_ERROR = new lib$es6$promise$$internal$$ErrorObject(); - - function lib$es6$promise$$internal$$selfFulfillment() { - return new TypeError("You cannot resolve a promise with itself"); - } - - function lib$es6$promise$$internal$$cannotReturnOwn() { - return new TypeError('A promises callback cannot return that same promise.'); - } - - function lib$es6$promise$$internal$$getThen(promise) { - try { - return promise.then; - } catch(error) { - lib$es6$promise$$internal$$GET_THEN_ERROR.error = error; - return lib$es6$promise$$internal$$GET_THEN_ERROR; - } - } - - function lib$es6$promise$$internal$$tryThen(then, value, fulfillmentHandler, rejectionHandler) { - try { - then.call(value, fulfillmentHandler, rejectionHandler); - } catch(e) { - return e; - } - } - - function lib$es6$promise$$internal$$handleForeignThenable(promise, thenable, then) { - lib$es6$promise$asap$$asap(function(promise) { - var sealed = false; - var error = lib$es6$promise$$internal$$tryThen(then, thenable, function(value) { - if (sealed) { return; } - sealed = true; - if (thenable !== value) { - lib$es6$promise$$internal$$resolve(promise, value); - } else { - lib$es6$promise$$internal$$fulfill(promise, value); - } - }, function(reason) { - if (sealed) { return; } - sealed = true; - - lib$es6$promise$$internal$$reject(promise, reason); - }, 'Settle: ' + (promise._label || ' unknown promise')); - - if (!sealed && error) { - sealed = true; - lib$es6$promise$$internal$$reject(promise, error); - } - }, promise); - } - - function lib$es6$promise$$internal$$handleOwnThenable(promise, thenable) { - if (thenable._state === lib$es6$promise$$internal$$FULFILLED) { - lib$es6$promise$$internal$$fulfill(promise, thenable._result); - } else if (thenable._state === lib$es6$promise$$internal$$REJECTED) { - lib$es6$promise$$internal$$reject(promise, thenable._result); - } else { - lib$es6$promise$$internal$$subscribe(thenable, undefined, function(value) { - lib$es6$promise$$internal$$resolve(promise, value); - }, function(reason) { - lib$es6$promise$$internal$$reject(promise, reason); - }); - } - } - - function lib$es6$promise$$internal$$handleMaybeThenable(promise, maybeThenable) { - if (maybeThenable.constructor === promise.constructor) { - lib$es6$promise$$internal$$handleOwnThenable(promise, maybeThenable); - } else { - var then = lib$es6$promise$$internal$$getThen(maybeThenable); - - if (then === lib$es6$promise$$internal$$GET_THEN_ERROR) { - lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$GET_THEN_ERROR.error); - } else if (then === undefined) { - lib$es6$promise$$internal$$fulfill(promise, maybeThenable); - } else if (lib$es6$promise$utils$$isFunction(then)) { - lib$es6$promise$$internal$$handleForeignThenable(promise, maybeThenable, then); - } else { - lib$es6$promise$$internal$$fulfill(promise, maybeThenable); - } - } - } - - function lib$es6$promise$$internal$$resolve(promise, value) { - if (promise === value) { - lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$selfFulfillment()); - } else if (lib$es6$promise$utils$$objectOrFunction(value)) { - lib$es6$promise$$internal$$handleMaybeThenable(promise, value); - } else { - lib$es6$promise$$internal$$fulfill(promise, value); - } - } - - function lib$es6$promise$$internal$$publishRejection(promise) { - if (promise._onerror) { - promise._onerror(promise._result); - } - - lib$es6$promise$$internal$$publish(promise); - } - - function lib$es6$promise$$internal$$fulfill(promise, value) { - if (promise._state !== lib$es6$promise$$internal$$PENDING) { return; } - - promise._result = value; - promise._state = lib$es6$promise$$internal$$FULFILLED; - - if (promise._subscribers.length !== 0) { - lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publish, promise); - } - } - - function lib$es6$promise$$internal$$reject(promise, reason) { - if (promise._state !== lib$es6$promise$$internal$$PENDING) { return; } - promise._state = lib$es6$promise$$internal$$REJECTED; - promise._result = reason; - - lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publishRejection, promise); - } - - function lib$es6$promise$$internal$$subscribe(parent, child, onFulfillment, onRejection) { - var subscribers = parent._subscribers; - var length = subscribers.length; - - parent._onerror = null; - - subscribers[length] = child; - subscribers[length + lib$es6$promise$$internal$$FULFILLED] = onFulfillment; - subscribers[length + lib$es6$promise$$internal$$REJECTED] = onRejection; - - if (length === 0 && parent._state) { - lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publish, parent); - } - } - - function lib$es6$promise$$internal$$publish(promise) { - var subscribers = promise._subscribers; - var settled = promise._state; - - if (subscribers.length === 0) { return; } - - var child, callback, detail = promise._result; - - for (var i = 0; i < subscribers.length; i += 3) { - child = subscribers[i]; - callback = subscribers[i + settled]; - - if (child) { - lib$es6$promise$$internal$$invokeCallback(settled, child, callback, detail); - } else { - callback(detail); - } - } - - promise._subscribers.length = 0; - } - - function lib$es6$promise$$internal$$ErrorObject() { - this.error = null; - } - - var lib$es6$promise$$internal$$TRY_CATCH_ERROR = new lib$es6$promise$$internal$$ErrorObject(); - - function lib$es6$promise$$internal$$tryCatch(callback, detail) { - try { - return callback(detail); - } catch(e) { - lib$es6$promise$$internal$$TRY_CATCH_ERROR.error = e; - return lib$es6$promise$$internal$$TRY_CATCH_ERROR; - } - } - - function lib$es6$promise$$internal$$invokeCallback(settled, promise, callback, detail) { - var hasCallback = lib$es6$promise$utils$$isFunction(callback), - value, error, succeeded, failed; - - if (hasCallback) { - value = lib$es6$promise$$internal$$tryCatch(callback, detail); - - if (value === lib$es6$promise$$internal$$TRY_CATCH_ERROR) { - failed = true; - error = value.error; - value = null; - } else { - succeeded = true; - } - - if (promise === value) { - lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$cannotReturnOwn()); - return; - } - - } else { - value = detail; - succeeded = true; - } - - if (promise._state !== lib$es6$promise$$internal$$PENDING) { - // noop - } else if (hasCallback && succeeded) { - lib$es6$promise$$internal$$resolve(promise, value); - } else if (failed) { - lib$es6$promise$$internal$$reject(promise, error); - } else if (settled === lib$es6$promise$$internal$$FULFILLED) { - lib$es6$promise$$internal$$fulfill(promise, value); - } else if (settled === lib$es6$promise$$internal$$REJECTED) { - lib$es6$promise$$internal$$reject(promise, value); - } - } - - function lib$es6$promise$$internal$$initializePromise(promise, resolver) { - try { - resolver(function resolvePromise(value){ - lib$es6$promise$$internal$$resolve(promise, value); - }, function rejectPromise(reason) { - lib$es6$promise$$internal$$reject(promise, reason); - }); - } catch(e) { - lib$es6$promise$$internal$$reject(promise, e); - } - } - - function lib$es6$promise$enumerator$$Enumerator(Constructor, input) { - var enumerator = this; - - enumerator._instanceConstructor = Constructor; - enumerator.promise = new Constructor(lib$es6$promise$$internal$$noop); - - if (enumerator._validateInput(input)) { - enumerator._input = input; - enumerator.length = input.length; - enumerator._remaining = input.length; - - enumerator._init(); - - if (enumerator.length === 0) { - lib$es6$promise$$internal$$fulfill(enumerator.promise, enumerator._result); - } else { - enumerator.length = enumerator.length || 0; - enumerator._enumerate(); - if (enumerator._remaining === 0) { - lib$es6$promise$$internal$$fulfill(enumerator.promise, enumerator._result); - } - } - } else { - lib$es6$promise$$internal$$reject(enumerator.promise, enumerator._validationError()); - } - } - - lib$es6$promise$enumerator$$Enumerator.prototype._validateInput = function(input) { - return lib$es6$promise$utils$$isArray(input); - }; - - lib$es6$promise$enumerator$$Enumerator.prototype._validationError = function() { - return new Error('Array Methods must be provided an Array'); - }; - - lib$es6$promise$enumerator$$Enumerator.prototype._init = function() { - this._result = new Array(this.length); - }; - - var lib$es6$promise$enumerator$$default = lib$es6$promise$enumerator$$Enumerator; - - lib$es6$promise$enumerator$$Enumerator.prototype._enumerate = function() { - var enumerator = this; - - var length = enumerator.length; - var promise = enumerator.promise; - var input = enumerator._input; - - for (var i = 0; promise._state === lib$es6$promise$$internal$$PENDING && i < length; i++) { - enumerator._eachEntry(input[i], i); - } - }; - - lib$es6$promise$enumerator$$Enumerator.prototype._eachEntry = function(entry, i) { - var enumerator = this; - var c = enumerator._instanceConstructor; - - if (lib$es6$promise$utils$$isMaybeThenable(entry)) { - if (entry.constructor === c && entry._state !== lib$es6$promise$$internal$$PENDING) { - entry._onerror = null; - enumerator._settledAt(entry._state, i, entry._result); - } else { - enumerator._willSettleAt(c.resolve(entry), i); - } - } else { - enumerator._remaining--; - enumerator._result[i] = entry; - } - }; - - lib$es6$promise$enumerator$$Enumerator.prototype._settledAt = function(state, i, value) { - var enumerator = this; - var promise = enumerator.promise; - - if (promise._state === lib$es6$promise$$internal$$PENDING) { - enumerator._remaining--; - - if (state === lib$es6$promise$$internal$$REJECTED) { - lib$es6$promise$$internal$$reject(promise, value); - } else { - enumerator._result[i] = value; - } - } - - if (enumerator._remaining === 0) { - lib$es6$promise$$internal$$fulfill(promise, enumerator._result); - } - }; - - lib$es6$promise$enumerator$$Enumerator.prototype._willSettleAt = function(promise, i) { - var enumerator = this; - - lib$es6$promise$$internal$$subscribe(promise, undefined, function(value) { - enumerator._settledAt(lib$es6$promise$$internal$$FULFILLED, i, value); - }, function(reason) { - enumerator._settledAt(lib$es6$promise$$internal$$REJECTED, i, reason); - }); - }; - function lib$es6$promise$promise$all$$all(entries) { - return new lib$es6$promise$enumerator$$default(this, entries).promise; - } - var lib$es6$promise$promise$all$$default = lib$es6$promise$promise$all$$all; - function lib$es6$promise$promise$race$$race(entries) { - /*jshint validthis:true */ - var Constructor = this; - - var promise = new Constructor(lib$es6$promise$$internal$$noop); - - if (!lib$es6$promise$utils$$isArray(entries)) { - lib$es6$promise$$internal$$reject(promise, new TypeError('You must pass an array to race.')); - return promise; - } - - var length = entries.length; - - function onFulfillment(value) { - lib$es6$promise$$internal$$resolve(promise, value); - } - - function onRejection(reason) { - lib$es6$promise$$internal$$reject(promise, reason); - } - - for (var i = 0; promise._state === lib$es6$promise$$internal$$PENDING && i < length; i++) { - lib$es6$promise$$internal$$subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection); - } - - return promise; - } - var lib$es6$promise$promise$race$$default = lib$es6$promise$promise$race$$race; - function lib$es6$promise$promise$resolve$$resolve(object) { - /*jshint validthis:true */ - var Constructor = this; - - if (object && typeof object === 'object' && object.constructor === Constructor) { - return object; - } - - var promise = new Constructor(lib$es6$promise$$internal$$noop); - lib$es6$promise$$internal$$resolve(promise, object); - return promise; - } - var lib$es6$promise$promise$resolve$$default = lib$es6$promise$promise$resolve$$resolve; - function lib$es6$promise$promise$reject$$reject(reason) { - /*jshint validthis:true */ - var Constructor = this; - var promise = new Constructor(lib$es6$promise$$internal$$noop); - lib$es6$promise$$internal$$reject(promise, reason); - return promise; - } - var lib$es6$promise$promise$reject$$default = lib$es6$promise$promise$reject$$reject; - - var lib$es6$promise$promise$$counter = 0; - - function lib$es6$promise$promise$$needsResolver() { - throw new TypeError('You must pass a resolver function as the first argument to the promise constructor'); - } - - function lib$es6$promise$promise$$needsNew() { - throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function."); - } - - var lib$es6$promise$promise$$default = lib$es6$promise$promise$$Promise; - /** - Promise objects represent the eventual result of an asynchronous operation. The - primary way of interacting with a promise is through its `then` method, which - registers callbacks to receive either a promise's eventual value or the reason - why the promise cannot be fulfilled. - - Terminology - ----------- - - - `promise` is an object or function with a `then` method whose behavior conforms to this specification. - - `thenable` is an object or function that defines a `then` method. - - `value` is any legal JavaScript value (including undefined, a thenable, or a promise). - - `exception` is a value that is thrown using the throw statement. - - `reason` is a value that indicates why a promise was rejected. - - `settled` the final resting state of a promise, fulfilled or rejected. - - A promise can be in one of three states: pending, fulfilled, or rejected. - - Promises that are fulfilled have a fulfillment value and are in the fulfilled - state. Promises that are rejected have a rejection reason and are in the - rejected state. A fulfillment value is never a thenable. - - Promises can also be said to *resolve* a value. If this value is also a - promise, then the original promise's settled state will match the value's - settled state. So a promise that *resolves* a promise that rejects will - itself reject, and a promise that *resolves* a promise that fulfills will - itself fulfill. - - - Basic Usage: - ------------ - - ```js - var promise = new Promise(function(resolve, reject) { - // on success - resolve(value); - - // on failure - reject(reason); - }); - - promise.then(function(value) { - // on fulfillment - }, function(reason) { - // on rejection - }); - ``` - - Advanced Usage: - --------------- - - Promises shine when abstracting away asynchronous interactions such as - `XMLHttpRequest`s. - - ```js - function getJSON(url) { - return new Promise(function(resolve, reject){ - var xhr = new XMLHttpRequest(); - - xhr.open('GET', url); - xhr.onreadystatechange = handler; - xhr.responseType = 'json'; - xhr.setRequestHeader('Accept', 'application/json'); - xhr.send(); - - function handler() { - if (this.readyState === this.DONE) { - if (this.status === 200) { - resolve(this.response); - } else { - reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']')); - } - } - }; - }); - } - - getJSON('/posts.json').then(function(json) { - // on fulfillment - }, function(reason) { - // on rejection - }); - ``` - - Unlike callbacks, promises are great composable primitives. - - ```js - Promise.all([ - getJSON('/posts'), - getJSON('/comments') - ]).then(function(values){ - values[0] // => postsJSON - values[1] // => commentsJSON - - return values; - }); - ``` - - @class Promise - @param {function} resolver - Useful for tooling. - @constructor - */ - function lib$es6$promise$promise$$Promise(resolver) { - this._id = lib$es6$promise$promise$$counter++; - this._state = undefined; - this._result = undefined; - this._subscribers = []; - - if (lib$es6$promise$$internal$$noop !== resolver) { - if (!lib$es6$promise$utils$$isFunction(resolver)) { - lib$es6$promise$promise$$needsResolver(); - } - - if (!(this instanceof lib$es6$promise$promise$$Promise)) { - lib$es6$promise$promise$$needsNew(); - } - - lib$es6$promise$$internal$$initializePromise(this, resolver); - } - } - - lib$es6$promise$promise$$Promise.all = lib$es6$promise$promise$all$$default; - lib$es6$promise$promise$$Promise.race = lib$es6$promise$promise$race$$default; - lib$es6$promise$promise$$Promise.resolve = lib$es6$promise$promise$resolve$$default; - lib$es6$promise$promise$$Promise.reject = lib$es6$promise$promise$reject$$default; - lib$es6$promise$promise$$Promise._setScheduler = lib$es6$promise$asap$$setScheduler; - lib$es6$promise$promise$$Promise._setAsap = lib$es6$promise$asap$$setAsap; - lib$es6$promise$promise$$Promise._asap = lib$es6$promise$asap$$asap; - - lib$es6$promise$promise$$Promise.prototype = { - constructor: lib$es6$promise$promise$$Promise, - - /** - The primary way of interacting with a promise is through its `then` method, - which registers callbacks to receive either a promise's eventual value or the - reason why the promise cannot be fulfilled. - - ```js - findUser().then(function(user){ - // user is available - }, function(reason){ - // user is unavailable, and you are given the reason why - }); - ``` - - Chaining - -------- - - The return value of `then` is itself a promise. This second, 'downstream' - promise is resolved with the return value of the first promise's fulfillment - or rejection handler, or rejected if the handler throws an exception. - - ```js - findUser().then(function (user) { - return user.name; - }, function (reason) { - return 'default name'; - }).then(function (userName) { - // If `findUser` fulfilled, `userName` will be the user's name, otherwise it - // will be `'default name'` - }); - - findUser().then(function (user) { - throw new Error('Found user, but still unhappy'); - }, function (reason) { - throw new Error('`findUser` rejected and we're unhappy'); - }).then(function (value) { - // never reached - }, function (reason) { - // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'. - // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'. - }); - ``` - If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream. - - ```js - findUser().then(function (user) { - throw new PedagogicalException('Upstream error'); - }).then(function (value) { - // never reached - }).then(function (value) { - // never reached - }, function (reason) { - // The `PedgagocialException` is propagated all the way down to here - }); - ``` - - Assimilation - ------------ - - Sometimes the value you want to propagate to a downstream promise can only be - retrieved asynchronously. This can be achieved by returning a promise in the - fulfillment or rejection handler. The downstream promise will then be pending - until the returned promise is settled. This is called *assimilation*. - - ```js - findUser().then(function (user) { - return findCommentsByAuthor(user); - }).then(function (comments) { - // The user's comments are now available - }); - ``` - - If the assimliated promise rejects, then the downstream promise will also reject. - - ```js - findUser().then(function (user) { - return findCommentsByAuthor(user); - }).then(function (comments) { - // If `findCommentsByAuthor` fulfills, we'll have the value here - }, function (reason) { - // If `findCommentsByAuthor` rejects, we'll have the reason here - }); - ``` - - Simple Example - -------------- - - Synchronous Example - - ```javascript - var result; - - try { - result = findResult(); - // success - } catch(reason) { - // failure - } - ``` - - Errback Example - - ```js - findResult(function(result, err){ - if (err) { - // failure - } else { - // success - } - }); - ``` - - Promise Example; - - ```javascript - findResult().then(function(result){ - // success - }, function(reason){ - // failure - }); - ``` - - Advanced Example - -------------- - - Synchronous Example - - ```javascript - var author, books; - - try { - author = findAuthor(); - books = findBooksByAuthor(author); - // success - } catch(reason) { - // failure - } - ``` - - Errback Example - - ```js - - function foundBooks(books) { - - } - - function failure(reason) { - - } - - findAuthor(function(author, err){ - if (err) { - failure(err); - // failure - } else { - try { - findBoooksByAuthor(author, function(books, err) { - if (err) { - failure(err); - } else { - try { - foundBooks(books); - } catch(reason) { - failure(reason); - } - } - }); - } catch(error) { - failure(err); - } - // success - } - }); - ``` - - Promise Example; - - ```javascript - findAuthor(). - then(findBooksByAuthor). - then(function(books){ - // found books - }).catch(function(reason){ - // something went wrong - }); - ``` - - @method then - @param {Function} onFulfilled - @param {Function} onRejected - Useful for tooling. - @return {Promise} - */ - then: function(onFulfillment, onRejection) { - var parent = this; - var state = parent._state; - - if (state === lib$es6$promise$$internal$$FULFILLED && !onFulfillment || state === lib$es6$promise$$internal$$REJECTED && !onRejection) { - return this; - } - - var child = new this.constructor(lib$es6$promise$$internal$$noop); - var result = parent._result; - - if (state) { - var callback = arguments[state - 1]; - lib$es6$promise$asap$$asap(function(){ - lib$es6$promise$$internal$$invokeCallback(state, child, callback, result); - }); - } else { - lib$es6$promise$$internal$$subscribe(parent, child, onFulfillment, onRejection); - } - - return child; - }, - - /** - `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same - as the catch block of a try/catch statement. - - ```js - function findAuthor(){ - throw new Error('couldn't find that author'); - } - - // synchronous - try { - findAuthor(); - } catch(reason) { - // something went wrong - } - - // async with promises - findAuthor().catch(function(reason){ - // something went wrong - }); - ``` - - @method catch - @param {Function} onRejection - Useful for tooling. - @return {Promise} - */ - 'catch': function(onRejection) { - return this.then(null, onRejection); - } - }; - function lib$es6$promise$polyfill$$polyfill() { - var local; - - if (typeof global !== 'undefined') { - local = global; - } else if (typeof self !== 'undefined') { - local = self; - } else { - try { - local = Function('return this')(); - } catch (e) { - throw new Error('polyfill failed because global object is unavailable in this environment'); - } - } - - var P = local.Promise; - - if (P && Object.prototype.toString.call(P.resolve()) === '[object Promise]' && !P.cast) { - return; - } - - local.Promise = lib$es6$promise$promise$$default; - } - var lib$es6$promise$polyfill$$default = lib$es6$promise$polyfill$$polyfill; - - var lib$es6$promise$umd$$ES6Promise = { - 'Promise': lib$es6$promise$promise$$default, - 'polyfill': lib$es6$promise$polyfill$$default - }; - - /* global define:true module:true window: true */ - if (typeof define === 'function' && define['amd']) { - define(function() { return lib$es6$promise$umd$$ES6Promise; }); - } else if (typeof module !== 'undefined' && module['exports']) { - module['exports'] = lib$es6$promise$umd$$ES6Promise; - } else if (typeof this !== 'undefined') { - this['ES6Promise'] = lib$es6$promise$umd$$ES6Promise; - } - - lib$es6$promise$polyfill$$default(); -}).call(this); - - -/** - * @constructor - */ -function DefaultHttpRequest() { - - /** - * @name _promiseFactory - * @type DefaultPromiseFactory - */ - - /** - * @param {XMLHttpRequest} xhr - * @param {object.} headers - */ - function setHeaders(xhr, headers) { - var keys = Object.keys(headers); - - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - var value = headers[key]; - - xhr.setRequestHeader(key, value); - } - } - - /** - * @param {string} url - * @param {{ headers: object. }} [config] - * @returns {Promise} - */ - this.getJSON = function (url, config) { - return _promiseFactory.create(function (resolve, reject) { - - try { - var xhr = new XMLHttpRequest(); - xhr.open("GET", url); - xhr.responseType = "json"; - - if (config) { - if (config.headers) { - setHeaders(xhr, config.headers); - } - } - - xhr.onload = function () { - try { - if (xhr.status === 200) { - var response = ""; - // To support IE9 get the response from xhr.responseText not xhr.response - if (window.XDomainRequest) { - response = xhr.responseText; - } else { - response = xhr.response; - } - if (typeof response === "string") { - response = JSON.parse(response); - } - resolve(response); - } - else { - reject(Error(xhr.statusText + "(" + xhr.status + ")")); - } - } - catch (err) { - reject(err); - } - }; - - xhr.onerror = function () { - reject(Error("Network error")); - }; - - xhr.send(); - } - catch (err) { - return reject(err); - } - }); - }; -} - -_httpRequest = new DefaultHttpRequest(); - -/** - * @constructor - * @param {Promise} promise - */ -function DefaultPromise(promise) { - - /** - * @param {function(*):*} successCallback - * @param {function(*):*} errorCallback - * @returns {DefaultPromise} - */ - this.then = function (successCallback, errorCallback) { - var childPromise = promise.then(successCallback, errorCallback); - - return new DefaultPromise(childPromise); - }; - - /** - * - * @param {function(*):*} errorCallback - * @returns {DefaultPromise} - */ - this.catch = function (errorCallback) { - var childPromise = promise.catch(errorCallback); - - return new DefaultPromise(childPromise); - }; -} - -/** - * @constructor - */ -function DefaultPromiseFactory() { - - this.resolve = function (value) { - return new DefaultPromise(Promise.resolve(value)); - }; - - this.reject = function (reason) { - return new DefaultPromise(Promise.reject(reason)); - }; - - /** - * @param {function(resolve:function, reject:function)} callback - * @returns {DefaultPromise} - */ - this.create = function (callback) { - return new DefaultPromise(new Promise(callback)); - }; -} - -_promiseFactory = new DefaultPromiseFactory(); -/* - * Copyright 2015 Dominick Baier, Brock Allen - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -function log() { - //var param = [].join.call(arguments); - //console.log(param); -} - -function copy(obj, target) { - target = target || {}; - for (var key in obj) { - if (obj.hasOwnProperty(key)) { - target[key] = obj[key]; - } - } - return target; -} - -function rand() { - return ((Date.now() + Math.random()) * Math.random()).toString().replace(".", ""); -} - -function resolve(param) { - return _promiseFactory.resolve(param); -} - -function error(message) { - return _promiseFactory.reject(Error(message)); -} - -function parseOidcResult(queryString) { - log("parseOidcResult"); - - queryString = queryString || location.hash; - - var idx = queryString.lastIndexOf("#"); - if (idx >= 0) { - queryString = queryString.substr(idx + 1); - } - - var params = {}, - regex = /([^&=]+)=([^&]*)/g, - m; - - var counter = 0; - while (m = regex.exec(queryString)) { - params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]); - if (counter++ > 50) { - return { - error: "Response exceeded expected number of parameters" - }; - } - } - - for (var prop in params) { - return params; - } -} - -function getJson(url, token) { - log("getJson", url); - - var config = {}; - - if (token) { - config.headers = {"Authorization": "Bearer " + token}; - } - - return _httpRequest.getJSON(url, config); -} - -function OidcClient(settings) { - this._settings = settings || {}; - - if (!this._settings.request_state_key) { - this._settings.request_state_key = "OidcClient.request_state"; - } - - if (!this._settings.request_state_store) { - this._settings.request_state_store = window.localStorage; - } - - if (typeof this._settings.load_user_profile === 'undefined') { - this._settings.load_user_profile = true; - } - - if (typeof this._settings.filter_protocol_claims === 'undefined') { - this._settings.filter_protocol_claims = true; - } - - if (this._settings.authority && this._settings.authority.indexOf('.well-known/openid-configuration') < 0) { - if (this._settings.authority[this._settings.authority.length - 1] !== '/') { - this._settings.authority += '/'; - } - this._settings.authority += '.well-known/openid-configuration'; - } - - if (!this._settings.response_type) { - this._settings.response_type = "id_token token"; - } - - Object.defineProperty(this, "isOidc", { - get: function () { - if (this._settings.response_type) { - var result = this._settings.response_type.split(/\s+/g).filter(function (item) { - return item === "id_token"; - }); - return !!(result[0]); - } - return false; - } - }); - - Object.defineProperty(this, "isOAuth", { - get: function () { - if (this._settings.response_type) { - var result = this._settings.response_type.split(/\s+/g).filter(function (item) { - return item === "token"; - }); - return !!(result[0]); - } - return false; - } - }); -} - -OidcClient.parseOidcResult = parseOidcResult; - -OidcClient.prototype.loadMetadataAsync = function () { - log("OidcClient.loadMetadataAsync"); - - var settings = this._settings; - - if (settings.metadata) { - return resolve(settings.metadata); - } - - if (!settings.authority) { - return error("No authority configured"); - } - - return getJson(settings.authority) - .then(function (metadata) { - settings.metadata = metadata; - return metadata; - }, function (err) { - return error("Failed to load metadata (" + err && err.message + ")"); - }); -}; - -OidcClient.prototype.loadX509SigningKeyAsync = function () { - log("OidcClient.loadX509SigningKeyAsync"); - - var settings = this._settings; - - function getKeyAsync(jwks) { - if (!jwks.keys || !jwks.keys.length) { - return error("Signing keys empty"); - } - - var key = jwks.keys[0]; - if (key.kty !== "RSA") { - return error("Signing key not RSA"); - } - - if (!key.x5c || !key.x5c.length) { - return error("RSA keys empty"); - } - - return resolve(key.x5c[0]); - } - - if (settings.jwks) { - return getKeyAsync(settings.jwks); - } - - return this.loadMetadataAsync().then(function (metadata) { - if (!metadata.jwks_uri) { - return error("Metadata does not contain jwks_uri"); - } - - return getJson(metadata.jwks_uri).then(function (jwks) { - settings.jwks = jwks; - return getKeyAsync(jwks); - }, function (err) { - return error("Failed to load signing keys (" + err && err.message + ")"); - }); - }); -}; - -OidcClient.prototype.loadUserProfile = function (access_token) { - log("OidcClient.loadUserProfile"); - - return this.loadMetadataAsync().then(function (metadata) { - - if (!metadata.userinfo_endpoint) { - return error("Metadata does not contain userinfo_endpoint"); - } - - return getJson(metadata.userinfo_endpoint, access_token); - }); -} - -OidcClient.prototype.loadAuthorizationEndpoint = function () { - log("OidcClient.loadAuthorizationEndpoint"); - - if (this._settings.authorization_endpoint) { - return resolve(this._settings.authorization_endpoint); - } - - if (!this._settings.authority) { - return error("No authorization_endpoint configured"); - } - - return this.loadMetadataAsync().then(function (metadata) { - if (!metadata.authorization_endpoint) { - return error("Metadata does not contain authorization_endpoint"); - } - - return metadata.authorization_endpoint; - }); -}; - -OidcClient.prototype.createTokenRequestAsync = function () { - log("OidcClient.createTokenRequestAsync"); - - var client = this; - var settings = client._settings; - - return client.loadAuthorizationEndpoint().then(function (authorization_endpoint) { - - var state = rand(); - var url = authorization_endpoint + "?state=" + encodeURIComponent(state); - - if (client.isOidc) { - var nonce = rand(); - url += "&nonce=" + encodeURIComponent(nonce); - } - - var required = ["client_id", "redirect_uri", "response_type", "scope"]; - required.forEach(function (key) { - var value = settings[key]; - if (value) { - url += "&" + key + "=" + encodeURIComponent(value); - } - }); - - var optional = ["prompt", "display", "max_age", "ui_locales", "id_token_hint", "login_hint", "acr_values"]; - optional.forEach(function (key) { - var value = settings[key]; - if (value) { - url += "&" + key + "=" + encodeURIComponent(value); - } - }); - - var request_state = { - oidc: client.isOidc, - oauth: client.isOAuth, - state: state - }; - - if (nonce) { - request_state["nonce"] = nonce; - } - - settings.request_state_store.setItem(settings.request_state_key, JSON.stringify(request_state)); - - return { - request_state: request_state, - url: url - }; - }); -} - -OidcClient.prototype.createLogoutRequestAsync = function (id_token_hint) { - log("OidcClient.createLogoutRequestAsync"); - - var settings = this._settings; - return this.loadMetadataAsync().then(function (metadata) { - if (!metadata.end_session_endpoint) { - return error("No end_session_endpoint in metadata"); - } - - var url = metadata.end_session_endpoint; - if (id_token_hint && settings.post_logout_redirect_uri) { - url += "?post_logout_redirect_uri=" + encodeURIComponent(settings.post_logout_redirect_uri); - url += "&id_token_hint=" + encodeURIComponent(id_token_hint); - } - return url; - }); -} - -OidcClient.prototype.validateIdTokenAsync = function (id_token, nonce, access_token) { - log("OidcClient.validateIdTokenAsync"); - - var client = this; - var settings = client._settings; - - return client.loadX509SigningKeyAsync().then(function (cert) { - - var jws = new KJUR.jws.JWS(); - if (jws.verifyJWSByPemX509Cert(id_token, cert)) { - var id_token_contents = JSON.parse(jws.parsedJWS.payloadS); - - if (nonce !== id_token_contents.nonce) { - return error("Invalid nonce"); - } - - return client.loadMetadataAsync().then(function (metadata) { - - if (id_token_contents.iss !== metadata.issuer) { - return error("Invalid issuer"); - } - - if (id_token_contents.aud !== settings.client_id) { - return error("Invalid audience"); - } - - var now = parseInt(Date.now() / 1000); - - // accept tokens issues up to 5 mins ago - var diff = now - id_token_contents.iat; - if (diff > (5 * 60)) { - return error("Token issued too long ago"); - } - - if (id_token_contents.exp < now) { - return error("Token expired"); - } - - if (access_token && settings.load_user_profile) { - // if we have an access token, then call user info endpoint - return client.loadUserProfile(access_token, id_token_contents).then(function (profile) { - return copy(profile, id_token_contents); - }); - } - else { - // no access token, so we have all our claims - return id_token_contents; - } - - }); - } - else { - return error("JWT failed to validate"); - } - - }); - -}; - -OidcClient.prototype.validateAccessTokenAsync = function (id_token_contents, access_token) { - log("OidcClient.validateAccessTokenAsync"); - - if (!id_token_contents.at_hash) { - return error("No at_hash in id_token"); - } - - var hash = KJUR.crypto.Util.sha256(access_token); - var left = hash.substr(0, hash.length / 2); - var left_b64u = hextob64u(left); - - if (left_b64u !== id_token_contents.at_hash) { - return error("at_hash failed to validate"); - } - - return resolve(); -}; - -OidcClient.prototype.validateIdTokenAndAccessTokenAsync = function (id_token, nonce, access_token) { - log("OidcClient.validateIdTokenAndAccessTokenAsync"); - - var client = this; - - return client.validateIdTokenAsync(id_token, nonce, access_token).then(function (id_token_contents) { - - return client.validateAccessTokenAsync(id_token_contents, access_token).then(function () { - - return id_token_contents; - - }); - - }); -} - -OidcClient.prototype.processResponseAsync = function (queryString) { - log("OidcClient.processResponseAsync"); - - var client = this; - var settings = client._settings; - - var request_state = settings.request_state_store.getItem(settings.request_state_key); - settings.request_state_store.removeItem(settings.request_state_key); - - if (!request_state) { - return error("No request state loaded"); - } - - request_state = JSON.parse(request_state); - if (!request_state) { - return error("No request state loaded"); - } - - if (!request_state.state) { - return error("No state loaded"); - } - - var result = parseOidcResult(queryString); - if (!result) { - return error("No OIDC response"); - } - - if (result.error) { - return error(result.error); - } - - if (result.state !== request_state.state) { - return error("Invalid state"); - } - - if (request_state.oidc) { - if (!result.id_token) { - return error("No identity token"); - } - - if (!request_state.nonce) { - return error("No nonce loaded"); - } - } - - if (request_state.oauth) { - if (!result.access_token) { - return error("No access token"); - } - - if (!result.token_type || result.token_type.toLowerCase() !== "bearer") { - return error("Invalid token type"); - } - - if (!result.expires_in) { - return error("No token expiration"); - } - } - - var promise = resolve(); - if (request_state.oidc && request_state.oauth) { - promise = client.validateIdTokenAndAccessTokenAsync(result.id_token, request_state.nonce, result.access_token); - } - else if (request_state.oidc) { - promise = client.validateIdTokenAsync(result.id_token, request_state.nonce); - } - - return promise.then(function (profile) { - if (profile && settings.filter_protocol_claims) { - var remove = ["nonce", "at_hash", "iat", "nbf", "exp", "aud", "iss"]; - remove.forEach(function (key) { - delete profile[key]; - }); - } - - return { - profile: profile, - id_token: result.id_token, - access_token: result.access_token, - expires_in: result.expires_in, - scope: result.scope, - session_state : result.session_state - }; - }); -} - - // exports - OidcClient._promiseFactory = _promiseFactory; - OidcClient._httpRequest = _httpRequest; - window.OidcClient = OidcClient; -})(); -(function () { - -/* -* Copyright 2014-2016 Dominick Baier, Brock Allen -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -// globals -var _promiseFactory = OidcClient._promiseFactory; -var _httpRequest = OidcClient._httpRequest; - -function copy(obj, target) { - target = target || {}; - for (var key in obj) { - if (obj.hasOwnProperty(key)) { - target[key] = obj[key]; - } - } - return target; -} - -function Token(other) { - if (other) { - this.profile = other.profile; - this.id_token = other.id_token; - this.access_token = other.access_token; - if (other.access_token) { - this.expires_at = parseInt(other.expires_at); - } - else if (other.id_token) { - this.expires_at = other.profile.exp; - } - else { - throw Error("Either access_token or id_token required."); - } - this.scope = other.scope; - this.session_state = other.session_state; - } - else { - this.expires_at = 0; - } - - Object.defineProperty(this, "scopes", { - get: function () { - return (this.scope || "").split(" "); - } - }); - - Object.defineProperty(this, "expired", { - get: function () { - var now = parseInt(Date.now() / 1000); - return this.expires_at < now; - } - }); - - Object.defineProperty(this, "expires_in", { - get: function () { - var now = parseInt(Date.now() / 1000); - return this.expires_at - now; - } - }); -} - -Token.fromResponse = function (response) { - if (response.access_token) { - var now = parseInt(Date.now() / 1000); - response.expires_at = now + parseInt(response.expires_in); - } - return new Token(response); -} - -Token.fromJSON = function (json) { - if (json) { - try { - var obj = JSON.parse(json); - return new Token(obj); - } - catch (e) { - } - } - return new Token(null); -} - -Token.prototype.toJSON = function () { - return JSON.stringify({ - profile: this.profile, - id_token: this.id_token, - access_token: this.access_token, - expires_at: this.expires_at, - scope: this.scopes.join(" "), - session_state: this.session_state - }); -} - -function FrameLoader(url, config) { - this.url = url; - config = config || {}; - config.cancelDelay = config.cancelDelay || 5000; - this.config = config; -} - -FrameLoader.prototype.loadAsync = function (url) { - var self = this; - url = url || this.url; - - if (!url) { - return _promiseFactory.reject(Error("No url provided")); - } - - return _promiseFactory.create(function (resolve, reject) { - var frame = window.document.createElement("iframe"); - frame.style.display = "none"; - - function cleanup() { - window.removeEventListener("message", message, false); - if (handle) { - window.clearTimeout(handle); - } - handle = null; - window.document.body.removeChild(frame); - } - - function cancel(e) { - cleanup(); - reject(); - } - - function message(e) { - if (handle && e.origin === location.protocol + "//" + location.host && e.source == frame.contentWindow) { - cleanup(); - resolve(e.data); - } - } - - var handle = window.setTimeout(cancel, self.config.cancelDelay); - window.addEventListener("message", message, false); - window.document.body.appendChild(frame); - frame.src = url; - }); -} - -function loadToken(mgr) { - mgr._token = null; - if (mgr._settings.persist) { - var tokenJson = mgr._settings.store.getItem(mgr._settings.persistKey); - if (tokenJson) { - var token = Token.fromJSON(tokenJson); - if (!token.expired) { - mgr._token = token; - } - } - } -} - -function configureTokenExpiring(mgr) { - - function callback() { - handle = null; - mgr._callTokenExpiring(); - } - - var handle = null; - - function cancel() { - if (handle) { - window.clearTimeout(handle); - handle = null; - } - } - - function setup(duration) { - handle = window.setTimeout(callback, duration * 1000); - } - - function configure() { - cancel(); - - if (!mgr.expired) { - var duration = mgr.expires_in; - if (duration > 60) { - setup(duration - 60); - } - else { - callback(); - } - } - } - - configure(); - - mgr.addOnTokenObtained(configure); - mgr.addOnTokenRemoved(cancel); -} - -function configureAutoRenewToken(mgr) { - - if (mgr._settings.silent_redirect_uri && mgr._settings.silent_renew) { - - mgr.addOnTokenExpiring(function () { - mgr.renewTokenSilentAsync().catch(function (e) { - mgr._callSilentTokenRenewFailed(); - console.error(e && e.message || "Unknown error"); - }); - }); - - } -} - -function configureTokenExpired(mgr) { - - function callback() { - handle = null; - - if (mgr._token) { - mgr.saveToken(null); - } - - mgr._callTokenExpired(); - } - - var handle = null; - - function cancel() { - if (handle) { - window.clearTimeout(handle); - handle = null; - } - } - - function setup(duration) { - handle = window.setTimeout(callback, duration * 1000); - } - - function configure() { - cancel(); - if (mgr.expires_in > 0) { - // register 1 second beyond expiration so we don't get into edge conditions for expiration - setup(mgr.expires_in + 1); - } - } - - configure(); - - mgr.addOnTokenObtained(configure); - mgr.addOnTokenRemoved(cancel); -} - -function TokenManager(settings) { - this._settings = settings || {}; - - if (typeof this._settings.persist === 'undefined') { - this._settings.persist = true; - } - this._settings.store = this._settings.store || window.localStorage; - this._settings.persistKey = this._settings.persistKey || "TokenManager.token"; - - this.oidcClient = new OidcClient(this._settings); - - this._callbacks = { - tokenRemovedCallbacks: [], - tokenExpiringCallbacks: [], - tokenExpiredCallbacks: [], - tokenObtainedCallbacks: [], - silentTokenRenewFailedCallbacks: [] - }; - - Object.defineProperty(this, "profile", { - get: function () { - if (this._token) { - return this._token.profile; - } - } - }); - Object.defineProperty(this, "id_token", { - get: function () { - if (this._token) { - return this._token.id_token; - } - } - }); - Object.defineProperty(this, "access_token", { - get: function () { - if (this._token && !this._token.expired) { - return this._token.access_token; - } - } - }); - Object.defineProperty(this, "expired", { - get: function () { - if (this._token) { - return this._token.expired; - } - return true; - } - }); - Object.defineProperty(this, "expires_in", { - get: function () { - if (this._token) { - return this._token.expires_in; - } - return 0; - } - }); - Object.defineProperty(this, "expires_at", { - get: function () { - if (this._token) { - return this._token.expires_at; - } - return 0; - } - }); - Object.defineProperty(this, "scope", { - get: function () { - return this._token && this._token.scope; - } - }); - Object.defineProperty(this, "scopes", { - get: function () { - if (this._token) { - return [].concat(this._token.scopes); - } - return []; - } - }); - Object.defineProperty(this, "session_state", { - get: function () { - if (this._token) { - return this._token.session_state; - } - } - }); - - var mgr = this; - loadToken(mgr); - if (mgr._settings.store instanceof window.localStorage.constructor) { - window.addEventListener("storage", function (e) { - if (e.key === mgr._settings.persistKey) { - loadToken(mgr); - - if (mgr._token) { - mgr._callTokenObtained(); - } - else { - mgr._callTokenRemoved(); - } - } - }); - } - configureTokenExpired(mgr); - configureAutoRenewToken(mgr); - - // delay this so consuming apps can register for callbacks first - window.setTimeout(function () { - configureTokenExpiring(mgr); - }, 0); -} - -/** - * @param {{ create:function(successCallback:function(), errorCallback:function()):Promise, resolve:function(value:*):Promise, reject:function():Promise}} promiseFactory - */ -TokenManager.setPromiseFactory = function (promiseFactory) { - _promiseFactory = promiseFactory; -}; - -/** - * @param {{getJSON:function(url:string, config:{ headers: object. })}} httpRequest - */ -TokenManager.setHttpRequest = function (httpRequest) { - if ((typeof httpRequest !== 'object') || (typeof httpRequest.getJSON !== 'function')) { - throw Error('The provided value is not a valid http request.'); - } - - _httpRequest = httpRequest; -}; - -TokenManager.prototype._callTokenRemoved = function () { - this._callbacks.tokenRemovedCallbacks.forEach(function (cb) { - cb(); - }); -} - -TokenManager.prototype._callTokenExpiring = function () { - this._callbacks.tokenExpiringCallbacks.forEach(function (cb) { - cb(); - }); -} - -TokenManager.prototype._callTokenExpired = function () { - this._callbacks.tokenExpiredCallbacks.forEach(function (cb) { - cb(); - }); -} - -TokenManager.prototype._callTokenObtained = function () { - this._callbacks.tokenObtainedCallbacks.forEach(function (cb) { - cb(); - }); -} - -TokenManager.prototype._callSilentTokenRenewFailed = function () { - this._callbacks.silentTokenRenewFailedCallbacks.forEach(function (cb) { - cb(); - }); -} - -TokenManager.prototype.saveToken = function (token) { - if (token && !(token instanceof Token)) { - token = Token.fromResponse(token); - } - - this._token = token; - - if (this._settings.persist && !this.expired) { - this._settings.store.setItem(this._settings.persistKey, token.toJSON()); - } - else { - this._settings.store.removeItem(this._settings.persistKey); - } - - if (token) { - this._callTokenObtained(); - } - else { - this._callTokenRemoved(); - } -} - -TokenManager.prototype.addOnTokenRemoved = function (cb) { - this._callbacks.tokenRemovedCallbacks.push(cb); -} - -TokenManager.prototype.addOnTokenObtained = function (cb) { - this._callbacks.tokenObtainedCallbacks.push(cb); -} - -TokenManager.prototype.addOnTokenExpiring = function (cb) { - this._callbacks.tokenExpiringCallbacks.push(cb); -} - -TokenManager.prototype.addOnTokenExpired = function (cb) { - this._callbacks.tokenExpiredCallbacks.push(cb); -} - -TokenManager.prototype.addOnSilentTokenRenewFailed = function (cb) { - this._callbacks.silentTokenRenewFailedCallbacks.push(cb); -} - -TokenManager.prototype.removeToken = function () { - this.saveToken(null); -} - -TokenManager.prototype.redirectForToken = function () { - var oidc = this.oidcClient; - return oidc.createTokenRequestAsync().then(function (request) { - window.location = request.url; - }, function (err) { - console.error("TokenManager.redirectForToken error: " + (err && err.message || "Unknown error")); - return _promiseFactory.reject(err); - }); -} - -TokenManager.prototype.redirectForLogout = function () { - var mgr = this; - return mgr.oidcClient.createLogoutRequestAsync(mgr.id_token).then(function (url) { - mgr.removeToken(); - window.location = url; - }, function (err) { - console.error("TokenManager.redirectForLogout error: " + (err && err.message || "Unknown error")); - return _promiseFactory.reject(err); - }); -} - -TokenManager.prototype.processTokenCallbackAsync = function (queryString) { - var mgr = this; - return mgr.oidcClient.processResponseAsync(queryString).then(function (token) { - mgr.saveToken(token); - }); -} - -TokenManager.prototype.renewTokenSilentAsync = function () { - var mgr = this; - - if (!mgr._settings.silent_redirect_uri) { - return _promiseFactory.reject(Error("silent_redirect_uri not configured")); - } - - var settings = copy(mgr._settings); - settings.redirect_uri = settings.silent_redirect_uri; - if (!settings.prompt) { - settings.prompt = "none"; - } - - var oidc = new OidcClient(settings); - return oidc.createTokenRequestAsync().then(function (request) { - var frame = new FrameLoader(request.url, { cancelDelay: mgr._settings.silent_renew_timeout }); - return frame.loadAsync().then(function (hash) { - return oidc.processResponseAsync(hash).then(function (token) { - mgr.saveToken(token); - }); - }); - }); -} - -TokenManager.prototype.processTokenCallbackSilent = function (hash) { - if (window.parent && window !== window.parent) { - var hash = hash || window.location.hash; - if (hash) { - window.parent.postMessage(hash, location.protocol + "//" + location.host); - } - } -} - -TokenManager.prototype.openPopupForTokenAsync = function (popupSettings) { - popupSettings = popupSettings || {}; - popupSettings.features = popupSettings.features || "location=no,toolbar=no"; - popupSettings.target = popupSettings.target || "_blank"; - - var callback_prefix = "tokenmgr_callback_"; - - // this is a shared callback - if (!window.openPopupForTokenAsyncCallback) { - window.openPopupForTokenAsyncCallback = function (hash) { - var result = OidcClient.parseOidcResult(hash); - if (result && result.state && window[callback_prefix + result.state]) { - window[callback_prefix + result.state](hash); - } - } - } - - var mgr = this; - var settings = copy(mgr._settings); - settings.redirect_uri = settings.popup_redirect_uri || settings.redirect_uri; - - if (mgr._pendingPopup) { - return _promiseFactory.create(function (resolve, reject) { - reject(Error("Already a pending popup token request.")); - }); - } - - var popup = window.open(settings.redirect_uri, popupSettings.target, popupSettings.features); - if (!popup) { - return _promiseFactory.create(function (resolve, reject) { - reject(Error("Error opening popup.")); - }); - } - - mgr._pendingPopup = true; - - function cleanup(name) { - if (handle) { - window.clearInterval(handle); - } - popup.close(); - delete mgr._pendingPopup; - if (name) { - delete window[name]; - } - } - - var reject_popup; - function checkClosed() { - if (!popup.window) { - cleanup(); - reject_popup(Error("Popup closed")); - } - } - var handle = window.setInterval(checkClosed, 1000); - - return _promiseFactory.create(function (resolve, reject) { - reject_popup = reject; - - var oidc = new OidcClient(settings); - oidc.createTokenRequestAsync().then(function (request) { - - var callback_name = callback_prefix + request.request_state.state; - window[callback_name] = function (hash) { - cleanup(callback_name); - - oidc.processResponseAsync(hash).then(function (token) { - mgr.saveToken(token); - resolve(); - }, function (err) { - reject(err); - }); - }; - - // give the popup 5 seconds to ready itself, otherwise fail - var seconds_to_wait = 5; - var interval = 500; - var total_times = (seconds_to_wait*1000) / interval; - var count = 0; - function redirectPopup() { - if (popup.setUrl) { - popup.setUrl(request.url); - } - else if (count < total_times) { - count++; - window.setTimeout(redirectPopup, interval); - } - else { - cleanup(callback_name); - reject(Error("Timeout error on popup")); - } - } - redirectPopup(); - }, function (err) { - cleanup(); - reject(err); - }); - }); -} - -TokenManager.prototype.processTokenPopup = function (hash) { - hash = hash || window.location.hash; - - window.setUrl = function (url) { - window.location = url; - } - - if (hash) { - window.opener.openPopupForTokenAsyncCallback(hash); - } -} - - - // exports - window.OidcTokenManager = TokenManager; -})(); diff --git a/src/Services/Basket/Basket.API/Auth/Client/popup.html b/src/Services/Basket/Basket.API/Auth/Client/popup.html deleted file mode 100644 index 364f8d7dd..000000000 --- a/src/Services/Basket/Basket.API/Auth/Client/popup.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/src/Services/Basket/Basket.API/Auth/Server/AuthorizationHeaderParameterOperationFilter.cs b/src/Services/Basket/Basket.API/Auth/Server/AuthorizationHeaderParameterOperationFilter.cs deleted file mode 100644 index 2acda7be8..000000000 --- a/src/Services/Basket/Basket.API/Auth/Server/AuthorizationHeaderParameterOperationFilter.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Basket.API.Auth.Server; - -public class AuthorizationHeaderParameterOperationFilter : IOperationFilter -{ - public void Apply(OpenApiOperation operation, OperationFilterContext context) - { - var filterPipeline = context.ApiDescription.ActionDescriptor.FilterDescriptors; - var isAuthorized = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is AuthorizeFilter); - var allowAnonymous = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is IAllowAnonymousFilter); - - if (isAuthorized && !allowAnonymous) - { - operation.Parameters ??= new List(); - - - operation.Parameters.Add(new OpenApiParameter - { - Name = "Authorization", - In = ParameterLocation.Header, - Description = "access token", - Required = true - }); - } - } -} diff --git a/src/Services/Basket/Basket.API/Basket.API.csproj b/src/Services/Basket/Basket.API/Basket.API.csproj index 74283d316..173169da1 100644 --- a/src/Services/Basket/Basket.API/Basket.API.csproj +++ b/src/Services/Basket/Basket.API/Basket.API.csproj @@ -1,59 +1,25 @@  - - net7.0 - $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; - ..\..\..\..\docker-compose.dcproj - false - true - + + net7.0 + ..\..\..\..\docker-compose.dcproj + 2964ec8e-0d48-4541-b305-94cab537f867 + - - - PreserveNewest - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - + + + - - - - - + + + diff --git a/src/Services/Basket/Basket.API/Controllers/BasketController.cs b/src/Services/Basket/Basket.API/Controllers/BasketController.cs index 5468bbc15..b971c96e9 100644 --- a/src/Services/Basket/Basket.API/Controllers/BasketController.cs +++ b/src/Services/Basket/Basket.API/Controllers/BasketController.cs @@ -56,7 +56,7 @@ public class BasketController : ControllerBase return BadRequest(); } - var userName = this.HttpContext.User.FindFirst(x => x.Type == ClaimTypes.Name).Value; + var userName = User.FindFirst(x => x.Type == ClaimTypes.Name).Value; var eventMessage = new UserCheckoutAcceptedIntegrationEvent(userId, userName, basketCheckout.City, basketCheckout.Street, basketCheckout.State, basketCheckout.Country, basketCheckout.ZipCode, basketCheckout.CardNumber, basketCheckout.CardHolderName, @@ -71,7 +71,7 @@ public class BasketController : ControllerBase } catch (Exception ex) { - _logger.LogError(ex, "ERROR Publishing integration event: {IntegrationEventId} from {AppName}", eventMessage.Id, Program.AppName); + _logger.LogError(ex, "Error Publishing integration event: {IntegrationEventId}", eventMessage.Id); throw; } diff --git a/src/Services/Basket/Basket.API/Controllers/HomeController.cs b/src/Services/Basket/Basket.API/Controllers/HomeController.cs deleted file mode 100644 index 8b2b7c2e7..000000000 --- a/src/Services/Basket/Basket.API/Controllers/HomeController.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers; - -public class HomeController : Controller -{ - // GET: // - public IActionResult Index() - { - return new RedirectResult("~/swagger"); - } -} - diff --git a/src/Services/Basket/Basket.API/CustomExtensionMethods.cs b/src/Services/Basket/Basket.API/CustomExtensionMethods.cs deleted file mode 100644 index 8fcbf0c28..000000000 --- a/src/Services/Basket/Basket.API/CustomExtensionMethods.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Basket.API; - -public static class CustomExtensionMethods -{ - public static IServiceCollection AddCustomHealthCheck(this IServiceCollection services, IConfiguration configuration) - { - var hcBuilder = services.AddHealthChecks(); - - hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy()); - - hcBuilder - .AddRedis( - configuration["ConnectionString"], - name: "redis-check", - tags: new string[] { "redis" }); - - if (configuration.GetValue("AzureServiceBusEnabled")) - { - hcBuilder - .AddAzureServiceBusTopic( - configuration["EventBusConnection"], - topicName: "eshop_event_bus", - name: "basket-servicebus-check", - tags: new string[] { "servicebus" }); - } - else - { - hcBuilder - .AddRabbitMQ( - $"amqp://{configuration["EventBusConnection"]}", - name: "basket-rabbitmqbus-check", - tags: new string[] { "rabbitmqbus" }); - } - - return services; - } -} \ No newline at end of file diff --git a/src/Services/Basket/Basket.API/Dockerfile b/src/Services/Basket/Basket.API/Dockerfile index 83e268825..71bfc1547 100644 --- a/src/Services/Basket/Basket.API/Dockerfile +++ b/src/Services/Basket/Basket.API/Dockerfile @@ -32,6 +32,7 @@ COPY "Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj" COPY "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" COPY "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" COPY "Services/Payment/Payment.API/Payment.API.csproj" "Services/Payment/Payment.API/Payment.API.csproj" +COPY "Services/Services.Common/Services.Common.csproj" "Services/Services.Common/Services.Common.csproj" COPY "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" COPY "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" COPY "Web/WebhookClient/WebhookClient.csproj" "Web/WebhookClient/WebhookClient.csproj" diff --git a/src/Services/Basket/Basket.API/Extensions/Extensions.cs b/src/Services/Basket/Basket.API/Extensions/Extensions.cs new file mode 100644 index 000000000..7fce10b50 --- /dev/null +++ b/src/Services/Basket/Basket.API/Extensions/Extensions.cs @@ -0,0 +1,20 @@ +public static class Extensions +{ + public static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration) + { + services.AddHealthChecks() + .AddRedis(_ => configuration.GetRequiredConnectionString("redis"), "redis", tags: new[] { "ready", "liveness" }); + + return services; + } + + public static IServiceCollection AddRedis(this IServiceCollection services, IConfiguration configuration) + { + return services.AddSingleton(sp => + { + var redisConfig = ConfigurationOptions.Parse(configuration.GetRequiredConnectionString("redis"), true); + + return ConnectionMultiplexer.Connect(redisConfig); + }); + } +} diff --git a/src/Services/Basket/Basket.API/GlobalUsings.cs b/src/Services/Basket/Basket.API/GlobalUsings.cs index 73978e20c..9a8376d4d 100644 --- a/src/Services/Basket/Basket.API/GlobalUsings.cs +++ b/src/Services/Basket/Basket.API/GlobalUsings.cs @@ -1,59 +1,29 @@ -global using Autofac.Extensions.DependencyInjection; -global using Autofac; -global using Azure.Core; -global using Azure.Identity; -global using Basket.API.Infrastructure.ActionResults; -global using Basket.API.Infrastructure.Exceptions; -global using Basket.API.Infrastructure.Filters; -global using Basket.API.Infrastructure.Middlewares; +global using System; +global using System.Collections.Generic; +global using System.ComponentModel.DataAnnotations; +global using System.Linq; +global using System.Net; +global using System.Security.Claims; +global using System.Text.Json; +global using System.Threading.Tasks; global using Basket.API.IntegrationEvents.EventHandling; global using Basket.API.IntegrationEvents.Events; global using Basket.API.Model; +global using Basket.API.Repositories; global using Grpc.Core; global using GrpcBasket; -global using HealthChecks.UI.Client; global using Microsoft.AspNetCore.Authorization; global using Microsoft.AspNetCore.Builder; -global using Microsoft.AspNetCore.Diagnostics.HealthChecks; -global using Microsoft.AspNetCore.Hosting; -global using Microsoft.AspNetCore.Http.Features; global using Microsoft.AspNetCore.Http; -global using Microsoft.AspNetCore.Mvc.Authorization; -global using Microsoft.AspNetCore.Mvc.Filters; global using Microsoft.AspNetCore.Mvc; -global using Microsoft.AspNetCore.Server.Kestrel.Core; -global using Microsoft.AspNetCore; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; -global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; -global using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; -global using Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus; -global using Microsoft.eShopOnContainers.Services.Basket.API.Controllers; -global using Microsoft.eShopOnContainers.Services.Basket.API.Infrastructure.Repositories; global using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.EventHandling; global using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Events; global using Microsoft.eShopOnContainers.Services.Basket.API.Model; global using Microsoft.eShopOnContainers.Services.Basket.API.Services; -global using Microsoft.eShopOnContainers.Services.Basket.API; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.Diagnostics.HealthChecks; -global using Microsoft.Extensions.Hosting; global using Microsoft.Extensions.Logging; -global using Microsoft.Extensions.Options; -global using Microsoft.OpenApi.Models; -global using RabbitMQ.Client; -global using Serilog.Context; -global using Serilog; +global using Services.Common; global using StackExchange.Redis; -global using Swashbuckle.AspNetCore.SwaggerGen; -global using System.Collections.Generic; -global using System.ComponentModel.DataAnnotations; -global using System.IdentityModel.Tokens.Jwt; -global using System.IO; -global using System.Linq; -global using System.Net; -global using System.Security.Claims; -global using System.Text.Json; -global using System.Threading.Tasks; -global using System; diff --git a/src/Services/Basket/Basket.API/Infrastructure/ActionResults/InternalServerErrorObjectResult.cs b/src/Services/Basket/Basket.API/Infrastructure/ActionResults/InternalServerErrorObjectResult.cs deleted file mode 100644 index 5f95e586e..000000000 --- a/src/Services/Basket/Basket.API/Infrastructure/ActionResults/InternalServerErrorObjectResult.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Basket.API.Infrastructure.ActionResults; - -public class InternalServerErrorObjectResult : ObjectResult -{ - public InternalServerErrorObjectResult(object error) - : base(error) - { - StatusCode = StatusCodes.Status500InternalServerError; - } -} - diff --git a/src/Services/Basket/Basket.API/Infrastructure/Exceptions/BasketDomainException.cs b/src/Services/Basket/Basket.API/Infrastructure/Exceptions/BasketDomainException.cs deleted file mode 100644 index 0502b7924..000000000 --- a/src/Services/Basket/Basket.API/Infrastructure/Exceptions/BasketDomainException.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Basket.API.Infrastructure.Exceptions; - -public class BasketDomainException : Exception -{ - public BasketDomainException() - { } - - public BasketDomainException(string message) - : base(message) - { } - - public BasketDomainException(string message, Exception innerException) - : base(message, innerException) - { } -} - diff --git a/src/Services/Basket/Basket.API/Infrastructure/Exceptions/FailingMiddlewareAppBuilderExtensions.cs b/src/Services/Basket/Basket.API/Infrastructure/Exceptions/FailingMiddlewareAppBuilderExtensions.cs deleted file mode 100644 index 66f55dddd..000000000 --- a/src/Services/Basket/Basket.API/Infrastructure/Exceptions/FailingMiddlewareAppBuilderExtensions.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Basket.API.Infrastructure.Middlewares; - -public static class FailingMiddlewareAppBuilderExtensions -{ - public static IApplicationBuilder UseFailingMiddleware(this IApplicationBuilder builder) - { - return UseFailingMiddleware(builder, null); - } - - public static IApplicationBuilder UseFailingMiddleware(this IApplicationBuilder builder, Action action) - { - var options = new FailingOptions(); - action?.Invoke(options); - builder.UseMiddleware(options); - return builder; - } -} - diff --git a/src/Services/Basket/Basket.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs b/src/Services/Basket/Basket.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs deleted file mode 100644 index 00b0b5195..000000000 --- a/src/Services/Basket/Basket.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace Basket.API.Infrastructure.Filters; - -public partial class HttpGlobalExceptionFilter : IExceptionFilter -{ - private readonly IWebHostEnvironment env; - private readonly ILogger logger; - - public HttpGlobalExceptionFilter(IWebHostEnvironment env, ILogger logger) - { - this.env = env; - this.logger = logger; - } - - public void OnException(ExceptionContext context) - { - logger.LogError(new EventId(context.Exception.HResult), - context.Exception, - context.Exception.Message); - - if (context.Exception.GetType() == typeof(BasketDomainException)) - { - var json = new JsonErrorResponse - { - Messages = new[] { context.Exception.Message } - }; - - context.Result = new BadRequestObjectResult(json); - context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; - } - else - { - var json = new JsonErrorResponse - { - Messages = new[] { "An error occurred. Try it again." } - }; - - if (env.IsDevelopment()) - { - json.DeveloperMessage = context.Exception; - } - - context.Result = new InternalServerErrorObjectResult(json); - context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; - } - context.ExceptionHandled = true; - } -} diff --git a/src/Services/Basket/Basket.API/Infrastructure/Filters/JsonErrorResponse.cs b/src/Services/Basket/Basket.API/Infrastructure/Filters/JsonErrorResponse.cs deleted file mode 100644 index 88bc02eda..000000000 --- a/src/Services/Basket/Basket.API/Infrastructure/Filters/JsonErrorResponse.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Basket.API.Infrastructure.Filters; - -public class JsonErrorResponse -{ - public string[] Messages { get; set; } - - public object DeveloperMessage { get; set; } -} - diff --git a/src/Services/Basket/Basket.API/Infrastructure/Filters/ValidateModelStateFilter.cs b/src/Services/Basket/Basket.API/Infrastructure/Filters/ValidateModelStateFilter.cs deleted file mode 100644 index 5c97b85dc..000000000 --- a/src/Services/Basket/Basket.API/Infrastructure/Filters/ValidateModelStateFilter.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace Basket.API.Infrastructure.Filters; - -public class ValidateModelStateFilter : ActionFilterAttribute -{ - public override void OnActionExecuting(ActionExecutingContext context) - { - if (context.ModelState.IsValid) - { - return; - } - - var validationErrors = context.ModelState - .Keys - .SelectMany(k => context.ModelState[k].Errors) - .Select(e => e.ErrorMessage) - .ToArray(); - - var json = new JsonErrorResponse - { - Messages = validationErrors - }; - - context.Result = new BadRequestObjectResult(json); - } -} - diff --git a/src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingMiddleware.cs b/src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingMiddleware.cs deleted file mode 100644 index 60fbdb655..000000000 --- a/src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingMiddleware.cs +++ /dev/null @@ -1,90 +0,0 @@ -namespace Basket.API.Infrastructure.Middlewares; - -using Microsoft.Extensions.Logging; - -public class FailingMiddleware -{ - private readonly RequestDelegate _next; - private bool _mustFail; - private readonly FailingOptions _options; - private readonly ILogger _logger; - - public FailingMiddleware(RequestDelegate next, ILogger logger, FailingOptions options) - { - _next = next; - _options = options; - _mustFail = false; - _logger = logger; - } - - public async Task Invoke(HttpContext context) - { - var path = context.Request.Path; - if (path.Equals(_options.ConfigPath, StringComparison.OrdinalIgnoreCase)) - { - await ProcessConfigRequest(context); - return; - } - - if (MustFail(context)) - { - _logger.LogInformation("Response for path {Path} will fail.", path); - context.Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError; - context.Response.ContentType = "text/plain"; - await context.Response.WriteAsync("Failed due to FailingMiddleware enabled."); - } - else - { - await _next.Invoke(context); - } - } - - private async Task ProcessConfigRequest(HttpContext context) - { - var enable = context.Request.Query.Keys.Any(k => k == "enable"); - var disable = context.Request.Query.Keys.Any(k => k == "disable"); - - if (enable && disable) - { - throw new ArgumentException("Must use enable or disable querystring values, but not both"); - } - - if (disable) - { - _mustFail = false; - await SendOkResponse(context, "FailingMiddleware disabled. Further requests will be processed."); - return; - } - if (enable) - { - _mustFail = true; - await SendOkResponse(context, "FailingMiddleware enabled. Further requests will return HTTP 500"); - return; - } - - // If reach here, that means that no valid parameter has been passed. Just output status - await SendOkResponse(context, string.Format("FailingMiddleware is {0}", _mustFail ? "enabled" : "disabled")); - return; - } - - private async Task SendOkResponse(HttpContext context, string message) - { - context.Response.StatusCode = (int)System.Net.HttpStatusCode.OK; - context.Response.ContentType = "text/plain"; - await context.Response.WriteAsync(message); - } - - private bool MustFail(HttpContext context) - { - var rpath = context.Request.Path.Value; - - if (_options.NotFilteredPaths.Any(p => p.Equals(rpath, StringComparison.InvariantCultureIgnoreCase))) - { - return false; - } - - return _mustFail && - (_options.EndpointPaths.Any(x => x == rpath) - || _options.EndpointPaths.Count == 0); - } -} diff --git a/src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingOptions.cs b/src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingOptions.cs deleted file mode 100644 index 7818938d2..000000000 --- a/src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingOptions.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Basket.API.Infrastructure.Middlewares; - -public class FailingOptions -{ - public string ConfigPath = "/Failing"; - public List EndpointPaths { get; set; } = new List(); - - public List NotFilteredPaths { get; set; } = new List(); -} - diff --git a/src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingStartupFilter.cs b/src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingStartupFilter.cs deleted file mode 100644 index 74da62b5d..000000000 --- a/src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingStartupFilter.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Basket.API.Infrastructure.Middlewares; - -public class FailingStartupFilter : IStartupFilter -{ - private readonly Action _options; - public FailingStartupFilter(Action optionsAction) - { - _options = optionsAction; - } - - public Action Configure(Action next) - { - return app => - { - app.UseFailingMiddleware(_options); - next(app); - }; - } -} - diff --git a/src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingWebHostBuilderExtensions.cs b/src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingWebHostBuilderExtensions.cs deleted file mode 100644 index 8a8ba9523..000000000 --- a/src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingWebHostBuilderExtensions.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Basket.API.Infrastructure.Middlewares; - -public static class WebHostBuildertExtensions -{ - public static IWebHostBuilder UseFailing(this IWebHostBuilder builder, Action options) - { - builder.ConfigureServices(services => - { - services.AddSingleton(new FailingStartupFilter(options)); - }); - return builder; - } -} - diff --git a/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs b/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs index 2c93f82fd..8796197c0 100644 --- a/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs +++ b/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs @@ -15,9 +15,9 @@ public class OrderStartedIntegrationEventHandler : IIntegrationEventHandler> { new ("IntegrationEventContext", @event.Id) })) { - _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + _logger.LogInformation("Handling integration event: {IntegrationEventId} - ({@IntegrationEvent})", @event.Id, @event); await _repository.DeleteBasketAsync(@event.UserId.ToString()); } diff --git a/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs b/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs index b389b73d7..94c39cd5e 100644 --- a/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs +++ b/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs @@ -15,9 +15,9 @@ public class ProductPriceChangedIntegrationEventHandler : IIntegrationEventHandl public async Task Handle(ProductPriceChangedIntegrationEvent @event) { - using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + using (_logger.BeginScope(new List> { new ("IntegrationEventContext", @event.Id) })) { - _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + _logger.LogInformation("Handling integration event: {IntegrationEventId} - ({@IntegrationEvent})", @event.Id, @event); var userIds = _repository.GetUsers(); @@ -36,7 +36,7 @@ public class ProductPriceChangedIntegrationEventHandler : IIntegrationEventHandl if (itemsToUpdate != null) { - _logger.LogInformation("----- ProductPriceChangedIntegrationEventHandler - Updating items in basket for user: {BuyerId} ({@Items})", basket.BuyerId, itemsToUpdate); + _logger.LogInformation("ProductPriceChangedIntegrationEventHandler - Updating items in basket for user: {BuyerId} ({@Items})", basket.BuyerId, itemsToUpdate); foreach (var item in itemsToUpdate) { diff --git a/src/Services/Basket/Basket.API/Program.cs b/src/Services/Basket/Basket.API/Program.cs index 9554841ec..28f3b1b1e 100644 --- a/src/Services/Basket/Basket.API/Program.cs +++ b/src/Services/Basket/Basket.API/Program.cs @@ -1,333 +1,30 @@ -using Autofac.Core; -using Microsoft.Azure.Amqp.Framing; -using Microsoft.Extensions.Configuration; +var builder = WebApplication.CreateBuilder(args); -var appName = "Basket.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.AddServiceDefaults(); -builder.Services.AddGrpc(options => -{ - options.EnableDetailedErrors = true; -}); -builder.Services.AddApplicationInsightsTelemetry(builder.Configuration); -builder.Services.AddApplicationInsightsKubernetesEnricher(); -builder.Services.AddControllers(options => -{ - options.Filters.Add(typeof(HttpGlobalExceptionFilter)); - options.Filters.Add(typeof(ValidateModelStateFilter)); +builder.Services.AddGrpc(); +builder.Services.AddControllers(); +builder.Services.AddProblemDetails(); -}) // Added for functional tests - .AddApplicationPart(typeof(BasketController).Assembly) - .AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true); -builder.Services.AddSwaggerGen(options => -{ - options.SwaggerDoc("v1", new OpenApiInfo - { - Title = "eShopOnContainers - Basket HTTP API", - Version = "v1", - Description = "The Basket Service HTTP API" - }); +builder.Services.AddHealthChecks(builder.Configuration); +builder.Services.AddRedis(builder.Configuration); - options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme - { - Type = SecuritySchemeType.OAuth2, - Flows = new OpenApiOAuthFlows() - { - Implicit = new OpenApiOAuthFlow() - { - AuthorizationUrl = new Uri($"{builder.Configuration.GetValue("IdentityUrlExternal")}/connect/authorize"), - TokenUrl = new Uri($"{builder.Configuration.GetValue("IdentityUrlExternal")}/connect/token"), - Scopes = new Dictionary() { { "basket", "Basket API" } } - } - } - }); +builder.Services.AddTransient(); +builder.Services.AddTransient(); - options.OperationFilter(); -}); - -// prevent from mapping "sub" claim to nameidentifier. -JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); - -var identityUrl = builder.Configuration.GetValue("IdentityUrl"); - -builder.Services.AddAuthentication("Bearer").AddJwtBearer(options => -{ - options.Authority = identityUrl; - options.RequireHttpsMetadata = false; - options.Audience = "basket"; - options.TokenValidationParameters.ValidateAudience = false; -}); -builder.Services.AddAuthorization(options => -{ - options.AddPolicy("ApiScope", policy => - { - policy.RequireAuthenticatedUser(); - policy.RequireClaim("scope", "basket"); - }); -}); - -builder.Services.AddCustomHealthCheck(builder.Configuration); - -builder.Services.Configure(builder.Configuration); - -builder.Services.AddSingleton(sp => -{ - var settings = sp.GetRequiredService>().Value; - var configuration = ConfigurationOptions.Parse(settings.ConnectionString, true); - - return ConnectionMultiplexer.Connect(configuration); -}); - - -if (builder.Configuration.GetValue("AzureServiceBusEnabled")) -{ - builder.Services.AddSingleton(sp => - { - var serviceBusConnectionString = builder.Configuration["EventBusConnection"]; - - return new DefaultServiceBusPersisterConnection(serviceBusConnectionString); - }); -} -else -{ - builder.Services.AddSingleton(sp => - { - var logger = sp.GetRequiredService>(); - - var factory = new ConnectionFactory() - { - HostName = builder.Configuration["EventBusConnection"], - DispatchConsumersAsync = true - }; - - if (!string.IsNullOrEmpty(builder.Configuration["EventBusUserName"])) - { - factory.UserName = builder.Configuration["EventBusUserName"]; - } - - if (!string.IsNullOrEmpty(builder.Configuration["EventBusPassword"])) - { - factory.Password = builder.Configuration["EventBusPassword"]; - } - - var retryCount = 5; - if (!string.IsNullOrEmpty(builder.Configuration["EventBusRetryCount"])) - { - retryCount = int.Parse(builder.Configuration["EventBusRetryCount"]); - } - - return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount); - }); -} -builder.Services.RegisterEventBus(builder.Configuration); -builder.Services.AddCors(options => -{ - options.AddPolicy("CorsPolicy", - builder => builder - .SetIsOriginAllowed((host) => true) - .AllowAnyMethod() - .AllowAnyHeader() - .AllowCredentials()); -}); -builder.Services.AddSingleton(); builder.Services.AddTransient(); builder.Services.AddTransient(); -builder.Services.AddOptions(); -builder.Configuration.SetBasePath(Directory.GetCurrentDirectory()); -builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true); -builder.Configuration.AddEnvironmentVariables(); -builder.WebHost.UseKestrel(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; - }); - -}); -builder.WebHost.CaptureStartupErrors(false); -builder.Host.UseSerilog(CreateSerilogLogger(builder.Configuration)); -builder.WebHost.UseFailing(options => -{ - options.ConfigPath = "/Failing"; - options.NotFilteredPaths.AddRange(new[] { "/hc", "/liveness" }); -}); 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(setup => - { - setup.SwaggerEndpoint($"{(!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty)}/swagger/v1/swagger.json", "Basket.API V1"); - setup.OAuthClientId("basketswaggerui"); - setup.OAuthAppName("Basket Swagger UI"); - }); - -app.UseRouting(); -app.UseCors("CorsPolicy"); -app.UseAuthentication(); -app.UseAuthorization(); -app.UseStaticFiles(); - +app.UseServiceDefaults(); 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); - - - Log.Information("Starting web host ({ApplicationContext})...", Program.AppName); - await app.RunAsync(); - - return 0; -} -catch (Exception ex) -{ - Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", Program.AppName); - return 1; -} -finally -{ - Log.CloseAndFlush(); -} - -Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) -{ - var seqServerUrl = configuration["Serilog:SeqServerUrl"]; - var logstashUrl = configuration["Serilog:LogstashgUrl"]; - return new LoggerConfiguration() - .MinimumLevel.Verbose() - .Enrich.WithProperty("ApplicationContext", Program.AppName) - .Enrich.FromLogContext() - .WriteTo.Console() - .WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) - .WriteTo.Http(string.IsNullOrWhiteSpace(logstashUrl) ? "http://logstash:8080" : logstashUrl, null) - .ReadFrom.Configuration(configuration) - .CreateLogger(); -} - -(int httpPort, int grpcPort) GetDefinedPorts(IConfiguration config) -{ - var grpcPort = config.GetValue("GRPC_PORT", 5001); - var port = config.GetValue("PORT", 80); - return (port, grpcPort); -} -void ConfigureEventBus(IApplicationBuilder app) -{ - var eventBus = app.ApplicationServices.GetRequiredService(); - - eventBus.Subscribe(); - eventBus.Subscribe(); -} -public partial class Program -{ - - public static string Namespace = typeof(Program).Assembly.GetName().Name; - public static string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1); -} - - -public static class CustomExtensionMethods -{ - - - public static IServiceCollection RegisterEventBus(this IServiceCollection services, IConfiguration configuration) - { - if (configuration.GetValue("AzureServiceBusEnabled")) - { - services.AddSingleton(sp => - { - var serviceBusPersisterConnection = sp.GetRequiredService(); - var logger = sp.GetRequiredService>(); - var eventBusSubscriptionsManager = sp.GetRequiredService(); - string subscriptionName = configuration["SubscriptionClientName"]; - - return new EventBusServiceBus(serviceBusPersisterConnection, logger, - eventBusSubscriptionsManager, sp, subscriptionName); - }); - } - else - { - services.AddSingleton(sp => - { - var subscriptionClientName = configuration["SubscriptionClientName"]; - var rabbitMQPersistentConnection = sp.GetRequiredService(); - var logger = sp.GetRequiredService>(); - var eventBusSubscriptionsManager = sp.GetRequiredService(); - - var retryCount = 5; - if (!string.IsNullOrEmpty(configuration["EventBusRetryCount"])) - { - retryCount = int.Parse(configuration["EventBusRetryCount"]); - } - return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, sp, eventBusSubscriptionsManager, subscriptionClientName, retryCount); - }); - } +var eventBus = app.Services.GetRequiredService(); - services.AddSingleton(); +eventBus.Subscribe(); +eventBus.Subscribe(); - services.AddTransient(); - services.AddTransient(); - return services; - } -} +await app.RunAsync(); diff --git a/src/Services/Basket/Basket.API/Properties/launchSettings.json b/src/Services/Basket/Basket.API/Properties/launchSettings.json index 60a56b153..d54621f97 100644 --- a/src/Services/Basket/Basket.API/Properties/launchSettings.json +++ b/src/Services/Basket/Basket.API/Properties/launchSettings.json @@ -1,26 +1,11 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:58017/", - "sslPort": 0 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Microsoft.eShopOnContainers.Services.Basket.API": { + "Basket.API": { "commandName": "Project", "launchBrowser": true, - "launchUrl": "http://localhost:55103/", + "applicationUrl": "http://localhost:5221", "environmentVariables": { + "Identity__Url": "http://localhost:5223", "ASPNETCORE_ENVIRONMENT": "Development" } } diff --git a/src/Services/Basket/Basket.API/Properties/serviceDependencies.json b/src/Services/Basket/Basket.API/Properties/serviceDependencies.json new file mode 100644 index 000000000..adb08c8f2 --- /dev/null +++ b/src/Services/Basket/Basket.API/Properties/serviceDependencies.json @@ -0,0 +1,17 @@ +{ + "dependencies": { + "secrets1": { + "type": "secrets" + }, + "rabbitmq1": { + "type": "rabbitmq", + "connectionId": "eventbus", + "dynamicId": null + }, + "redis1": { + "type": "redis", + "connectionId": "ConnectionStrings:Redis", + "dynamicId": null + } + } +} \ No newline at end of file diff --git a/src/Services/Basket/Basket.API/Properties/serviceDependencies.local.json b/src/Services/Basket/Basket.API/Properties/serviceDependencies.local.json new file mode 100644 index 000000000..8fb5df48a --- /dev/null +++ b/src/Services/Basket/Basket.API/Properties/serviceDependencies.local.json @@ -0,0 +1,26 @@ +{ + "dependencies": { + "secrets1": { + "type": "secrets.user" + }, + "rabbitmq1": { + "containerPorts": "5672:5672,15672:15672", + "secretStore": "LocalSecretsFile", + "containerName": "rabbitmq", + "containerImage": "rabbitmq:3-management-alpine", + "type": "rabbitmq.container", + "connectionId": "eventbus", + "dynamicId": null + }, + "redis1": { + "serviceConnectorResourceId": "", + "containerPorts": "6379:6379", + "secretStore": "LocalSecretsFile", + "containerName": "basket-redis", + "containerImage": "redis:alpine", + "type": "redis.container", + "connectionId": "ConnectionStrings:Redis", + "dynamicId": null + } + } +} \ No newline at end of file diff --git a/src/Services/Basket/Basket.API/Infrastructure/Repositories/RedisBasketRepository.cs b/src/Services/Basket/Basket.API/Repositories/RedisBasketRepository.cs similarity index 86% rename from src/Services/Basket/Basket.API/Infrastructure/Repositories/RedisBasketRepository.cs rename to src/Services/Basket/Basket.API/Repositories/RedisBasketRepository.cs index 7db463f20..bca10ca33 100644 --- a/src/Services/Basket/Basket.API/Infrastructure/Repositories/RedisBasketRepository.cs +++ b/src/Services/Basket/Basket.API/Repositories/RedisBasketRepository.cs @@ -1,4 +1,4 @@ -namespace Microsoft.eShopOnContainers.Services.Basket.API.Infrastructure.Repositories; +namespace Basket.API.Repositories; public class RedisBasketRepository : IBasketRepository { @@ -6,9 +6,9 @@ public class RedisBasketRepository : IBasketRepository private readonly ConnectionMultiplexer _redis; private readonly IDatabase _database; - public RedisBasketRepository(ILoggerFactory loggerFactory, ConnectionMultiplexer redis) + public RedisBasketRepository(ILogger logger, ConnectionMultiplexer redis) { - _logger = loggerFactory.CreateLogger(); + _logger = logger; _redis = redis; _database = redis.GetDatabase(); } diff --git a/src/Services/Basket/Basket.API/TestHttpResponseTrailersFeature.cs b/src/Services/Basket/Basket.API/TestHttpResponseTrailersFeature.cs deleted file mode 100644 index b1cfef87b..000000000 --- a/src/Services/Basket/Basket.API/TestHttpResponseTrailersFeature.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Basket.API; - -internal class TestHttpResponseTrailersFeature : IHttpResponseTrailersFeature -{ - public IHeaderDictionary Trailers { get; set; } -} diff --git a/src/Services/Basket/Basket.API/appsettings.Development.json b/src/Services/Basket/Basket.API/appsettings.Development.json index f4a3b9407..4088044ae 100644 --- a/src/Services/Basket/Basket.API/appsettings.Development.json +++ b/src/Services/Basket/Basket.API/appsettings.Development.json @@ -1,16 +1,4 @@ { - "Serilog": { - "MinimumLevel": { - "Default": "Debug", - "Override": { - "Microsoft": "Warning", - "Microsoft.eShopOnContainers": "Debug", - "System": "Warning" - } - } - }, - "IdentityUrlExternal": "http://localhost:5105", - "IdentityUrl": "http://localhost:5105", "ConnectionString": "127.0.0.1", "AzureServiceBusEnabled": false, "EventBusConnection": "localhost" diff --git a/src/Services/Basket/Basket.API/appsettings.json b/src/Services/Basket/Basket.API/appsettings.json index 295294308..3a4547555 100644 --- a/src/Services/Basket/Basket.API/appsettings.json +++ b/src/Services/Basket/Basket.API/appsettings.json @@ -1,30 +1,48 @@ { - "Serilog": { - "SeqServerUrl": null, - "LogstashgUrl": null, - "MinimumLevel": { + "Logging": { + "LogLevel": { "Default": "Information", - "Override": { - "Microsoft": "Warning", - "Microsoft.eShopOnContainers": "Information", - "System": "Warning" - } + "Microsoft.AspNetCore": "Warning" } }, "Kestrel": { - "EndpointDefaults": { - "Protocols": "Http2" + "Endpoints": { + "Http": { + "Url": "http://localhost:5221" + }, + "gRPC": { + "Url": "http://localhost:6221", + "Protocols": "Http2" + } + } + }, + "OpenApi": { + "Endpoint": { + "Name": "Basket.API V1" + }, + "Document": { + "Description": "The Basket Service HTTP API", + "Title": "eShopOnContainers - Basket HTTP API", + "Version": "v1" + }, + "Auth": { + "ClientId": "basketswaggerui", + "AppName": "Basket Swagger UI" } }, - "SubscriptionClientName": "Basket", - "ApplicationInsights": { - "InstrumentationKey": "" + "ConnectionStrings": { + "Redis": "localhost", + "EventBus": "localhost" + }, + "Identity": { + "Audience": "basket", + "Url": "http://localhost:5223", + "Scopes": { + "basket": "Basket API" + } }, - "EventBusRetryCount": 5, - "UseVault": false, - "Vault": { - "Name": "eshop", - "ClientId": "your-client-id", - "ClientSecret": "your-client-secret" + "EventBus": { + "SubscriptionClientName": "Basket", + "RetryCount": 5 } } diff --git a/src/Services/Basket/Basket.API/web.config b/src/Services/Basket/Basket.API/web.config deleted file mode 100644 index a2cf1fe26..000000000 --- a/src/Services/Basket/Basket.API/web.config +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Services/Basket/Basket.FunctionalTests/Base/AutoAuthorizeMiddleware.cs b/src/Services/Basket/Basket.FunctionalTests/Base/AutoAuthorizeMiddleware.cs index 6343dbe68..551e3069e 100644 --- a/src/Services/Basket/Basket.FunctionalTests/Base/AutoAuthorizeMiddleware.cs +++ b/src/Services/Basket/Basket.FunctionalTests/Base/AutoAuthorizeMiddleware.cs @@ -18,6 +18,7 @@ class AutoAuthorizeMiddleware identity.AddClaim(new Claim("sub", IDENTITY_ID)); identity.AddClaim(new Claim("unique_name", IDENTITY_ID)); identity.AddClaim(new Claim(ClaimTypes.Name, IDENTITY_ID)); + identity.AddClaim(new Claim("scope", "basket")); httpContext.User.AddIdentity(identity); diff --git a/src/Services/Basket/Basket.FunctionalTests/Base/BasketScenarioBase.cs b/src/Services/Basket/Basket.FunctionalTests/Base/BasketScenarioBase.cs index 5fe08bb58..8fe4f2e60 100644 --- a/src/Services/Basket/Basket.FunctionalTests/Base/BasketScenarioBase.cs +++ b/src/Services/Basket/Basket.FunctionalTests/Base/BasketScenarioBase.cs @@ -1,4 +1,7 @@ -namespace Basket.FunctionalTests.Base; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.Hosting; + +namespace Basket.FunctionalTests.Base; public class BasketScenarioBase { @@ -6,18 +9,8 @@ public class BasketScenarioBase public TestServer CreateServer() { - var path = Assembly.GetAssembly(typeof(BasketScenarioBase)) - .Location; - - var hostBuilder = new WebHostBuilder() - .UseContentRoot(Path.GetDirectoryName(path)) - .ConfigureAppConfiguration(cb => - { - cb.AddJsonFile("appsettings.json", optional: false) - .AddEnvironmentVariables(); - }); - - return new TestServer(hostBuilder); + var factory = new BasketApplication(); + return factory.Server; } public static class Get @@ -26,6 +19,11 @@ public class BasketScenarioBase { return $"{ApiUrlBase}/{id}"; } + + public static string GetBasketByCustomer(string customerId) + { + return $"{ApiUrlBase}/{customerId}"; + } } public static class Post @@ -33,4 +31,37 @@ public class BasketScenarioBase public static string Basket = $"{ApiUrlBase}/"; public static string CheckoutOrder = $"{ApiUrlBase}/checkout"; } + + private class BasketApplication : WebApplicationFactory + { + protected override IHost CreateHost(IHostBuilder builder) + { + builder.ConfigureServices(services => + { + services.AddSingleton(); + }); + + builder.ConfigureAppConfiguration(c => + { + var directory = Path.GetDirectoryName(typeof(BasketScenarioBase).Assembly.Location)!; + + c.AddJsonFile(Path.Combine(directory, "appsettings.Basket.json"), optional: false); + }); + + return base.CreateHost(builder); + } + + private class AuthStartupFilter : IStartupFilter + { + public Action Configure(Action next) + { + return app => + { + app.UseMiddleware(); + + next(app); + }; + } + } + } } diff --git a/src/Services/Basket/Basket.FunctionalTests/Base/HttpClientExtensions.cs b/src/Services/Basket/Basket.FunctionalTests/Base/HttpClientExtensions.cs deleted file mode 100644 index 45910df14..000000000 --- a/src/Services/Basket/Basket.FunctionalTests/Base/HttpClientExtensions.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Basket.FunctionalTests.Base; - -static class HttpClientExtensions -{ - public static HttpClient CreateIdempotentClient(this TestServer server) - { - var client = server.CreateClient(); - - client.DefaultRequestHeaders.Add("x-requestid", Guid.NewGuid().ToString()); - - return client; - } -} diff --git a/src/Services/Basket/Basket.FunctionalTests/Basket.FunctionalTests.csproj b/src/Services/Basket/Basket.FunctionalTests/Basket.FunctionalTests.csproj index f7ccc5df6..717520e67 100644 --- a/src/Services/Basket/Basket.FunctionalTests/Basket.FunctionalTests.csproj +++ b/src/Services/Basket/Basket.FunctionalTests/Basket.FunctionalTests.csproj @@ -7,11 +7,7 @@ - - - - - + PreserveNewest diff --git a/src/Services/Basket/Basket.FunctionalTests/BasketScenarios.cs b/src/Services/Basket/Basket.FunctionalTests/BasketScenarios.cs index f727b999e..4a96207a4 100644 --- a/src/Services/Basket/Basket.FunctionalTests/BasketScenarios.cs +++ b/src/Services/Basket/Basket.FunctionalTests/BasketScenarios.cs @@ -1,16 +1,15 @@ namespace Basket.FunctionalTests; -public class BasketScenarios - : BasketScenarioBase +public class BasketScenarios : + BasketScenarioBase { [Fact] public async Task Post_basket_and_response_ok_status_code() { using var server = CreateServer(); var content = new StringContent(BuildBasket(), UTF8Encoding.UTF8, "application/json"); - var response = await server.CreateClient() - .PostAsync(Post.Basket, content); - + var uri = "/api/v1/basket/"; + var response = await server.CreateClient().PostAsync(uri, content); response.EnsureSuccessStatusCode(); } @@ -20,7 +19,6 @@ public class BasketScenarios using var server = CreateServer(); var response = await server.CreateClient() .GetAsync(Get.GetBasket(1)); - response.EnsureSuccessStatusCode(); } @@ -33,9 +31,12 @@ public class BasketScenarios await server.CreateClient() .PostAsync(Post.Basket, contentBasket); - var contentCheckout = new StringContent(BuildCheckout(), UTF8Encoding.UTF8, "application/json"); + var contentCheckout = new StringContent(BuildCheckout(), UTF8Encoding.UTF8, "application/json") + { + Headers = { { "x-requestid", Guid.NewGuid().ToString() } } + }; - var response = await server.CreateIdempotentClient() + var response = await server.CreateClient() .PostAsync(Post.CheckoutOrder, contentCheckout); response.EnsureSuccessStatusCode(); diff --git a/src/Services/Basket/Basket.FunctionalTests/GlobalUsings.cs b/src/Services/Basket/Basket.FunctionalTests/GlobalUsings.cs index d3657c2d3..219e4ce57 100644 --- a/src/Services/Basket/Basket.FunctionalTests/GlobalUsings.cs +++ b/src/Services/Basket/Basket.FunctionalTests/GlobalUsings.cs @@ -1,23 +1,19 @@ -global using Basket.FunctionalTests.Base; +global using System; +global using System.Collections.Generic; +global using System.IO; +global using System.Net.Http; +global using System.Security.Claims; +global using System.Text; +global using System.Text.Json; +global using System.Threading.Tasks; +global using Basket.FunctionalTests.Base; global using Microsoft.AspNetCore.Builder; global using Microsoft.AspNetCore.Hosting; global using Microsoft.AspNetCore.Http; -global using Microsoft.AspNetCore.Routing; global using Microsoft.AspNetCore.TestHost; -global using Microsoft.eShopOnContainers.Services.Basket.API.Infrastructure.Repositories; global using Microsoft.eShopOnContainers.Services.Basket.API.Model; -global using Microsoft.eShopOnContainers.Services.Basket.API; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.Logging; global using StackExchange.Redis; -global using System.Collections.Generic; -global using System.IO; -global using System.Net.Http; -global using System.Reflection; -global using System.Security.Claims; -global using System.Text.Json; -global using System.Text; -global using System.Threading.Tasks; -global using System; global using Xunit; diff --git a/src/Services/Basket/Basket.FunctionalTests/RedisBasketRepositoryTests.cs b/src/Services/Basket/Basket.FunctionalTests/RedisBasketRepositoryTests.cs index 0a0cb11fa..639f36bb8 100644 --- a/src/Services/Basket/Basket.FunctionalTests/RedisBasketRepositoryTests.cs +++ b/src/Services/Basket/Basket.FunctionalTests/RedisBasketRepositoryTests.cs @@ -1,4 +1,4 @@ - +using Basket.API.Repositories; namespace Basket.FunctionalTests { @@ -9,8 +9,8 @@ namespace Basket.FunctionalTests [Fact] public async Task UpdateBasket_return_and_add_basket() { - using var server = CreateServer(); - var redis = server.Host.Services.GetRequiredService(); + var server = CreateServer(); + var redis = server.Services.GetRequiredService(); var redisBasketRepository = BuildBasketRepository(redis); @@ -22,16 +22,13 @@ namespace Basket.FunctionalTests Assert.NotNull(basket); Assert.Single(basket.Items); - - } [Fact] public async Task Delete_Basket_return_null() { - - using var server = CreateServer(); - var redis = server.Host.Services.GetRequiredService(); + var server = CreateServer(); + var redis = server.Services.GetRequiredService(); var redisBasketRepository = BuildBasketRepository(redis); @@ -52,7 +49,7 @@ namespace Basket.FunctionalTests RedisBasketRepository BuildBasketRepository(ConnectionMultiplexer connMux) { var loggerFactory = new LoggerFactory(); - return new RedisBasketRepository(loggerFactory, connMux); + return new RedisBasketRepository(loggerFactory.CreateLogger(), connMux); } List BuildBasketItems() diff --git a/src/Services/Basket/Basket.FunctionalTests/appsettings.Basket.json b/src/Services/Basket/Basket.FunctionalTests/appsettings.Basket.json new file mode 100644 index 000000000..364a58def --- /dev/null +++ b/src/Services/Basket/Basket.FunctionalTests/appsettings.Basket.json @@ -0,0 +1,25 @@ +{ + "Logging": { + "Console": { + "IncludeScopes": false + }, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + }, + "Identity": { + "ExternalUrl": "http://localhost:5105", + "Url": "http://localhost:5105" + }, + "ConnectionStrings": { + "Redis": "127.0.0.1" + }, + "EventBus": { + "ConnectionString": "localhost", + "SubscriptionClientName": "Basket" + }, + "isTest": "true", + "SuppressCheckForUnhandledSecurityMetadata": true +} diff --git a/src/Services/Basket/Basket.FunctionalTests/appsettings.json b/src/Services/Basket/Basket.FunctionalTests/appsettings.json deleted file mode 100644 index 8b9ec4d3c..000000000 --- a/src/Services/Basket/Basket.FunctionalTests/appsettings.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "Logging": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Debug", - "System": "Information", - "Microsoft": "Information" - } - }, - "IdentityUrl": "http://localhost:5105", - "IdentityUrlExternal": "http://localhost:5105", - "ConnectionString": "127.0.0.1", - "isTest": "true", - "EventBusConnection": "localhost", - "SubscriptionClientName": "Basket", - "SuppressCheckForUnhandledSecurityMetadata": true -} diff --git a/src/Services/Basket/Basket.UnitTests/Application/CartControllerTest.cs b/src/Services/Basket/Basket.UnitTests/Application/CartControllerTest.cs index 4231d6a9e..44d565f39 100644 --- a/src/Services/Basket/Basket.UnitTests/Application/CartControllerTest.cs +++ b/src/Services/Basket/Basket.UnitTests/Application/CartControllerTest.cs @@ -79,7 +79,7 @@ public class CartControllerTest //Arrange var fakeCatalogItem = GetFakeCatalogItem(); - _basketServiceMock.Setup(x => x.AddItemToBasket(It.IsAny(), It.IsAny())) + _basketServiceMock.Setup(x => x.AddItemToBasket(It.IsAny(), It.IsAny())) .Returns(Task.FromResult(1)); //Act diff --git a/src/Services/Catalog/Catalog.API/Apis/PicApi.cs b/src/Services/Catalog/Catalog.API/Apis/PicApi.cs new file mode 100644 index 000000000..277b718a7 --- /dev/null +++ b/src/Services/Catalog/Catalog.API/Apis/PicApi.cs @@ -0,0 +1,42 @@ +using Microsoft.AspNetCore.Routing; + +namespace Catalog.API.Apis; + +public static class PicApi +{ + public static IEndpointConventionBuilder MapPicApi(this IEndpointRouteBuilder routes) + { + return routes.MapGet("api/v1/catalog/items/{catalogItemId:int}/pic", + async (int catalogItemId, CatalogContext db, IWebHostEnvironment environment) => + { + var item = await db.CatalogItems.FindAsync(catalogItemId); + + if (item is null) + { + return Results.NotFound(); + } + + var path = Path.Combine(environment.ContentRootPath, "Pics", item.PictureFileName); + + string imageFileExtension = Path.GetExtension(item.PictureFileName); + string mimetype = GetImageMimeTypeFromImageFileExtension(imageFileExtension); + + return Results.File(path, mimetype); + }) + .WithTags("Pic") + .Produces(404); + + static string GetImageMimeTypeFromImageFileExtension(string extension) => extension switch + { + ".png" => "image/png", + ".gif" => "image/gif", + ".jpg" or ".jpeg" => "image/jpeg", + ".bmp" => "image/bmp", + ".tiff" => "image/tiff", + ".wmf" => "image/wmf", + ".jp2" => "image/jp2", + ".svg" => "image/svg+xml", + _ => "application/octet-stream", + }; + } +} diff --git a/src/Services/Catalog/Catalog.API/Catalog.API.csproj b/src/Services/Catalog/Catalog.API/Catalog.API.csproj index 60d6addfd..8a4669c16 100644 --- a/src/Services/Catalog/Catalog.API/Catalog.API.csproj +++ b/src/Services/Catalog/Catalog.API/Catalog.API.csproj @@ -2,22 +2,15 @@ net7.0 - portable - true Catalog.API Catalog.API aspnet-Catalog.API-20161122013618 ..\..\..\..\docker-compose.dcproj - false - true - Always - - - PreserveNewest + PreserveNewest PreserveNewest @@ -25,49 +18,23 @@ PreserveNewest - - - - - PreserveNewest - + + + + + - - - + - - - - - - - - - - - - - - - + - - - - - - - - - @@ -77,19 +44,7 @@ - - - + - - - - PreserveNewest - - - PreserveNewest - - - diff --git a/src/Services/Catalog/Catalog.API/Controllers/HomeController.cs b/src/Services/Catalog/Catalog.API/Controllers/HomeController.cs deleted file mode 100644 index cd86a2966..000000000 --- a/src/Services/Catalog/Catalog.API/Controllers/HomeController.cs +++ /dev/null @@ -1,11 +0,0 @@ -// For more information on enabling MVC for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860 -namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers; - -public class HomeController : Controller -{ - // GET: // - public IActionResult Index() - { - return new RedirectResult("~/swagger"); - } -} diff --git a/src/Services/Catalog/Catalog.API/Controllers/PicController.cs b/src/Services/Catalog/Catalog.API/Controllers/PicController.cs deleted file mode 100644 index dfb6f0d03..000000000 --- a/src/Services/Catalog/Catalog.API/Controllers/PicController.cs +++ /dev/null @@ -1,64 +0,0 @@ -// For more information on enabling MVC for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860 -namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers; - -[ApiController] -public class PicController : ControllerBase -{ - private readonly IWebHostEnvironment _env; - private readonly CatalogContext _catalogContext; - - public PicController(IWebHostEnvironment env, - CatalogContext catalogContext) - { - _env = env; - _catalogContext = catalogContext; - } - - [HttpGet] - [Route("api/v1/catalog/items/{catalogItemId:int}/pic")] - [ProducesResponseType((int)HttpStatusCode.NotFound)] - [ProducesResponseType((int)HttpStatusCode.BadRequest)] - // GET: // - public async Task GetImageAsync(int catalogItemId) - { - if (catalogItemId <= 0) - { - return BadRequest(); - } - - var item = await _catalogContext.CatalogItems - .SingleOrDefaultAsync(ci => ci.Id == catalogItemId); - - if (item != null) - { - var webRoot = _env.WebRootPath; - var path = Path.Combine(webRoot, item.PictureFileName); - - string imageFileExtension = Path.GetExtension(item.PictureFileName); - string mimetype = GetImageMimeTypeFromImageFileExtension(imageFileExtension); - - var buffer = await System.IO.File.ReadAllBytesAsync(path); - - return File(buffer, mimetype); - } - - return NotFound(); - } - - private string GetImageMimeTypeFromImageFileExtension(string extension) - { - string mimetype = extension switch - { - ".png" => "image/png", - ".gif" => "image/gif", - ".jpg" or ".jpeg" => "image/jpeg", - ".bmp" => "image/bmp", - ".tiff" => "image/tiff", - ".wmf" => "image/wmf", - ".jp2" => "image/jp2", - ".svg" => "image/svg+xml", - _ => "application/octet-stream", - }; - return mimetype; - } -} diff --git a/src/Services/Catalog/Catalog.API/Dockerfile b/src/Services/Catalog/Catalog.API/Dockerfile index 2752c5c66..9138482c1 100644 --- a/src/Services/Catalog/Catalog.API/Dockerfile +++ b/src/Services/Catalog/Catalog.API/Dockerfile @@ -33,6 +33,7 @@ COPY "Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj" COPY "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" COPY "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" COPY "Services/Payment/Payment.API/Payment.API.csproj" "Services/Payment/Payment.API/Payment.API.csproj" +COPY "Services/Services.Common/Services.Common.csproj" "Services/Services.Common/Services.Common.csproj" COPY "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" COPY "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" COPY "Web/WebhookClient/WebhookClient.csproj" "Web/WebhookClient/WebhookClient.csproj" diff --git a/src/Services/Catalog/Catalog.API/Extensions/Extensions.cs b/src/Services/Catalog/Catalog.API/Extensions/Extensions.cs new file mode 100644 index 000000000..490c5ede4 --- /dev/null +++ b/src/Services/Catalog/Catalog.API/Extensions/Extensions.cs @@ -0,0 +1,92 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; + +public static class Extensions +{ + public static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration) + { + var hcBuilder = services.AddHealthChecks(); + + hcBuilder + .AddSqlServer(_ => configuration.GetRequiredConnectionString("CatalogDB"), + name: "CatalogDB-check", + tags: new string[] { "ready" }); + + var accountName = configuration["AzureStorageAccountName"]; + var accountKey = configuration["AzureStorageAccountKey"]; + + if (!string.IsNullOrEmpty(accountName) && !string.IsNullOrEmpty(accountKey)) + { + hcBuilder + .AddAzureBlobStorage( + $"DefaultEndpointsProtocol=https;AccountName={accountName};AccountKey={accountKey};EndpointSuffix=core.windows.net", + name: "catalog-storage-check", + tags: new string[] { "ready" }); + } + + return services; + } + + public static IServiceCollection AddDbContexts(this IServiceCollection services, IConfiguration configuration) + { + static void ConfigureSqlOptions(SqlServerDbContextOptionsBuilder sqlOptions) + { + sqlOptions.MigrationsAssembly(typeof(Program).Assembly.FullName); + + // 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.AddDbContext(options => + { + var connectionString = configuration.GetRequiredConnectionString("CatalogDB"); + + options.UseSqlServer(connectionString, ConfigureSqlOptions); + }); + + services.AddDbContext(options => + { + var connectionString = configuration.GetRequiredConnectionString("CatalogDB"); + + options.UseSqlServer(connectionString, ConfigureSqlOptions); + }); + + return services; + } + + public static IServiceCollection AddApplicationOptions(this IServiceCollection services, IConfiguration configuration) + { + services.Configure(configuration); + + // TODO: Move to the new problem details middleware + 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 AddIntegrationServices(this IServiceCollection services) + { + services.AddTransient>( + sp => (DbConnection c) => new IntegrationEventLogService(c)); + + services.AddTransient(); + + return services; + } +} diff --git a/src/Services/Catalog/Catalog.API/Extensions/LinqSelectExtensions.cs b/src/Services/Catalog/Catalog.API/Extensions/LinqSelectExtensions.cs index 85fa9300c..65d72c1d2 100644 --- a/src/Services/Catalog/Catalog.API/Extensions/LinqSelectExtensions.cs +++ b/src/Services/Catalog/Catalog.API/Extensions/LinqSelectExtensions.cs @@ -13,7 +13,7 @@ public static class LinqSelectExtensions } catch (Exception ex) { - returnedValue = new SelectTryResult(element, default(TResult), ex); + returnedValue = new SelectTryResult(element, default, ex); } yield return returnedValue; } diff --git a/src/Services/Catalog/Catalog.API/Extensions/WebHostExtensions.cs b/src/Services/Catalog/Catalog.API/Extensions/WebHostExtensions.cs deleted file mode 100644 index 588ca7a35..000000000 --- a/src/Services/Catalog/Catalog.API/Extensions/WebHostExtensions.cs +++ /dev/null @@ -1,68 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Catalog.API.Extensions; - -public static class WebHostExtensions -{ - public static bool IsInKubernetes(this IWebHost host) - { - var cfg = host.Services.GetService(); - var orchestratorType = cfg.GetValue("OrchestratorType"); - return orchestratorType?.ToUpper() == "K8S"; - } - - public static IWebHost MigrateDbContext(this IWebHost host, Action seeder) where TContext : DbContext - { - var underK8s = host.IsInKubernetes(); - - using var scope = host.Services.CreateScope(); - var services = scope.ServiceProvider; - - var logger = services.GetRequiredService>(); - - var context = services.GetService(); - - try - { - logger.LogInformation("Migrating database associated with context {DbContextName}", typeof(TContext).Name); - - if (underK8s) - { - InvokeSeeder(seeder, context, services); - } - else - { - var retry = Policy.Handle() - .WaitAndRetry(new TimeSpan[] - { - TimeSpan.FromSeconds(3), - TimeSpan.FromSeconds(5), - TimeSpan.FromSeconds(8), - }); - - //if the sql server container is not created on run docker compose this - //migration can't fail for network related exception. The retry options for DbContext only - //apply to transient exceptions - // Note that this is NOT applied when running some orchestrators (let the orchestrator to recreate the failing service) - retry.Execute(() => InvokeSeeder(seeder, context, services)); - } - - logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name); - } - catch (Exception ex) - { - logger.LogError(ex, "An error occurred while migrating the database used on context {DbContextName}", typeof(TContext).Name); - if (underK8s) - { - throw; // Rethrow under k8s because we rely on k8s to re-run the pod - } - } - - return host; - } - - private static void InvokeSeeder(Action seeder, TContext context, IServiceProvider services) - where TContext : DbContext - { - context.Database.Migrate(); - seeder(context, services); - } -} diff --git a/src/Services/Catalog/Catalog.API/GlobalUsings.cs b/src/Services/Catalog/Catalog.API/GlobalUsings.cs index 277288adc..e521114b2 100644 --- a/src/Services/Catalog/Catalog.API/GlobalUsings.cs +++ b/src/Services/Catalog/Catalog.API/GlobalUsings.cs @@ -1,63 +1,43 @@ -global using Azure.Core; -global using Azure.Identity; -global using Autofac.Extensions.DependencyInjection; -global using Autofac; -global using Microsoft.eShopOnContainers.Services.Catalog.API.Extensions; -global using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.ActionResults; -global using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.Exceptions; -global using Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents; +global using System; +global using System.Collections.Generic; +global using System.Data.Common; +global using System.Data.SqlClient; +global using System.Globalization; +global using System.IO; +global using System.IO.Compression; +global using System.Linq; +global using System.Net; +global using System.Text.RegularExpressions; +global using System.Threading.Tasks; global using Grpc.Core; +global using Microsoft.AspNetCore.Builder; global using Microsoft.AspNetCore.Hosting; global using Microsoft.AspNetCore.Http; -global using Microsoft.AspNetCore.Builder; -global using Microsoft.AspNetCore.Mvc.Filters; global using Microsoft.AspNetCore.Mvc; -global using Microsoft.AspNetCore.Server.Kestrel.Core; -global using Microsoft.AspNetCore; -global using Microsoft.Extensions.Logging; +global using Microsoft.EntityFrameworkCore; global using Microsoft.EntityFrameworkCore.Design; global using Microsoft.EntityFrameworkCore.Metadata.Builders; -global using Microsoft.EntityFrameworkCore; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +global using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; global using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services; global using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Utilities; -global using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; +global using Microsoft.eShopOnContainers.Services.Catalog.API; +global using Microsoft.eShopOnContainers.Services.Catalog.API.Extensions; +global using Microsoft.eShopOnContainers.Services.Catalog.API.Grpc; global using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; global using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.EntityConfigurations; -global using Microsoft.eShopOnContainers.Services.Catalog.API; +global using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.Exceptions; +global using Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents; +global using Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.EventHandling; global using Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Events; global using Microsoft.eShopOnContainers.Services.Catalog.API.Model; global using Microsoft.eShopOnContainers.Services.Catalog.API.ViewModel; -global using Microsoft.eShopOnContainers.Services.Catalog.API.Grpc; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.Hosting; +global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.Options; -global using Polly.Retry; global using Polly; -global using Serilog.Context; -global using Serilog; -global using System.Collections.Generic; -global using System.Data.Common; -global using System.Data.SqlClient; -global using System.Globalization; -global using System.IO.Compression; -global using System.IO; -global using System.Linq; -global using System.Net; -global using System.Text.RegularExpressions; -global using System.Threading.Tasks; -global using System; -global using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.Filters; -global using HealthChecks.UI.Client; -global using Microsoft.AspNetCore.Diagnostics.HealthChecks; -global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; -global using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; -global using Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus; -global using Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.EventHandling; -global using Microsoft.Extensions.Diagnostics.HealthChecks; -global using Microsoft.OpenApi.Models; -global using RabbitMQ.Client; -global using System.Reflection; -global using Microsoft.Extensions.FileProviders; \ No newline at end of file +global using Polly.Retry; +global using Services.Common; +global using Catalog.API.Apis; diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/ActionResults/InternalServerErrorObjectResult.cs b/src/Services/Catalog/Catalog.API/Infrastructure/ActionResults/InternalServerErrorObjectResult.cs deleted file mode 100644 index 53af0f0b8..000000000 --- a/src/Services/Catalog/Catalog.API/Infrastructure/ActionResults/InternalServerErrorObjectResult.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.ActionResults; - -public class InternalServerErrorObjectResult : ObjectResult -{ - public InternalServerErrorObjectResult(object error) - : base(error) - { - StatusCode = StatusCodes.Status500InternalServerError; - } -} diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContextSeed.cs b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContextSeed.cs index db154cb7f..472f392c9 100644 --- a/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContextSeed.cs +++ b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContextSeed.cs @@ -60,14 +60,14 @@ public class CatalogContextSeed } catch (Exception ex) { - logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); + logger.LogError(ex, "Error reading CSV headers"); return GetPreconfiguredCatalogBrands(); } return File.ReadAllLines(csvFileCatalogBrands) .Skip(1) // skip header row - .SelectTry(x => CreateCatalogBrand(x)) - .OnCaughtException(ex => { logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return null; }) + .SelectTry(CreateCatalogBrand) + .OnCaughtException(ex => { logger.LogError(ex, "Error creating brand while seeding database"); return null; }) .Where(x => x != null); } @@ -75,9 +75,9 @@ public class CatalogContextSeed { brand = brand.Trim('"').Trim(); - if (String.IsNullOrEmpty(brand)) + if (string.IsNullOrEmpty(brand)) { - throw new Exception("catalog Brand Name is empty"); + throw new Exception("Catalog Brand Name is empty"); } return new CatalogBrand @@ -115,14 +115,14 @@ public class CatalogContextSeed } catch (Exception ex) { - logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); + logger.LogError(ex, "Error reading CSV headers"); return GetPreconfiguredCatalogTypes(); } return File.ReadAllLines(csvFileCatalogTypes) .Skip(1) // skip header row .SelectTry(x => CreateCatalogType(x)) - .OnCaughtException(ex => { logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return null; }) + .OnCaughtException(ex => { logger.LogError(ex, "Error creating catalog type while seeding database"); return null; }) .Where(x => x != null); } @@ -130,7 +130,7 @@ public class CatalogContextSeed { type = type.Trim('"').Trim(); - if (String.IsNullOrEmpty(type)) + if (string.IsNullOrEmpty(type)) { throw new Exception("catalog Type Name is empty"); } @@ -170,7 +170,7 @@ public class CatalogContextSeed } catch (Exception ex) { - logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); + logger.LogError(ex, "Error reading CSV headers"); return GetPreconfiguredItems(); } @@ -181,11 +181,11 @@ public class CatalogContextSeed .Skip(1) // skip header row .Select(row => Regex.Split(row, ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)")) .SelectTry(column => CreateCatalogItem(column, csvheaders, catalogTypeIdLookup, catalogBrandIdLookup)) - .OnCaughtException(ex => { logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return null; }) + .OnCaughtException(ex => { logger.LogError(ex, "Error creating catalog item while seeding database"); return null; }) .Where(x => x != null); } - private CatalogItem CreateCatalogItem(string[] column, string[] headers, Dictionary catalogTypeIdLookup, Dictionary catalogBrandIdLookup) + private CatalogItem CreateCatalogItem(string[] column, string[] headers, Dictionary catalogTypeIdLookup, Dictionary catalogBrandIdLookup) { if (column.Count() != headers.Count()) { @@ -205,7 +205,7 @@ public class CatalogContextSeed } string priceString = column[Array.IndexOf(headers, "price")].Trim('"').Trim(); - if (!Decimal.TryParse(priceString, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out Decimal price)) + if (!decimal.TryParse(priceString, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out decimal price)) { throw new Exception($"price={priceString}is not a valid decimal number"); } @@ -224,7 +224,7 @@ public class CatalogContextSeed if (availableStockIndex != -1) { string availableStockString = column[availableStockIndex].Trim('"').Trim(); - if (!String.IsNullOrEmpty(availableStockString)) + if (!string.IsNullOrEmpty(availableStockString)) { if (int.TryParse(availableStockString, out int availableStock)) { @@ -241,7 +241,7 @@ public class CatalogContextSeed if (restockThresholdIndex != -1) { string restockThresholdString = column[restockThresholdIndex].Trim('"').Trim(); - if (!String.IsNullOrEmpty(restockThresholdString)) + if (!string.IsNullOrEmpty(restockThresholdString)) { if (int.TryParse(restockThresholdString, out int restockThreshold)) { @@ -258,7 +258,7 @@ public class CatalogContextSeed if (maxStockThresholdIndex != -1) { string maxStockThresholdString = column[maxStockThresholdIndex].Trim('"').Trim(); - if (!String.IsNullOrEmpty(maxStockThresholdString)) + if (!string.IsNullOrEmpty(maxStockThresholdString)) { if (int.TryParse(maxStockThresholdString, out int maxStockThreshold)) { @@ -275,7 +275,7 @@ public class CatalogContextSeed if (onReorderIndex != -1) { string onReorderString = column[onReorderIndex].Trim('"').Trim(); - if (!String.IsNullOrEmpty(onReorderString)) + if (!string.IsNullOrEmpty(onReorderString)) { if (bool.TryParse(onReorderString, out bool onReorder)) { @@ -361,7 +361,7 @@ public class CatalogContextSeed sleepDurationProvider: retry => TimeSpan.FromSeconds(5), onRetry: (exception, timeSpan, retry, ctx) => { - logger.LogWarning(exception, "[{prefix}] Exception {ExceptionType} with message {Message} detected on attempt {retry} of {retries}", prefix, exception.GetType().Name, exception.Message, retry, retries); + logger.LogWarning(exception, "[{prefix}] Error seeding database (attempt {retry} of {retries})", prefix, retry, retries); } ); } diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs b/src/Services/Catalog/Catalog.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs deleted file mode 100644 index 66ee35e7b..000000000 --- a/src/Services/Catalog/Catalog.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs +++ /dev/null @@ -1,58 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.Filters; - -public class HttpGlobalExceptionFilter : IExceptionFilter -{ - private readonly IWebHostEnvironment env; - private readonly ILogger logger; - - public HttpGlobalExceptionFilter(IWebHostEnvironment env, ILogger logger) - { - this.env = env; - this.logger = logger; - } - - public void OnException(ExceptionContext context) - { - logger.LogError(new EventId(context.Exception.HResult), - context.Exception, - context.Exception.Message); - - if (context.Exception.GetType() == typeof(CatalogDomainException)) - { - var problemDetails = new ValidationProblemDetails() - { - Instance = context.HttpContext.Request.Path, - Status = StatusCodes.Status400BadRequest, - Detail = "Please refer to the errors property for additional details." - }; - - problemDetails.Errors.Add("DomainValidations", new string[] { context.Exception.Message.ToString() }); - - context.Result = new BadRequestObjectResult(problemDetails); - context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; - } - else - { - var json = new JsonErrorResponse - { - Messages = new[] { "An error ocurred." } - }; - - if (env.IsDevelopment()) - { - json.DeveloperMessage = context.Exception; - } - - context.Result = new InternalServerErrorObjectResult(json); - context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; - } - context.ExceptionHandled = true; - } - - private class JsonErrorResponse - { - public string[] Messages { get; set; } - - public object DeveloperMessage { get; set; } - } -} diff --git a/src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs b/src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs index 282bea5b3..44c7b8c75 100644 --- a/src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs +++ b/src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs @@ -26,7 +26,7 @@ public class CatalogIntegrationEventService : ICatalogIntegrationEventService, I { try { - _logger.LogInformation("----- Publishing integration event: {IntegrationEventId_published} from {AppName} - ({@IntegrationEvent})", evt.Id, Program.AppName, evt); + _logger.LogInformation("Publishing integration event: {IntegrationEventId_published} - ({@IntegrationEvent})", evt.Id, evt); await _eventLogService.MarkEventAsInProgressAsync(evt.Id); _eventBus.Publish(evt); @@ -34,14 +34,14 @@ public class CatalogIntegrationEventService : ICatalogIntegrationEventService, I } catch (Exception ex) { - _logger.LogError(ex, "ERROR Publishing integration event: {IntegrationEventId} from {AppName} - ({@IntegrationEvent})", evt.Id, Program.AppName, evt); + _logger.LogError(ex, "Error Publishing integration event: {IntegrationEventId} - ({@IntegrationEvent})", evt.Id, evt); await _eventLogService.MarkEventAsFailedAsync(evt.Id); } } public async Task SaveEventAndCatalogContextChangesAsync(IntegrationEvent evt) { - _logger.LogInformation("----- CatalogIntegrationEventService - Saving changes and integrationEvent: {IntegrationEventId}", evt.Id); + _logger.LogInformation("CatalogIntegrationEventService - Saving changes and integrationEvent: {IntegrationEventId}", evt.Id); //Use of an EF Core resiliency strategy when using multiple DbContexts within an explicit BeginTransaction(): //See: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency diff --git a/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToAwaitingValidationIntegrationEventHandler.cs b/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToAwaitingValidationIntegrationEventHandler.cs index 01fb5dfa9..6ad0a3867 100644 --- a/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToAwaitingValidationIntegrationEventHandler.cs +++ b/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToAwaitingValidationIntegrationEventHandler.cs @@ -19,9 +19,9 @@ public class OrderStatusChangedToAwaitingValidationIntegrationEventHandler : public async Task Handle(OrderStatusChangedToAwaitingValidationIntegrationEvent @event) { - using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + using (_logger.BeginScope(new List> { new ("IntegrationEventContext", @event.Id) })) { - _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + _logger.LogInformation("Handling integration event: {IntegrationEventId} - ({@IntegrationEvent})", @event.Id, @event); var confirmedOrderStockItems = new List(); diff --git a/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToPaidIntegrationEventHandler.cs b/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToPaidIntegrationEventHandler.cs index 8882e78a6..48e7fe571 100644 --- a/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToPaidIntegrationEventHandler.cs +++ b/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToPaidIntegrationEventHandler.cs @@ -16,9 +16,9 @@ public class OrderStatusChangedToPaidIntegrationEventHandler : public async Task Handle(OrderStatusChangedToPaidIntegrationEvent @event) { - using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + using (_logger.BeginScope(new List> { new ("IntegrationEventContext", @event.Id) })) { - _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + _logger.LogInformation("Handling integration event: {IntegrationEventId} - ({@IntegrationEvent})", @event.Id, @event); //we're not blocking stock/inventory foreach (var orderStockItem in @event.OrderStockItems) diff --git a/src/Services/Catalog/Catalog.API/Program.cs b/src/Services/Catalog/Catalog.API/Program.cs index 9c919dbc1..695ea9197 100644 --- a/src/Services/Catalog/Catalog.API/Program.cs +++ b/src/Services/Catalog/Catalog.API/Program.cs @@ -1,388 +1,43 @@ -var builder = WebApplication.CreateBuilder(new WebApplicationOptions -{ - Args = args, - ApplicationName = typeof(Program).Assembly.FullName, - ContentRootPath = Directory.GetCurrentDirectory(), - WebRootPath = "Pics", -}); -if (builder.Configuration.GetValue("UseVault", false)) -{ - TokenCredential credential = new ClientSecretCredential( - builder.Configuration["Vault:TenantId"], - builder.Configuration["Vault:ClientId"], - builder.Configuration["Vault:ClientSecret"]); - //builder.AddAzureKeyVault(new Uri($"https://{builder.Configuration["Vault:Name"]}.vault.azure.net/"), credential); -} -builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true); -builder.WebHost.UseKestrel(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; - }); +var builder = WebApplication.CreateBuilder(args); -}); -builder.Services.AddAppInsight(builder.Configuration); -builder.Services.AddGrpc().Services - .AddCustomMVC(builder.Configuration) - .AddCustomDbContext(builder.Configuration) - .AddCustomOptions(builder.Configuration) - .AddCustomHealthCheck(builder.Configuration) - .AddIntegrationServices(builder.Configuration) - .AddEventBus(builder.Configuration) - .AddSwagger(builder.Configuration); +builder.AddServiceDefaults(); -var app = builder.Build(); +builder.Services.AddGrpc(); +builder.Services.AddControllers(); -if (app.Environment.IsDevelopment()) -{ - app.UseDeveloperExceptionPage(); -} -else -{ - app.UseExceptionHandler("/Home/Error"); -} +// Application specific services +builder.Services.AddHealthChecks(builder.Configuration); +builder.Services.AddDbContexts(builder.Configuration); +builder.Services.AddApplicationOptions(builder.Configuration); +builder.Services.AddIntegrationServices(); -var pathBase = app.Configuration["PATH_BASE"]; -if (!string.IsNullOrEmpty(pathBase)) -{ - app.UsePathBase(pathBase); -} +builder.Services.AddTransient(); +builder.Services.AddTransient(); -app.UseSwagger() - .UseSwaggerUI(c => - { - c.SwaggerEndpoint($"{(!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty)}/swagger/v1/swagger.json", "Catalog.API V1"); - }); +var app = builder.Build(); -app.UseRouting(); -app.UseCors("CorsPolicy"); -app.MapDefaultControllerRoute(); +app.UseServiceDefaults(); + +app.MapPicApi(); app.MapControllers(); -app.UseFileServer(new FileServerOptions -{ - FileProvider = new PhysicalFileProvider(Path.Combine(app.Environment.ContentRootPath, "Pics")), - RequestPath = "/pics" -}); -app.UseStaticFiles(new StaticFileOptions -{ - FileProvider = new PhysicalFileProvider(Path.Combine(app.Environment.ContentRootPath, "Pics")), - RequestPath = "/pics" -}); -app.MapGet("/_proto/", async ctx => -{ - ctx.Response.ContentType = "text/plain"; - using var fs = new FileStream(Path.Combine(app.Environment.ContentRootPath, "Proto", "catalog.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.MapGrpcService(); -app.MapHealthChecks("/hc", new HealthCheckOptions() -{ - Predicate = _ => true, - ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse -}); -app.MapHealthChecks("/liveness", new HealthCheckOptions -{ - Predicate = r => r.Name.Contains("self") -}); -ConfigureEventBus(app); +var eventBus = app.Services.GetRequiredService(); -try +eventBus.Subscribe(); +eventBus.Subscribe(); + +// REVIEW: This is done fore development east but shouldn't be here in production +using (var scope = app.Services.CreateScope()) { - Log.Information("Configuring web host ({ApplicationContext})...", Program.AppName); - 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(); - await new CatalogContextSeed().SeedAsync(context, env, settings, logger); + await new CatalogContextSeed().SeedAsync(context, app.Environment, settings, logger); var integEventContext = scope.ServiceProvider.GetRequiredService(); await integEventContext.Database.MigrateAsync(); - app.Logger.LogInformation("Starting web host ({ApplicationName})...", AppName); - await app.RunAsync(); - - return 0; } -catch (Exception ex) -{ - Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", Program.AppName); - return 1; -} -finally -{ - Log.CloseAndFlush(); -} -void ConfigureEventBus(IApplicationBuilder app) -{ - var eventBus = app.ApplicationServices.GetRequiredService(); - eventBus.Subscribe(); - eventBus.Subscribe(); -} - -(int httpPort, int grpcPort) GetDefinedPorts(IConfiguration config) -{ - var grpcPort = config.GetValue("GRPC_PORT", 81); - var port = config.GetValue("PORT", 80); - return (port, grpcPort); -} - -public partial class Program -{ - public static string Namespace = typeof(Program).Assembly.GetName().Name; - public static string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1); -} - -public static class CustomExtensionMethods -{ - public static IServiceCollection AddAppInsight(this IServiceCollection services, IConfiguration configuration) - { - services.AddApplicationInsightsTelemetry(configuration); - services.AddApplicationInsightsKubernetesEnricher(); - - return services; - } - public static IServiceCollection AddCustomMVC(this IServiceCollection services, IConfiguration configuration) - { - services.AddControllers(options => - { - options.Filters.Add(typeof(HttpGlobalExceptionFilter)); - }) - .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 AddCustomHealthCheck(this IServiceCollection services, IConfiguration configuration) - { - var accountName = configuration.GetValue("AzureStorageAccountName"); - var accountKey = configuration.GetValue("AzureStorageAccountKey"); - - var hcBuilder = services.AddHealthChecks(); - - hcBuilder - .AddCheck("self", () => HealthCheckResult.Healthy()) - .AddSqlServer( - configuration["ConnectionString"], - name: "CatalogDB-check", - tags: new string[] { "catalogdb" }); - - if (!string.IsNullOrEmpty(accountName) && !string.IsNullOrEmpty(accountKey)) - { - hcBuilder - .AddAzureBlobStorage( - $"DefaultEndpointsProtocol=https;AccountName={accountName};AccountKey={accountKey};EndpointSuffix=core.windows.net", - name: "catalog-storage-check", - tags: new string[] { "catalogstorage" }); - } - - if (configuration.GetValue("AzureServiceBusEnabled")) - { - hcBuilder - .AddAzureServiceBusTopic( - configuration["EventBusConnection"], - topicName: "eshop_event_bus", - name: "catalog-servicebus-check", - tags: new string[] { "servicebus" }); - } - else - { - hcBuilder - .AddRabbitMQ( - $"amqp://{configuration["EventBusConnection"]}", - name: "catalog-rabbitmqbus-check", - tags: new string[] { "rabbitmqbus" }); - } - - return services; - } - - public static IServiceCollection AddCustomDbContext(this IServiceCollection services, IConfiguration configuration) - { - services.AddEntityFrameworkSqlServer() - .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); - }); - }); - - 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 AddCustomOptions(this IServiceCollection services, IConfiguration configuration) - { - 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 AddSwagger(this IServiceCollection services, IConfiguration configuration) - { - services.AddSwaggerGen(options => - { - options.SwaggerDoc("v1", new OpenApiInfo - { - Title = "eShopOnContainers - Catalog HTTP API", - Version = "v1", - Description = "The Catalog Microservice HTTP API. This is a Data-Driven/CRUD microservice sample" - }); - }); - - return services; - - } - - public static IServiceCollection AddIntegrationServices(this IServiceCollection services, IConfiguration configuration) - { - services.AddTransient>( - sp => (DbConnection c) => new IntegrationEventLogService(c)); - - services.AddTransient(); - - if (configuration.GetValue("AzureServiceBusEnabled")) - { - services.AddSingleton(sp => - { - var settings = sp.GetRequiredService>().Value; - var serviceBusConnection = settings.EventBusConnection; - - return new DefaultServiceBusPersisterConnection(serviceBusConnection); - }); - } - else - { - services.AddSingleton(sp => - { - var settings = sp.GetRequiredService>().Value; - 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 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(); - services.AddTransient(); - services.AddTransient(); - - return services; - } -} +await app.RunAsync(); diff --git a/src/Services/Catalog/Catalog.API/Properties/launchSettings.json b/src/Services/Catalog/Catalog.API/Properties/launchSettings.json index 71aab05ab..1b56eefed 100644 --- a/src/Services/Catalog/Catalog.API/Properties/launchSettings.json +++ b/src/Services/Catalog/Catalog.API/Properties/launchSettings.json @@ -1,29 +1,9 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:57424/", - "sslPort": 0 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "/swagger", - "environmentVariables": { - "ConnectionString": "server=localhost,5433;Database=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word", - "Serilog:LogstashgUrl": "http://locahost:8080", - "ASPNETCORE_ENVIRONMENT": "Development", - "EventBusConnection": "localhost", - "Serilog:SeqServerUrl": "http://locahost:5340" - } - }, - "Microsoft.eShopOnContainers.Services.Catalog.API": { + "Catalog.API": { "commandName": "Project", "launchBrowser": true, - "launchUrl": "http://localhost:55101/", + "applicationUrl": "http://localhost:5222/", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/src/Services/Catalog/Catalog.API/Properties/serviceDependencies.json b/src/Services/Catalog/Catalog.API/Properties/serviceDependencies.json new file mode 100644 index 000000000..62b84ead2 --- /dev/null +++ b/src/Services/Catalog/Catalog.API/Properties/serviceDependencies.json @@ -0,0 +1,14 @@ +{ + "dependencies": { + "rabbitmq1": { + "type": "rabbitmq", + "connectionId": "ConnectionStrings:EventBus", + "dynamicId": null + }, + "mssql1": { + "type": "mssql", + "connectionId": "ConnectionStrings:CatalogDB", + "dynamicId": null + } + } +} \ No newline at end of file diff --git a/src/Services/Catalog/Catalog.API/Properties/serviceDependencies.local.json b/src/Services/Catalog/Catalog.API/Properties/serviceDependencies.local.json new file mode 100644 index 000000000..9dcbd89ce --- /dev/null +++ b/src/Services/Catalog/Catalog.API/Properties/serviceDependencies.local.json @@ -0,0 +1,23 @@ +{ + "dependencies": { + "rabbitmq1": { + "containerPorts": "5672:5672,15672:15672", + "secretStore": "LocalSecretsFile", + "containerName": "rabbitmq", + "containerImage": "rabbitmq:3-management-alpine", + "type": "rabbitmq.container", + "connectionId": "ConnectionStrings:EventBus", + "dynamicId": null + }, + "mssql1": { + "serviceConnectorResourceId": "", + "containerPorts": "1434:1433", + "secretStore": "LocalSecretsFile", + "containerName": "catalog-mssql", + "containerImage": "mcr.microsoft.com/mssql/server:2019-latest", + "type": "mssql.container", + "connectionId": "ConnectionStrings:CatalogDB", + "dynamicId": null + } + } +} \ No newline at end of file diff --git a/src/Services/Catalog/Catalog.API/appsettings.Development.json b/src/Services/Catalog/Catalog.API/appsettings.Development.json index 1d5574f63..6e2811bf0 100644 --- a/src/Services/Catalog/Catalog.API/appsettings.Development.json +++ b/src/Services/Catalog/Catalog.API/appsettings.Development.json @@ -1,15 +1,6 @@ { - "ConnectionString": "Server=tcp:127.0.0.1,5433;Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word", - "PicBaseUrl": "http://localhost:5101/api/v1/catalog/items/[0]/pic/", - "Serilog": { - "MinimumLevel": { - "Default": "Debug", - "Override": { - "Microsoft": "Warning", - "Microsoft.eShopOnContainers": "Debug", - "System": "Warning" - } - } + "ConnectionStrings": { + "CatalogDB": "Server=tcp:127.0.0.1,5433;Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word;Encrypt=false" }, - "EventBusConnection": "localhost" + "PicBaseUrl": "http://localhost:5222/api/v1/catalog/items/[0]/pic/" } \ No newline at end of file diff --git a/src/Services/Catalog/Catalog.API/appsettings.json b/src/Services/Catalog/Catalog.API/appsettings.json index f8342fe8d..3ddd8ff25 100644 --- a/src/Services/Catalog/Catalog.API/appsettings.json +++ b/src/Services/Catalog/Catalog.API/appsettings.json @@ -1,30 +1,43 @@ { - "UseCustomizationData": false, - "Serilog": { - "SeqServerUrl": null, - "LogstashgUrl": null, - "MinimumLevel": { + "Logging": { + "LogLevel": { "Default": "Information", - "Override": { - "Microsoft": "Warning", - "Microsoft.eShopOnContainers": "Information", - "System": "Warning" + "Microsoft.AspNetCore": "Warning" + } + }, + "Kestrel": { + "Endpoints": { + "Http": { + "Url": "http://localhost:5222" + }, + "gRPC": { + "Url": "http://localhost:6222", + "Protocols": "Http2" } } }, - "AzureServiceBusEnabled": false, - "AzureStorageEnabled": false, - "SubscriptionClientName": "Catalog", + "OpenApi": { + "Endpoint": { + "Name": "Catalog.API V1" + }, + "Document": { + "Description": "The Catalog Microservice HTTP API. This is a Data-Driven/CRUD microservice sample", + "Title": "eShopOnContainers - Catalog HTTP API", + "Version": "v1" + } + }, + "ConnectionStrings": { + "EventBus": "localhost" + }, + "EventBus": { + "SubscriptionClientName": "Catalog", + "RetryCount": 5 + }, "ApplicationInsights": { "InstrumentationKey": "" }, - "EventBusRetryCount": 5, - "UseVault": false, - "Vault": { - "Name": "eshop", - "ClientId": "your-client-id", - "ClientSecret": "your-client-secret" - } - + "UseCustomizationData": false, + "AzureServiceBusEnabled": false, + "AzureStorageEnabled": false } - + diff --git a/src/Services/Catalog/Catalog.API/web.config b/src/Services/Catalog/Catalog.API/web.config deleted file mode 100644 index 498dea85d..000000000 --- a/src/Services/Catalog/Catalog.API/web.config +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj b/src/Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj index 3b57655b7..37f9cf89b 100644 --- a/src/Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj +++ b/src/Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj @@ -7,7 +7,7 @@ - + @@ -15,7 +15,7 @@ - + Always diff --git a/src/Services/Catalog/Catalog.FunctionalTests/CatalogScenarioBase.cs b/src/Services/Catalog/Catalog.FunctionalTests/CatalogScenarioBase.cs index 562dc000d..64296dd0a 100644 --- a/src/Services/Catalog/Catalog.FunctionalTests/CatalogScenarioBase.cs +++ b/src/Services/Catalog/Catalog.FunctionalTests/CatalogScenarioBase.cs @@ -1,37 +1,29 @@ +using System; +using Microsoft.AspNetCore.Mvc.Testing; + namespace Catalog.FunctionalTests; -public class CatalogScenariosBase +public class CatalogScenariosBase { - public TestServer CreateServer() + private class CatalogApplication : WebApplicationFactory { - var path = Assembly.GetAssembly(typeof(CatalogScenariosBase)) - .Location; - - var hostBuilder = new WebHostBuilder() - .UseContentRoot(Path.GetDirectoryName(path)) - .ConfigureAppConfiguration(cb => + protected override IHost CreateHost(IHostBuilder builder) + { + builder.ConfigureAppConfiguration(c => { - cb.AddJsonFile("appsettings.json", optional: false) - .AddEnvironmentVariables(); - }); - + var directory = Path.GetDirectoryName(typeof(CatalogScenariosBase).Assembly.Location)!; - var testServer = new TestServer(hostBuilder); - - testServer.Host - .MigrateDbContext((context, services) => - { - var env = services.GetService(); - var settings = services.GetService>(); - var logger = services.GetService>(); + c.AddJsonFile(Path.Combine(directory, "appsettings.Catalog.json"), optional: false); + }); - new CatalogContextSeed() - .SeedAsync(context, env, settings, logger) - .Wait(); - }) - .MigrateDbContext((_, __) => { }); + return base.CreateHost(builder); + } + } - return testServer; + public TestServer CreateServer() + { + var factory = new CatalogApplication(); + return factory.Server; } public static class Get @@ -74,4 +66,9 @@ public class CatalogScenariosBase return $"?pageIndex={pageIndex}&pageSize={pageCount}"; } } + + public static class Put + { + public static string UpdateCatalogProduct = "api/v1/catalog/items"; + } } diff --git a/src/Services/Catalog/Catalog.FunctionalTests/appsettings.Catalog.json b/src/Services/Catalog/Catalog.FunctionalTests/appsettings.Catalog.json new file mode 100644 index 000000000..6cd2cb192 --- /dev/null +++ b/src/Services/Catalog/Catalog.FunctionalTests/appsettings.Catalog.json @@ -0,0 +1,13 @@ +{ + "ExternalCatalogBaseUrl": "http://localhost:5101", + "isTest": "true", + "PicBaseUrl": "http://localhost:5101/api/v1/catalog/items/[0]/pic/", + + "ConnectionStrings": { + "CatalogDB": "Server=tcp:127.0.0.1,5433;Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word;Encrypt=False;TrustServerCertificate=true" + }, + + "EventBus": { + "SubscriptionClientName": "Catalog" + } +} diff --git a/src/Services/Catalog/Catalog.FunctionalTests/appsettings.json b/src/Services/Catalog/Catalog.FunctionalTests/appsettings.json deleted file mode 100644 index 708598b8a..000000000 --- a/src/Services/Catalog/Catalog.FunctionalTests/appsettings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "ConnectionString": "Server=tcp:127.0.0.1,5433;Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word;Encrypt=False;TrustServerCertificate=true", - "ExternalCatalogBaseUrl": "http://localhost:5101", - "IdentityUrl": "http://localhost:5105", - "isTest": "true", - "EventBusConnection": "localhost", - "PicBaseUrl": "http://localhost:5101/api/v1/catalog/items/[0]/pic/", - "SubscriptionClientName": "Catalog" -} diff --git a/src/Services/Identity/Identity.API/AppSettings.cs b/src/Services/Identity/Identity.API/AppSettings.cs deleted file mode 100644 index 1f45763fe..000000000 --- a/src/Services/Identity/Identity.API/AppSettings.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Identity.API -{ - public class AppSettings - { - public string MvcClient { get; set; } - - public bool UseCustomizationData { get; set; } - } -} diff --git a/src/Services/Identity/Identity.API/Dockerfile b/src/Services/Identity/Identity.API/Dockerfile index 0aa3105af..574eb277a 100644 --- a/src/Services/Identity/Identity.API/Dockerfile +++ b/src/Services/Identity/Identity.API/Dockerfile @@ -32,6 +32,7 @@ COPY "Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj" COPY "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" COPY "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" COPY "Services/Payment/Payment.API/Payment.API.csproj" "Services/Payment/Payment.API/Payment.API.csproj" +COPY "Services/Services.Common/Services.Common.csproj" "Services/Services.Common/Services.Common.csproj" COPY "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" COPY "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" COPY "Web/WebhookClient/WebhookClient.csproj" "Web/WebhookClient/WebhookClient.csproj" diff --git a/src/Services/Identity/Identity.API/GlobalUsings.cs b/src/Services/Identity/Identity.API/GlobalUsings.cs index bbbe458c9..439583c53 100644 --- a/src/Services/Identity/Identity.API/GlobalUsings.cs +++ b/src/Services/Identity/Identity.API/GlobalUsings.cs @@ -1,7 +1,11 @@ -global using Azure.Core; -global using Azure.Identity; -global using HealthChecks.UI.Client; -global using IdentityModel; +global using System; +global using System.Collections.Generic; +global using System.ComponentModel.DataAnnotations; +global using System.IdentityModel.Tokens.Jwt; +global using System.Linq; +global using System.Security.Claims; +global using System.Text.RegularExpressions; +global using System.Threading.Tasks; global using Duende.IdentityServer; global using Duende.IdentityServer.Configuration; global using Duende.IdentityServer.Events; @@ -10,54 +14,33 @@ global using Duende.IdentityServer.Models; global using Duende.IdentityServer.Services; global using Duende.IdentityServer.Stores; global using Duende.IdentityServer.Validation; +global using IdentityModel; global using Microsoft.AspNetCore.Authentication; global using Microsoft.AspNetCore.Authorization; global using Microsoft.AspNetCore.Builder; -global using Microsoft.AspNetCore.Diagnostics.HealthChecks; global using Microsoft.AspNetCore.Hosting; -global using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +global using Microsoft.AspNetCore.Http; global using Microsoft.AspNetCore.Identity; -global using Microsoft.AspNetCore.Mvc.Rendering; +global using Microsoft.AspNetCore.Identity.EntityFrameworkCore; global using Microsoft.AspNetCore.Mvc; global using Microsoft.AspNetCore.Mvc.Filters; +global using Microsoft.AspNetCore.Mvc.Rendering; +global using Microsoft.EntityFrameworkCore; 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; -global using Microsoft.eShopOnContainers.Services.Identity.API.Data; global using Microsoft.eShopOnContainers.Services.Identity.API.Configuration; +global using Microsoft.eShopOnContainers.Services.Identity.API.Data; global using Microsoft.eShopOnContainers.Services.Identity.API.Models; global using Microsoft.eShopOnContainers.Services.Identity.API.Services; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.Diagnostics.HealthChecks; global using Microsoft.Extensions.Hosting; global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.Options; global using Polly; -global using System.Collections.Generic; -global using System.ComponentModel.DataAnnotations; -global using System.Data.SqlClient; -global using System.IdentityModel.Tokens.Jwt; -global using System.IO; -global using System.Linq; -global using System.Security.Claims; -global using System.Text.RegularExpressions; -global using System.Threading.Tasks; -global using System; -global using Microsoft.AspNetCore.Http; - - - - - - - - - - +global using Services.Common; diff --git a/src/Services/Identity/Identity.API/IWebHostExtensions.cs b/src/Services/Identity/Identity.API/IWebHostExtensions.cs deleted file mode 100644 index 734ae3fde..000000000 --- a/src/Services/Identity/Identity.API/IWebHostExtensions.cs +++ /dev/null @@ -1,69 +0,0 @@ -namespace Microsoft.AspNetCore.Hosting -{ - public static class IWebHostExtensions - { - public static bool IsInKubernetes(this IWebHost webHost) - { - var cfg = webHost.Services.GetService(); - var orchestratorType = cfg.GetValue("OrchestratorType"); - return orchestratorType?.ToUpper() == "K8S"; - } - - public static IWebHost MigrateDbContext(this IWebHost webHost, Action seeder) where TContext : DbContext - { - var underK8s = webHost.IsInKubernetes(); - - using var scope = webHost.Services.CreateScope(); - var services = scope.ServiceProvider; - var logger = services.GetRequiredService>(); - var context = services.GetService(); - - try - { - logger.LogInformation("Migrating database associated with context {DbContextName}", typeof(TContext).Name); - - if (underK8s) - { - InvokeSeeder(seeder, context, services); - } - else - { - var retries = 10; - var retry = Policy.Handle() - .WaitAndRetry( - retryCount: retries, - sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), - onRetry: (exception, timeSpan, retry, ctx) => - { - logger.LogWarning(exception, "[{prefix}] Exception {ExceptionType} with message {Message} detected on attempt {retry} of {retries}", nameof(TContext), exception.GetType().Name, exception.Message, retry, retries); - }); - - //if the sql server container is not created on run docker compose this - //migration can't fail for network related exception. The retry options for DbContext only - //apply to transient exceptions - // Note that this is NOT applied when running some orchestrators (let the orchestrator to recreate the failing service) - retry.Execute(() => InvokeSeeder(seeder, context, services)); - } - - logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name); - } - catch (Exception ex) - { - logger.LogError(ex, "An error occurred while migrating the database used on context {DbContextName}", typeof(TContext).Name); - if (underK8s) - { - throw; // Rethrow under k8s because we rely on k8s to re-run the pod - } - } - - return webHost; - } - - private static void InvokeSeeder(Action seeder, TContext context, IServiceProvider services) - where TContext : DbContext - { - context.Database.Migrate(); - seeder(context, services); - } - } -} diff --git a/src/Services/Identity/Identity.API/Identity.API.csproj b/src/Services/Identity/Identity.API/Identity.API.csproj index 58f550997..91d74a376 100644 --- a/src/Services/Identity/Identity.API/Identity.API.csproj +++ b/src/Services/Identity/Identity.API/Identity.API.csproj @@ -4,75 +4,62 @@ net7.0 aspnet-eShopOnContainers.Identity-90487118-103c-4ff0-b9da-e5e26f7ab0c5 ..\..\..\..\docker-compose.dcproj - false - true - - - - - - - - - - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + true + PreserveNewest + + + + + + + diff --git a/src/Services/Identity/Identity.API/Program.cs b/src/Services/Identity/Identity.API/Program.cs index 2dafe5cbe..aa3d03a84 100644 --- a/src/Services/Identity/Identity.API/Program.cs +++ b/src/Services/Identity/Identity.API/Program.cs @@ -1,82 +1,63 @@ -var appName = "Identity.API"; -var builder = WebApplication.CreateBuilder(); +var builder = WebApplication.CreateBuilder(args); -if (builder.Configuration.GetValue("UseVault", false)) +builder.AddServiceDefaults(); + +builder.Services.AddControllersWithViews(); + +builder.Services.AddDbContext(options => + options.UseSqlServer(builder.Configuration.GetConnectionString("IdentityDB"))); + +builder.Services.AddIdentity() + .AddEntityFrameworkStores() + .AddDefaultTokenProviders(); + +builder.Services.AddIdentityServer(options => { - 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); -} + 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() +.AddDeveloperSigningCredential(); // Not recommended for production - you need to store your key material somewhere secure + +builder.Services.AddHealthChecks() + .AddSqlServer(_ => + builder.Configuration.GetRequiredConnectionString("IdentityDB"), + name: "IdentityDB-check", + tags: new string[] { "IdentityDB" }); -builder.AddCustomConfiguration(); -builder.AddCustomSerilog(); -builder.AddCustomMvc(); -builder.AddCustomDatabase(); -builder.AddCustomIdentity(); -builder.AddCustomIdentityServer(); -builder.AddCustomAuthentication(); -builder.AddCustomHealthChecks(); -builder.AddCustomApplicationServices(); +builder.Services.AddTransient(); +builder.Services.AddTransient, EFLoginService>(); +builder.Services.AddTransient(); var app = builder.Build(); -if (app.Environment.IsDevelopment()) -{ - app.UseDeveloperExceptionPage(); -} -var pathBase = builder.Configuration["PATH_BASE"]; -if (!string.IsNullOrEmpty(pathBase)) -{ - app.UsePathBase(pathBase); -} +app.UseServiceDefaults(); + 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 -{ - 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) +// 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()) { - app.Logger.LogCritical(ex, "Host terminated unexpectedly ({ApplicationName})...", appName); - return 1; + await SeedData.EnsureSeedData(scope, app.Configuration, app.Logger); } -finally -{ - Serilog.Log.CloseAndFlush(); -} \ No newline at end of file + +await app.RunAsync(); diff --git a/src/Services/Identity/Identity.API/ProgramExtensions.cs b/src/Services/Identity/Identity.API/ProgramExtensions.cs deleted file mode 100644 index ac869b6ae..000000000 --- a/src/Services/Identity/Identity.API/ProgramExtensions.cs +++ /dev/null @@ -1,117 +0,0 @@ -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/Properties/launchSettings.json b/src/Services/Identity/Identity.API/Properties/launchSettings.json index e52e9f99c..485a5f313 100644 --- a/src/Services/Identity/Identity.API/Properties/launchSettings.json +++ b/src/Services/Identity/Identity.API/Properties/launchSettings.json @@ -1,25 +1,9 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:54010/", - "sslPort": 0 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "http://localhost:55105", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "eShopOnContainers.Identity": { + "Identity.API": { "commandName": "Project", "launchBrowser": true, - "launchUrl": "http://localhost:55105", + "applicationUrl": "http://localhost:5223", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/src/Services/Identity/Identity.API/Properties/serviceDependencies.json b/src/Services/Identity/Identity.API/Properties/serviceDependencies.json new file mode 100644 index 000000000..718987b23 --- /dev/null +++ b/src/Services/Identity/Identity.API/Properties/serviceDependencies.json @@ -0,0 +1,9 @@ +{ + "dependencies": { + "mssql1": { + "type": "mssql", + "connectionId": "ConnectionString", + "dynamicId": null + } + } +} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Properties/serviceDependencies.local.json b/src/Services/Identity/Identity.API/Properties/serviceDependencies.local.json new file mode 100644 index 000000000..cae5e3931 --- /dev/null +++ b/src/Services/Identity/Identity.API/Properties/serviceDependencies.local.json @@ -0,0 +1,14 @@ +{ + "dependencies": { + "mssql1": { + "serviceConnectorResourceId": "", + "containerPorts": "1433:1433", + "secretStore": "LocalSecretsFile", + "containerName": "identity-sql", + "containerImage": "mcr.microsoft.com/mssql/server:2019-latest", + "type": "mssql.container", + "connectionId": "ConnectionString", + "dynamicId": null + } + } +} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Quickstart/Account/LoginViewModel.cs b/src/Services/Identity/Identity.API/Quickstart/Account/LoginViewModel.cs index 753f4773e..f539c70e9 100644 --- a/src/Services/Identity/Identity.API/Quickstart/Account/LoginViewModel.cs +++ b/src/Services/Identity/Identity.API/Quickstart/Account/LoginViewModel.cs @@ -10,7 +10,7 @@ public class LoginViewModel : LoginInputModel public bool EnableLocalLogin { get; set; } = true; public IEnumerable ExternalProviders { get; set; } = Enumerable.Empty(); - public IEnumerable VisibleExternalProviders => ExternalProviders.Where(x => !String.IsNullOrWhiteSpace(x.DisplayName)); + public IEnumerable 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/Consent/ConsentController.cs b/src/Services/Identity/Identity.API/Quickstart/Consent/ConsentController.cs index 1255df44a..2cdf323ec 100644 --- a/src/Services/Identity/Identity.API/Quickstart/Consent/ConsentController.cs +++ b/src/Services/Identity/Identity.API/Quickstart/Consent/ConsentController.cs @@ -217,7 +217,7 @@ public class ConsentController : Controller public ScopeViewModel CreateScopeViewModel(ParsedScopeValue parsedScopeValue, ApiScope apiScope, bool check) { var displayName = apiScope.DisplayName ?? apiScope.Name; - if (!String.IsNullOrWhiteSpace(parsedScopeValue.ParsedParameter)) + if (!string.IsNullOrWhiteSpace(parsedScopeValue.ParsedParameter)) { displayName += ":" + parsedScopeValue.ParsedParameter; } diff --git a/src/Services/Identity/Identity.API/SeedData.cs b/src/Services/Identity/Identity.API/SeedData.cs index 1ce95871f..fa8365f9e 100644 --- a/src/Services/Identity/Identity.API/SeedData.cs +++ b/src/Services/Identity/Identity.API/SeedData.cs @@ -1,8 +1,8 @@ -namespace Microsoft.eShopOnContainers.Services.Identity.API; +namespace Microsoft.eShopOnContainers.Services.Identity.API; public class SeedData { - public static async Task EnsureSeedData(IServiceScope scope, IConfiguration configuration, Microsoft.Extensions.Logging.ILogger logger) + public static async Task EnsureSeedData(IServiceScope scope, IConfiguration configuration, ILogger logger) { var retryPolicy = CreateRetryPolicy(configuration, logger); var context = scope.ServiceProvider.GetRequiredService(); @@ -104,16 +104,7 @@ public class SeedData 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); - } - ); + onRetry: (exception, retry, timeSpan) => logger.LogWarning(exception, "Error migrating database (retry attempt {retry})", retry)); } return Policy.NoOpAsync(); diff --git a/src/Services/Identity/Identity.API/appsettings.Development.json b/src/Services/Identity/Identity.API/appsettings.Development.json new file mode 100644 index 000000000..7b119bdf1 --- /dev/null +++ b/src/Services/Identity/Identity.API/appsettings.Development.json @@ -0,0 +1,5 @@ +{ + "ConnectionStrings": { + "IdentityDB": "Server=tcp:127.0.0.1,5433;Initial Catalog=Microsoft.eShopOnContainers.Services.IdentityDb;User Id=sa;Password=Pass@word;Encrypt=false" + } +} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/appsettings.json b/src/Services/Identity/Identity.API/appsettings.json index 087e17fe7..cc633ba9e 100644 --- a/src/Services/Identity/Identity.API/appsettings.json +++ b/src/Services/Identity/Identity.API/appsettings.json @@ -1,31 +1,22 @@ { - "ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.IdentityDb;User Id=sa;Password=Pass@word;Encrypt=False;TrustServerCertificate=true;", - "IsClusterEnv": "False", - "MvcClient": "http://localhost:5100", - "SpaClient": "http://localhost:5104", - "XamarinCallback": "http://localhost:5105/xamarincallback", - "UseCustomizationData": false, - "Serilog": { - "SeqServerUrl": null, - "LogstashgUrl": null, - "MinimumLevel": { + "Logging": { + "LogLevel": { "Default": "Information", - "Override": { - "Microsoft": "Warning", - "Microsoft.eShopOnContainers": "Information", - "System": "Warning" - } + "Microsoft.AspNetCore": "Warning" } }, + "IsClusterEnv": "False", + "XamarinCallback": "http://localhost:5105/xamarincallback", + "UseCustomizationData": false, "ApplicationInsights": { "InstrumentationKey": "" }, - "UseVault": false, - "Vault": { - "Name": "eshop", - "ClientId": "your-client-id", - "ClientSecret": "your-client-secret" - }, "TokenLifetimeMinutes": 120, - "PermanentTokenLifetimeDays": 365 + "PermanentTokenLifetimeDays": 365, + "BasketApiClient": "http://localhost:5221", + "OrderingApiClient": "http://localhost:5224", + "WebShoppingAggClient": "http://localhost:5229", + "WebhooksApiClient": "http://localhost:5227", + "MvcClient": "http://localhost:5331", + "SpaClient": "http://localhost:5104" } diff --git a/src/Services/Identity/Identity.API/keys/is-signing-key-10C452043E09C5A22FC8D97669868B8F.json b/src/Services/Identity/Identity.API/keys/is-signing-key-10C452043E09C5A22FC8D97669868B8F.json deleted file mode 100644 index 09d78ff83..000000000 --- a/src/Services/Identity/Identity.API/keys/is-signing-key-10C452043E09C5A22FC8D97669868B8F.json +++ /dev/null @@ -1 +0,0 @@ -{"Version":1,"Id":"10C452043E09C5A22FC8D97669868B8F","Created":"2022-11-30T12:03:40.1630635Z","Algorithm":"RS256","IsX509Certificate":false,"Data":"CfDJ8JmnWj_BpdBDujWYr1NmSawi1hoBHaFG7R1_Hu0pSObE9SiTNg1Wm83zVA3-oCwbntipelQt_gpZ3KH-NMBYdleYt4Gzhpvf4uchFNFzvdnx1X9jpWQi9WCmUm3cVNfzNG2eIKzrVLAoiaWLuDDG9XT-u2ojWIuKRKH7zEBMGrqCDQiVHZlBuqH_qzpWUQn-qKYEkFRcm05OmMYXJA0AquXimdOl1V_BcagZOpNVzG3C5t7lZTsTrm7FpD5zMdyZxL2VJJjRy5fUZbIQIjbQdTGYJaROhj-Sc4h2MgyrMNYJ-TQZKci_5ZoN-GJfjdaB-3UHDk_uFrVk9WpAYEWw0rw-I05tMu8vVxlE2jAnEGaVWEqS0f_Bz53MkDQb2YEleekTwaLJM1JZF791NTaG7oDXHSetSeo7UngHt3D9Ls2FHnZwV-B5mvzZju_-U7inwsoKXjZeqzQfQMs2IsIgiXJJkS20PObmosEbq7LkT97qRrinGicY0pA2zIcDmLiywYzWpsP6Rjw7epDaDlPu2J9BZ93Ni-slfTkNdn2SvAfE2dsJmV7Pz5tMEE-0mBlSMbcrs8TuVQayYPm2dm-sZLCv-iQu0rrvGcJ-lLPliby69du5oFU6WKpo52X4Du9tcM-5prBLWRVt_gcq16zBY_f8ieNnq6b0C_9chaztCevTQAouiSk7ni3zjJo3RuG1wPUts2tLT4sjY6IK2Zbhsvi-negegn91cFA0MzdOaD2Ca2GWN2L2kRrDWioVz_P4ztVFIU3SrgjW6stOGNreRvOB8txUCexJuUP0rb85pOa8rxPCkk1Iw8BjjRdirhvU3bvqhml6i2_iqNTAxpx89A6LRUuOYvYiwJersfhOF9F-FfOVGNxxfm1AKKnUBDfgLWDAw1r8PBeV6huAqUSIwaN5PcQJcdsfER9FeP9_CJqDgNGaL-psm2NQDaVV2Vx4m3GqWQhKMlk90zee5XNe1wobBS0XW_GQYieWWhH5t0pyzJi0QvpFyMAz1mcGXQs-TiHoU142FlFQojIu3_Ynf2RHOOByWDM9krMKMn6ZTnLWgBXnWtMefXqYNEhlACtP8lIXBxcbI2wFeBgRmaQA2DtEEjlymZ4RTaiQEuVC6B_wtTTVVFx3CL-TM62Xs-FOWTYafzKqGsHKzVWnB3fZqjDh4DNwb_A4mkfxoyb2UI9W4P7BJmbV5tOktYudVAFLt3XL7uNhBYLDSOjL_n8bixTg-sLUyO1H5-IxYQJciZdAro4yGO7r40FTYpZfTyvlr3RqFMDhBEPu_nSx9lPKrgpei0DmqYWw_cHJ3mPR1HDUE_i8Prlyzsyf9F-pYV4HMyupetXeXxMAIULnB52SE7uYfSUZHC25Ffo_tif8zMPo2ZKiMpc42BZ0cBptkPKUCvkR-Cq3pFnqXkKADWh1LLBP09hZtOjXqkFdsIFZq5KfRE6UVIyMJ91L-OSHOnggdjScFJzPlgEteUbydKSlPEBkicIs-uXXe8Tk8KZg2CfUU2WnziP0-VgymbTomKR5xz36kJqMjP2VYp6Xy-t3joQEqPE50ntT0AYUFJb6NZt5stHQz76-WecPnA6ZSA3aJec7NlLrxwM6BMGDyBZpbkgWtWFJdGWz6m9j4RKuyZBnyOXzYZU8FDNpDu6FVEymUkYxEhUnpIl7PZWuiZGADwj9-Bpzg0To8jMJ-jgx8w3RDzb5DoM6-jh0IJ5vecn8lSfRzHcOu48CLY5mTuHFr93YKiQCK06DS8VY3kI1b8irtdvmY-IvUCyCJlpzBYh8vFrS2PdJYsRVWuGJraxMiHYP_zH5r4Q4z27p6vt0Rbm0S4Fm1I9z4Hmaa4igF1mnktuTMizyxKWTsh3DjbsB_6W_lNU62RqJAt_IGr69MIphVY6ZOcbLx2I8p0z4yWpuYJey2BmRQPQeCZ-Tb9HtHESxTBFxupYul8619OJWA7RfLl-xv43X81OZnaxE9wLVFs5uEowKoEOzl_K5nxOw4igCiWYWufaFMMrZSu-0ni9RVSwytOPiEbGV0XOcVnB_lHDz1ymvDI4u1rfOVkehwB_wFBeRJRSEymzoD-bgRox-a1qLJNhOsdm9WUdZF3MHbmmA8NpyqU5qOTTqCvY-FXdJHL_OHmq1nDVHCJvcwLqUfPFMfFAi0MZQEhRndeQWYGdRGOhmvtwbRX-Hryz2HKXber1ixUvY3-XCxjzSyZdn845UzpKBdsgwAwY-LFzlgNQGEKHzsZUl8MSeqx3iO2QwDwC5eV4gNWBC5QBw0NAloBmdnQ84sSWydPViOYW1CinUQAnZsW2Z-0Xw0FP4fbS4HvulNBMIxE_mfJsHny4YZu_463zGR0SrV2hEFSiXuwSx27K6xSyPRB26__t8w37AeFYXtavqr9rB5GXnEJRQkW-i5lDnhtA93uRjfNzVhUpcTK-ROm3h7b4UrUTaK_IMLXxoYEjOFm8-cw1ZGHu3","DataProtected":true} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/keys/is-signing-key-1417AE0BAF26F393609EF30FA355D06C.json b/src/Services/Identity/Identity.API/keys/is-signing-key-1417AE0BAF26F393609EF30FA355D06C.json deleted file mode 100644 index 47ee8034b..000000000 --- a/src/Services/Identity/Identity.API/keys/is-signing-key-1417AE0BAF26F393609EF30FA355D06C.json +++ /dev/null @@ -1 +0,0 @@ -{"Version":1,"Id":"1417AE0BAF26F393609EF30FA355D06C","Created":"2022-11-30T15:12:36.3392917Z","Algorithm":"RS256","IsX509Certificate":false,"Data":"CfDJ8Etya4tS5j5DiLTWt2Gh6S_0SY7Shff7x3C-zhStPviP80hTMRjXNVXCumUQnwoKrJ48OLZhHuU7wgSxqXaAT3hk35KvO_KOhz5pV_sWLPOogHC-hpQTIzSEbXzyfEIkliqVgW3AfpLbuDTZY4Hj8hu-OM0waYEjCQkIv-PrftxX7JU4KCT1rxZBUrDJN_mKCQEGWy0LGLVsLV8dKyLMSvJsIz3HJh4sngzist-XVaPxat2eMPqVOJEWkbUf6VqwJOnssR-ZOS9b63YZPhihUcz6J0T5Y2aWIyZfZwSkuh8DiL9_wzKSRTmplaY1tzeq_-FhbkgwtLX9VDbeesx_HGL_Pxj5SG5aprfZ7DN2FBr70m7olb-bEl8jOm0w7ot0QGwxHft53Nf9piLEyQp4hsReq-PNwCirzFNkQ5aM6Kwvwitkop_Ab8gpzfwiEw-uVyPN_gpkrt099MQrdtljc9gXHKuXr4321pgcbwO6Ua-amRcF_9uYSCVBdIhwmpaQAgMmI-0uI-5Ph7H23ZV2zR1epClkiR2t5RWOtXNgLqoupXyBjDWrse3al-CsdnkofmtY0bqr2ZdNd_EKFxqn7Gf5-dNuPJEboDUdwvjUpWYIzMlq6_3q9KP012SbimkqMgz3-3jDBw_xe43DvZd2CnWFTDQSuD0m7DTf2GVL0DlasQNYQgO1T7xN7hgScsXmzhJNUx7iz2ze5BMqWV3vw6P6ny4sj_QA4KAi0zxl7sPgPo2p-65MWlbZy646ZYlm-jDD9dfORdo9nmURg3ITYNVUREtVg4IcEzPVDBMnV-NEf3T3vHD1V49yVhovA9oAHj4uTye-bbLUIhoLPCZ5GfR8AEHE4IhL0JyjuaWilp1qNEXAhziwZUELenYAf74wEzMvSSobEWL5Hq-ApMdRePBjjeGrIobS6KuE7b3HVsYwJt9pWvmVCYZ-nQCDAOIFcgH5kifkHu6NEi23Jk7a5hmgq1kVJXr5xcli9B-bHu1mO1kl0gXsqJcGpfetcw8bEtwO8F_6kdkmW3RXLa3rvrnkigPJJtn4sm_3ee1dqFYarJlXHPugwuyzfEPXNiT1HCcDhQYlrYxfKSzmlhjthGn14W2ZYtb3YVCXUFReA8V20rs4ec7VTdjuKCPgA8bk3xKevr6aETF9I65hbI0pIFQ9_cJHkl9a3BZL-X5qSH2s3_rvPupVuP6mu-Y1pUEGp4p5XjZc0CqsLm-W6MsUKmBgeCiokmUGfs5P_Riv9ZuvlHyYcwj2qmgf4MDkmhThfisook1nrPomO1lByHMRHx8rwbVQQXHPKMI32WZxfpRQiuAERonMvLBd2P2UOutqe6t7qy_gbeM0WNEcKb08UowSxGUFnEH6Vk1Dx9Vzye2tw7ej-EsT39hudhe_59nd9IYvx1o547N1cI_ASDVn3ibVQU_BWLrSK-HIqGaA52F_hthPGpMjVBO8xtLXHiyxqI-gh9iLq0vpvMeFQjyLSmFjKQifMEVaATAF9h2TJTc0yqkIVN9y77LvkUDoFvi96zGSYDyIf6rtiF_E1GnAwu-6Le16bj9npI-FcNmh-DRIvA6LepbSXs27rVMjQdK6dOPDavu5Y9IM5bg0pndKek0orZzkbaFwxTxmM08AhKlyN6PPiIqcZ7i456FQDhqMTUEeBEShsOJy4573e0GA-J1sHvzVQYiKDlzvgYD-zh79OxnJXlq22BNrWsQ6pqXfXgB5UIvhUTrmcrUtv3uw6GcAyl76ySgfs3Hb9Mpk0aeUdI-xyMuxxJvz1w1NffcuXPsxf3VosZO8vkofjVSO8lGtzarScy-abFGD9UA-S7xGvz0rqRSAEnpxe4AZ5eBfs_VmTy21RYJqxPbrYWcjrRdQjxERhND70wiuLkF1pivUQ-bohP3w0EfV0fJoSoH9HthQ9_aY2ASJnv82BX_lPRdEgYVh1RYWziVGB_6WWkch8BLrUzXezJZcfQHGuEQYzVB17RQIhCqFo7GexmHkFB40punvvS1gUZOQLiqSu8UvJ27F5KNLpkgDy8q0pDWewRGmZ5J09aMlJE6e08u_gPrA9G52SWXvdkX8CRvPn-xOLsBfb1_84QBhB1PuSKeptNLjgMufTdfUkGrL_b3fTo4AgLz7VuKrAMNYjwCTLr4e6towu-JJ08Fr3c8igbWApP6yNiVbE1W1mX66jaRfyw3jiVAP2ytEH_0kLOE3BDFkepIopwGtkQ2ultqFHxr_-oSadSpP3g4dLt1GfQ7fjzYkYAkstXxgYgGGe7Q-mwQM1dR8RnE0jFvQnTKi-Oc_pj0h91HgRK_ASQrqrgOf9JN6McbVlE3VbEKvFfOKP8ukFa6i8n7tk1H0B8bX5iy4m_b3i3MbQmQ16gkZ2ECW29-bA4vWz6-R3LZ3kf2hHAJzv8U6cG71KLVgWSBxIyRJE-cSiBPxtM1hGT1qcJvpAVWvowkcO9Anng3lrfgnGaYh","DataProtected":true} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/keys/is-signing-key-22178AEDB80CD0A41DF6874FE93F794D.json b/src/Services/Identity/Identity.API/keys/is-signing-key-22178AEDB80CD0A41DF6874FE93F794D.json deleted file mode 100644 index 07609260d..000000000 --- a/src/Services/Identity/Identity.API/keys/is-signing-key-22178AEDB80CD0A41DF6874FE93F794D.json +++ /dev/null @@ -1 +0,0 @@ -{"Version":1,"Id":"22178AEDB80CD0A41DF6874FE93F794D","Created":"2022-11-29T07:52:44.7571677Z","Algorithm":"RS256","IsX509Certificate":false,"Data":"CfDJ8E4RSuU_s2BGnQ_I0J1Xz9YiYIkBDFNHHGo4A20gtVeWqoNIQNxtP1p0JacgjeZkGl9NlE2-7qD02Vs9NIZ_IfOHW71XkfBIuIp5JvnwMi-X42Qw1F6UTQKNlYNvk1Fh27a6tM-mQgxysayCdDLlJWoRJYKDPDkUO03rZVkWDD2r6QNcMNtYhjM7oxy2yiw479PIeIldPOk73cPqp_k4oMPTRWwS04dX8fpQNA_3d2PoV1sw8-Hbmspiv3PO-umUGU2OFcN243cP9aaGXDoR-RgEbCn225WuHAWTe3tf39zVDtbAWDnRTNEX_jCNBItSaFOdMerDKD28PXTnWXuoMKyZ1_8ETZk0CDMAMVktqOatYrx0JHisG8vJbHtzxIvQrZL1YqoAN_qraVLgTl99WIZv56rYSb3RbNSzqB7yNn1Y_YAobXVbqJCaqqVG6uEaQxQ4WRI78sit630anIlKP7vccOtJH_bspQlPisNVrpCWiGo0D0IzNsZXLNPkc9kUbJKGc3xgE5H-z9kbk3sOecVH_iQy4b6fJ0IrDGz9fIhu2UI0-_lUE6sQ0g-NAA9kW68vD6PVSytiag7tFVV0olRPhaiBkSRuNGKjqsPmlPtkpzVxuTxSj19YxoC200cFARZq68qjxZ7ugdHQGlqd6bKFjj6NWCfG3Sbub1HpTkPnEUIR6ic8EfUrbu18MlOh-7bnIJkIuz0Nr5zZ9tPgiJCdGPjNo47z4i-kPWiNhS9k1sJqg60dgCT3onC2Uft4F4dnQpIKb28Fli9qkTDOg9gObEr9W19tMJvEPAhWJSl6HSmIJ-TElrW7nNCdS-H7PIlGWIvP6f0CCzrlgtsfMh8TK0B-Vpv5ky9D67L03IA7VfrhHss5mEkoz2s5-yefn2Wo5uMTxy53O4GIS8j3lF0ufUIBjGqR5q6IyEQUyU79OA5A7A3gJx78Cb-OEK3GixhYwjXYOw1YcguMZo_rGsq-EnjPoVBuZfdwyEIZ847gVwDzFsh0FHZIi2vVxXZ3tNGjJI2TXxNx7nyXuBxERgCPI3C5OeK2Uy8e3ooZCqPICKS9xEnICZteEcSYkRFmvPX6O1pZMKWoiyzGe-bhuBiotTB3yGIVlkhMyqNni2hQHoR5b1CX4YaGBMT_fdtW4uhvYLhLnGR69aal2ffIhiFJMfB_ByI8sALzgsn5jW-zOAmtiEOaPs1O9FqPf1x3Wk2YuoyujgHjHi9SSUz1hAJRmsyCmcYsMdGjMd-wnk83dsdMMeNANTKJHlZP5-__XA8emO-sloHrCiHpALX-JB5RQ1BGVD-dPiTlM0MYeTLdt7y5HRKSAbsOCnnF1vzV8y8NkUgpKVLTPRsJaoNYqik4Wpoa2d2ez-gXrG9S-pAByXHyTHiVDVNyqUTDMI0s8Fm_6_QwJTkgGEV6dR6r89Rzq70FFjdKB5RygRLhRdF2Be8blyExHI3HQg8_S9fcZ3zCybjatlNuNCgNZTTelC1UzWSEql3HqeqiMWIi9Kr8GNl59OIGm2lm9KNoyvcFHlQVQ33lw4akd5Z7aYu_HcdpBhMcEr9CuN6yQPhbCDOVhsMkqzXGzpwC2ezFnALAkbj9P44vRU9p0FEKFDNn_pQimVTLQJoU5Taf1n70CxYcPgM9BU3JPL9VUb9rDS3bHA7XQoO6878k6PkLbJ6GcfIJitaRZL5TkAUJUywH-a5xADVo-1_k3ADJYhOTbwxsYU96Kuhw8FOIToBH8CtLnqJLoJ8iwVPjB9LSsHPhmR7Rsi-x7Oi5JeugLctOvBn1sq7x_hHQKPVHA5LvtQ6Y719bOgPKLybQSCebe0ordUIsPAZMq-OvLj1sPS8PvfvZBDkyUA04avHm5kgT6RJF7ARUI1vMNLijm-_dxxrzY2bj6FHT340iFDURFePLdSM-Ctu3X-gLXKqPzQ6MBBQS0pyC3GkuX7J_GhPOZ-yXvRJmvIEoDAVEvjUQc57Ud6nyS_C_ljLmIoHghJiTJxNOOjR5pucb9Vljxm_BGPZwMi6-bTbD454ESbZ1kyvOAG73PjnPoQSaEYyM3XLW3XgJ9R4dnYDk31zHpQebodqwcFsXzxV43yi7zPrJSMzaFnC3-uPQ7qH4xs9puX5RL4q4lSCUKAB82TMtCJ0xHwj4DYqsJQt9jUAz1IZbgbChq8d0EPe6y2yg38LZs50r6DD0f-VgF1fyup_1ORM1Ub6gRyWxIBZLyLFY6uLvzPhVQaBsDitK2ku-9ELv9frr0j-BPNNCBpNRQyJCbZwOcovJ3gn9w1TAJ4cXGxzJWRGVnJY0e_tJ5ZMwRrn4E6LwhKXK9N2lBU7-2RQxEU79sTRz12SV9v8Az51MlUHzjWZp8p2bttpkQKMeoIkIVu31t88Dn7MOZkwR5DQ8b1_tNV_3Nbqyc9OUUFTSrpfEJeU6VRrUilgvBq78lSov4q1tbKxp4OjhxdNv9NxfYCL11kTZVB_h","DataProtected":true} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/keys/is-signing-key-3139DF3FF07C8E3881CFA9743F89A787.json b/src/Services/Identity/Identity.API/keys/is-signing-key-3139DF3FF07C8E3881CFA9743F89A787.json deleted file mode 100644 index 30deeccf4..000000000 --- a/src/Services/Identity/Identity.API/keys/is-signing-key-3139DF3FF07C8E3881CFA9743F89A787.json +++ /dev/null @@ -1 +0,0 @@ -{"Version":1,"Id":"3139DF3FF07C8E3881CFA9743F89A787","Created":"2022-12-13T11:30:03.9474661Z","Algorithm":"RS256","IsX509Certificate":false,"Data":"CfDJ8LWRXnsATcxOsVa-h-Gywwx7S1URlnPkv5vJtTle9xG_ytf2YXKlRsyey8qFhmrfLZ9uixxUmHTLywyrhUV6-gdvwyirWsUU8qn3SNe4rlPgNYHESkjYwWHERmwrMQIjRell8Xnot5UU1tfIvdpX9AbruzjEuAtYg668fBiXv338XfZ-2DW2RlYxT2TsPAak5ZsGXuIojYCLC3S4uGRz8AhSoko_DSl2KXQqXsqJWTNqAmiEHkVtHeFzc_o2gJIbegzqi7l4F2jDxM0-gkkRZYk6AZlN25wYb90xYaM82bKbzEnqPhLEIkuM-3QTGnjLZIF9jFp1qftq6dDm0feovXXM3SQCsgQPvZ6rKHR_8f-Z072t8PiRxDTgVQKywQ5bCS3u3Mq89hOZf_o_IBNT4uO_hDBqv4eFhj_AAy1Kk2K_QJZhF94bPLsujkkoFjWlNXvm6-Layr9chiY7ZQGxb5QtJr03Y5CT7w6hO2LsdsYEyJLs4kWBdlILT5FjrB4LCVHJlVDGxPBNuGzZL75rSGDa38FCKsUhoBvu7T5XFTm37FaBYrgY7MASQrlHQjGPwyRrjGe-M2ZwTCrtd_MwAHJnJsLoYs6SLQLpQa4sII2qgjRr2UmfDlXP12E6wJrpWfSL6QV13CoCy4hEko4LAy4tG2Yz0HzsqFzPYL142UeJC5z-d9JNdJr9Ya8TqVc7biOJCdY47jeOF5wJpM_BWTzT584w5HRs_1LVNsmjm9l6w1v5I04uNxbpopfRS0B1J2NPS3bfpS2lGuQlyPsZJwNgkZNwxDwCQMa8cgwREnaKWXOeKNksWmS6-ve2M5DguPaKVYF30AyJ1ECqWLX-CLJScBym-ZwVM-2pdfKw3pucfXXUqsXK1lTZSWi7A8gFtnsUZFUC2lkltBT80xuw7f9ojIfoYZjGSwl1mwM5LN5IuwUyppToU7vct9GueMaBjNP5bTrWS1XwiU_oOJbjIx8gNdN-DWNG8-5VmdgXoaJDcd_ZRLaM2XpWcpB5R2MwodIe3HGdpieTDxdqh9Nd1TzQO3FivfxcW0K21CQC-xYaRsx8Ii3djek6k90C304VFRF_QLe4lTOIaAjxxDSFVKbn8tVOZyINRI3FRAXOOaVBoGR1Wsn7bXVGNGbM5eN6wCPOzE-g5_489Sq2MWgczq33_GHW8rGc840OXiqJVu7fL4LVIuG7hPzaVZD3Q-jCu3xG3rYmr2ophVP3HNhl7QDgtpHLYNAIdzZNXz7ROKJU7ws0uZq34plYnLhXEzNe8ntV1N3qCbv1lMk7RjfffZxRwE0OM0mbIC--SPqKG_Cm0cD2k04vS36N9mr1LCmDNiFQRcOqzqxgk8mJdl0Vna-IYycviWyUBs63y6n7bDU3RAifVTSVPUrrrAIMTT5uk4ZVLbO_tGKvKWaSsi7TMypJXd993tut8SPwCl8iNZS3qPwIUXx8IpStnAmJmqcWLZcldZa1bfOWqXW2s22xLNgexft7Xosp-gPNPsICPwtux_tXN-XbbgopGnWgrsyrXctm95OcfEUOVYna73ZA5cukdUbfftGVBitwe7DkG1Jb3MJLZbo2ykiqW71mLgDqvQ2KD_PHw410v-51jsXkUPNknyeSuHiRXomo2HqUf-y0xvx2S73v58yM39XadMJUMYrQlw9lnWBqCDcYoutAVakgWAXSPYELDX2BbmpZsa099h6HlKJptmSLqp2D3J7fHGGWZpdBr9hxVQ56TkMDUNCEd5W1Bc5ecT7b1R5u6IuM16A5aEGOa_phaGuqc9cUhD0UmRBDO6FE-LbfjCnzhjroAOYEujJloOcAYEAL1zx3wUHd_-0hVkmffPWC_Wu2uV8EyQQwlj8bVgCEz6R6bqxl8TN5993C2joikVDCcFYSi8RingP-ItGC5TVTxx0kWweImuBA9s6eoqUZ7TMLKOAQHTxjx5g8mBkLs94RVWhChIUif513Br0aJGxwjBvhN_NBWDFJSbP-l581YNCrmALfS4IX2jeV81bnOAAde53Yplaski3eoR2Z3daNdAJNVqOBe32gHf1eTwDCXrgqd5wYsXxW_YJ-P2kKN2CpuPE-so6tdoCLqFmXu4-3q3vGGE_nVvp-MnhHwLP50U6-h7_MDh105qlIxduM91AT7I3XPTQGrOeIv_eBrng3vULA_ohah-OzbPxpPmYSNFS8YUlGChhPgmMWHKnaYbirh-Df4rc6sK5OFEyRVlCzeKon8hFsmwIEEk7uAR2AysF1PJWjoObQkM-3vqJphkTBBYsZEmngjvQlV8TnGY6P0GRe5gNOaT0BZ71SIlKQY1iLq5NmzEGM1iIOEJI-JA3VHhXs2mw7WDKpsE_k5kbFDlgdachB778jMTSs8xWjESxyidPz-tjqvWaDg482VjoOVYBQOGyUuFRMKVJ9xZBE03nVLG29cKVCNJ_KylBUy0O4LkHskJRzrbZE_HlcoCafl1z1","DataProtected":true} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/keys/is-signing-key-870FA7120249C21C30ADE458B07918C1.json b/src/Services/Identity/Identity.API/keys/is-signing-key-870FA7120249C21C30ADE458B07918C1.json deleted file mode 100644 index acb1883a8..000000000 --- a/src/Services/Identity/Identity.API/keys/is-signing-key-870FA7120249C21C30ADE458B07918C1.json +++ /dev/null @@ -1 +0,0 @@ -{"Version":1,"Id":"870FA7120249C21C30ADE458B07918C1","Created":"2022-11-30T09:59:58.1299062Z","Algorithm":"RS256","IsX509Certificate":false,"Data":"CfDJ8BCNHSqqa79Fqw5EoSNNrOMZDqq5p81RZsuqUc9iuvF_3nm9RHjc2lAsu-T8AjFPxhbWkSNnQAU7JGWpNh_TU1XK9-Skieb68wO2lUFauCBnoNf7R7JEpuw7zdjkInQZFOF1hBzhL0qKszqhM8-2gz2Q2V5c8Ng0C_2CzHNqCsyhTHBD8NgXfzpxdym7V8HZSS4SkJ3QpKLH-VMXRFssCM7PXEimNWBN2yky4IW5_fRgYgRkkCaXCU43ayGxLyBe_J6DzZ1fefO7ZBne2b8HaVVQaAEQDTlsFUxNqDLcAO-k6q3FoL9ZW2gm82a8ra1mSJTbEeAacNK5oE22urnovVF1GPwTVnnXMi_9eloBqyTM5xtTQPYbVlOkZl5pgRjKOY_Mc62Ix9p9hoH-gCfDHw2Q9X34JT1ymzBdYqtgTmWoY548-ZkX6v3AEpkhmsMdKMyNSdz-SJfasTqKkp4JkqGR-gXW17HOtxHmaOPg0ZRuQ4Dx_YHuQr98UQPMUktYL_kF_76FevNyfy5eyN6eawHNGwhXzn0YdyCjaRV1LXfD-4LqqJBhExUSyCtG0Pq0qjZ0ySAuKi72-0532XNRJ1gh7CxlfRdeLDID4ffs1cgmfODFv1k1JElYihqqGwXMwx-SfgpM9eo4j6jqd0kdAgj3n4ZsJKQMCDOGv84Gfhnw7dxlIL0e-D0sKKc6f7KagNFJCbt0LZ492IWzIPIGp5lXQwhrDPECjdNvBv0jhC6yMzZQZlVQXX6MYue_qzXb45TK23j8HjlgYPCip9eqH1NBA1MWkHykA6ZkdZ29eCCMuO-m8zB_Z_7YdEdzGcViK25T2q8qo_0BMzAzdrEJ3rOYOly7wzsoESn6aKr2U1b4qh-fljOwQrYqoSzuiu5l4QY1qVG4ZR3VMjV2lo3LEbMayA6Sis38LQy_ulUKGdOi4KeyNSlbL44wN48gDM86kNPkTM2kVoJc6RRZ5zptnGyXgAsuknHaF5EX48Ihxt2AFU_pVgKGnrx4XkYkxMEwbInoL19WgaKZyLAXjwtUG3FSOPdWswUqbcDNoetfY21Y9yWZsZ0rUTUa3x9rLzRP-H9NGbK_Yf8j75lpdx2sFUvJyyxZ2KEoq74bS-Zg203Q3jhz_RVJi8M3R0oRQHh2I-wUimdAHQ9JQu-M-VAtVLG6DVrY4vyhdv2JxIPyLrRTJPVhMDEqKqUcbm0VJE8MJOb3C6hdFrrzcM8AAkxGhYHh2vlJu0MAa8y7U0lHm8mcrNqsjPahAFvVMwUPkxaXFSYwJDdnBfODquWHt6reE9GwQw-ZFvigPGmY3SWMZiEBK-2Zkz9NglOKEgnojT6xcAbdaeEDadvY-Qriej8rMBZ3F-2IHYNMM4J2MkbexgloQgS6UWx7Nx1ln-pI-uHCDUmwsb5bl05Jma4_DXMsIjzinc2u-CvpEUXe1KmDddh7NYSrUv5RjaZfxgzjYCW3gSynFRGNfy57PaX7R7VV9pn3IRMKsbdWKlA957M2tDqmgkzC2M8KZAJRFmw_tCVk-swjPjyFmXUgsdLs0oX8OSAh4JX8DWvK42wHerh4gbjX_N7mYWh38e4-jMvyZ5Zdqf-2phj21z9uZla1mCsncjDDPJy7sVCrlJasgPLgtCD5j0lxp3wdAjvx2I4q6CKNcP8-MapHABe2TIgMmEf2VOJv42yrO99q49OwkopbyhMf-0-drAOo7Tw7eKHH7S174LfEKewCMmj2BVaM_I-rTgU28Be8pfbl6jTmj-9WCvuBbBiCtvhpeOApuMbJkc43As4QV031W9DEQpB520DWUdAHjvNkUMo0QDBLm7Zx4xzeO3Ei35Xw8_w9OjZBCZw3kalIbZAL9MlsyfFXczbZXMZnzkccPTwTK2SgS5lq1Ic07YB_uqY52CwYlpjbRP3ygM2mVCLTB921NhXKh9-Md4oJ9ieaDUNyNPch2MLQluKmyIH0sumgBkyn1k17IM4qN3EXRSsUiqa-FBU-PFa1gVIf_dhdnLI_GOPGEYBmZYTrk8J8HK2-wIi5shGDOB-WjBaAWbymdMmPhifRKiDKdOmPVArGptSyrM_5UvHF189Jl0ABxEWcsW6DScgw72GtcXxhDQligdcjanZHNVGEnfwFN604v68G42IC7clOVAno7cgaapHXXYSadEqVzP_Q4d-d-obG0v44yMu3WSdfhmJ8GC-8jS3b16q5_vTfyim5VFmLdG0PX1L9--QYop1Rns-64a5Dz8T8MmyrtgRXBAMfXC84R7N8I7SgRbsSo2pgMzRafCOpYWqfUlNcPjqQXSPj8oEt0iz0DBq9ui1dK0JiiOGOO9uOdswQP2r_cVlF-QvTszcyy1hn6QHCdsfqojSi70jmyhpBa5Vgba7nyP_qbJCtYiZwk7RLWT-jZ47BwZzEK73UXoAYCc8NT5HsIW8KFuManq8Mau7BLNj6WhXJOKmbP0oD88LMj3TeEvZ3","DataProtected":true} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/keys/is-signing-key-B0B34AEB91E2638F61EB9A5543F3940F.json b/src/Services/Identity/Identity.API/keys/is-signing-key-B0B34AEB91E2638F61EB9A5543F3940F.json deleted file mode 100644 index ddcb78b6d..000000000 --- a/src/Services/Identity/Identity.API/keys/is-signing-key-B0B34AEB91E2638F61EB9A5543F3940F.json +++ /dev/null @@ -1 +0,0 @@ -{"Version":1,"Id":"B0B34AEB91E2638F61EB9A5543F3940F","Created":"2022-11-30T11:21:15.586844Z","Algorithm":"RS256","IsX509Certificate":false,"Data":"CfDJ8Fzd_R2KZDdMrJulCrd4GFTnFtf9LeKAfSemG0-9e385u01WZOrLNU5PPCNSRvcQKMERaUvLDcAsYIsqdKPJ14QGDgSTlxggwrR50kCv3494z5uCvWOvWqYc3KKzhR-7e-mXViY4m2oYHpGaVCITKXYwCRYxO-P0mSdr6D7W-846cfdst47_rjIxxeMajL1suYSYe_sz_nz8ooKW8ZH_kl5M9cI0waB2XFviIP3f08pz_eyneKqy7tCc3ruSNiUzfUBAF7fBHDcyPrE2h4AKaXG1PT-I6JOFVj9lAgv_1ACSqTVEjJiRWfFIfft3sQ-chSv1yxy7b8GvhIsKKEn-OqXCnYr5pFW7VPagt7pf9IQdivU7I4m4zSyW0bjIWXdCL3sXQAChiDpvRCFqFfsiH4lWmI4X8Aq8Tz1_LkbicHW5-ZxeZpvgPlg6t77xqFIxFtixqdc1XxifVwJR5GwrFcbcI9VpnlBomkBSUFMslW9NxGwSztgnYatkQuiXTzdqFoOr5d4YGPEHe_Oc3h9yDyUxgBdJK30sSx1Dwki94CeQNBS7PisGbu5q60MbsyFQ2S6kBogReQdURLW326NkgOBJ27jHk3ccx24WfFuuDzquE7xetxvnX4AV0oKOudMClsOv4Wr1aMm98p4VimUQkAneFImQszK1-zSQfxeElamoYy-4h1P2V_A5siSmfKpDs1q2ja3C62lBr0YSx822O-QF4XI0id6GOZiCc_9v52B_teTlgMq6L54EjqDM-h6ERkPvoqNFL0fWz3ktOBPCQm56YrSyyaXNKG2TwL4AMLsaQ9us5SXnJ7UpiY_7ls4SVm_h3btoQcokOiO247mYo4-BRq4pCsFHNBsCReiKlJH8G8fwrujWcu_U7NdVWgmUgmcsIh9mfommuOdBdVQdP8CYZM8od1NBIIifIoj51WuZXtb9Wr4QVIddEjAQrYnexJAozF9TeBVfjhDkIIA2-b8yBGqNl5VrF4bo3b3LLm1rbq5vzfKN6pKzs2sDGple6kZn_t0Ym5fmf8iWXLzMnRyasEDuXX0-Vg9WxiWyXzHsUY-SZEfEXs_WfuzBZb9NP1JvrB_w33cUfgl9Sa6RD56O5F1YAR3WxiCiWGnlSnqpaiiyTb4NVr8N0qQl9xOz9KCSaN8H2WKgCML2DdjW5_O8Zea2EPpSf1NGB1G-Nm-9HWAn8xDAei5x8kzTIxka6Jb8GO6Rdffk7wwuYMoB4ZYuiuL1q-rqvaU5GffQWMjGz-yH-WCivVFCosvJgk9340K0bDl97CULs43LF3nQFcmuLQoW-fasNYk5ZAXYA8bjn9eYm-hWbsfA402c3iZKKZVcLAU-jPAr44Z1P9PbT-xwooGTKKnZuR55l24T3p4hDatpazlObRd27kPtSLgbXPkNKbru9N8--JUhoEeFGC8E01gCn9256wlpIO1hk9a-P87t7zJWdYkl9Uo8Poz_Hfm9Uaml55BWSghfXlB99hULUmpnPwyOVJnRhGOrnr6ys_2l6bCW-gZlWS7oevQ_BuJBcCXVuRYQPbVtH9ZZSKF-S-uczbtfzhJfH29tCNsHzAukVHFiIwHqzhLHNu1gX5COGzYdN57p7DXGkJXGJUipczm2ZAfI9g-Kgvy6MaXScuwaobPobL5AH5WG1CclEb8kg_08rnQsDfvNhmsAJE7dN1GjpA46fP2bJ09T4hfNdmW-IZbeQaDMGc_8RpoADdUY81swXUgrL56UxoCFxqifXtskbzxF5Z1ad4qRYNlqU_zMbxjSZTsSizNja4pmsx1O2NAhvN31bSoA8PWpLLz37Lq-craDpXW6-mIySm3NQWxTLx2hsk4i29-dD6AjUX2aDLEng4JLKLWKLcH_ulkujNP0oXdkXcGikLqFwUKG8_lEWV_ROlXUVlmwNf9WaohIX8caCK2CF_tyEkCxEjZIBzw5xazq7_zNbI66rXfqBHxDKqrdM3UJYBNFMk2LLyEGD4TetGu4RzBDt2r4rXatSXQh9a662LYlCXxlElP8xfH7BudBa9KgV0FQOdHQ4sm9chHiT9JPQX6bHwf_imYHmTyMV3cIN7f7bACLji-5hFf7if8ANbhY7zer9-rQWR9Wy-vYUyUaSWCWRXHQC2eXL0XklBNnv3vK1nFOcaeC5R0GGULfMnBluyClu2Z67OCsAzQqi-_aacxQVN91SNmAfUbVCrPgBTbQY_1u64fEMc8ZcL52xwT0qgCSvBPCNDEJ8YDVVd8L8eLX0hg7rV8SgCnAl5qG74BCdkmhCMLB-OiwGZxZ9s94yGdjX9jA4D70uOgn5t9OhpboX3OoRK40Xged6zCBEhUd9MilD9o-BtEPeAHloKqMrvrVDE4l9R739pjhgOfvXqGIytXD-bKlMZAY7dnJ4vu4GjVvhHfzxTV3zu_nAV8j3n41_knCe2RyCm9j74YZejnRC15ADqItTDmTre8_iS6hTOrVO9FP","DataProtected":true} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/keys/is-signing-key-CFE19FEED1112F0740C7CEAAE490834F.json b/src/Services/Identity/Identity.API/keys/is-signing-key-CFE19FEED1112F0740C7CEAAE490834F.json deleted file mode 100644 index ca414c2b2..000000000 --- a/src/Services/Identity/Identity.API/keys/is-signing-key-CFE19FEED1112F0740C7CEAAE490834F.json +++ /dev/null @@ -1 +0,0 @@ -{"Version":1,"Id":"CFE19FEED1112F0740C7CEAAE490834F","Created":"2022-11-29T07:38:58.7490696Z","Algorithm":"RS256","IsX509Certificate":false,"Data":"CfDJ8PyEV1YRvftHv5uH-VZQ5M7m2ypM-2X7o3A9EvjXNQQsFD50DwqX3UR8e4cDEITQrBowxyPPRXo_bTgEnifJjx1kdzA3q0L-FPJqCOjh6AaRqUg_TlnhvbEgG1HYoBZTAwvhi6g3h86RFPU4juwhOWHcD1vzCgW1vfJ9VFXXq3F-FSfbpdykS5bLn5hUdg3zgNWh7zZXOIFxgNkelR7lGMfF6hquTQMbCRhtDg0-N8O8m7M0JaNsB6_H6v-Z2RlYzH0R0B0Ka2fH3uPnudPrd1RIP4BONzMorJpDFZP9TF0DY9sJhkcHEhKDZlRlwI_Z-e0z8WeW183Gg_HSV23wO4B5R8ldXRSioqWyUuQfoYplUanuNeccblBe7hxQw9fhsanHGgbiuBjuypXarZmlVlbFO2zPKl46Vj8YZTxopOFOi4ALcNlUzGgPG5lwPjDfXEyAp7_ipMIW84UVfwqnPZFE1FASpFx1BPif-huoAaXw2GF4HJ2QVqfxkr_Gn8isrzBOT3fqmeZ1n0HNOc8VxCSdScbBSKFMnxNSgPQSexHGSKnXpvXF0OL6R8oqHoi8TNfYFitGE_qED6wCjQ77qMXOUVXB18kW7nJYqph2u1BH1PykRWaWDlnh3H7fKWRhO0ejaTJWl7oSLllYzi99yL5JvudDCjI0QdfbMz7o3SrdrZbIKQcbTPPgl_cfNneP4QA6MSlJFbG3bc3q8IUPhISrZHO_s7SsoX0BiP-AHcA7M_WrkAQYLkTMsBuO3x6N-bWjtTouqzHY7-h5ZYHqvk7Mg-jlgJjFjgKRDOJFiDorbf-5nvxjl3eLV8rjX7ML9KqjD-ZUjMsDt79lpjCi6B2EFqbiGT35I67hC6a7l8gPjwLKB7Oc8h4YggwyS1eLCq3CgmfMqlIAeAqLL7nVwDPReW-pirHix92q2tSkKCuz5UV5jphmDAnRFQmICaIinoSsBtjb1RlonM-yklDO-jdR5r0ZEFb8ZCssSojhfqEi5mcK3xJsyzM_Inzg71zAHCUl5cuBhbWekBeHiqb2p5ar9guCK_h7zmga1ZZTqfWAGzxHrHkHb6XiK_wLlwBjJEoGhgHfi0UzUECddK2DVwEuTsXhNDJ8CgB2oWNhIHgnlNxL_JA6_bY8BnGtcOKnEuaKpR6rXEZit2Jcr-1-Q8YJ29tVNubphYm2ZMrDTGdAVE8ODftey6BIN_HmBELf6a7x-Cd5DPrW46iVqlOuBqmjxjZ4HHTS08HHGSD4_xj2vNaWl8rIbE4BbDq9V_qyP548KLNbGqSIed38DDcbsRMIuac8po95Z3q6NUf2JR0mCOEizdrszZOW9sGVlO-WReaTkpNN8MaKYoUy6k84IxwPPv-Tov0yMdTN-jp7H2HaTOGDvEb72swcW39huoAbXwjZdrpFQjTD9_cnNrVdclDGN_yix_i8-vQDQlQ9Ronpk2NvAdnrmEzQ6w7D7PqAe5c4YJfn_0z45ERm9zBwCMCbw8lP_Yi3nwTbqGtFqAoXTSZ8vR5_UxQLqxsJy6U6gK4Es68-hnD-YR5p3CzBXxSEgV7_qvW2gly5VCB-1yoD8dTVInogMhOzDZD71gmpJURgKXX9WN92yq-YB24E8koOXdbxTvSblIVTxwDqQ7lb81y68HmZVSRLk3zhxbkEkjI1kkB8uzlArbXljN7LnxyXHU81DDeufVK5_myoIMA5NpSwEKxAkmCoF7p642qBwo90k0q0AvJVmoy8xpCRiJyLDR8wYHFsed3T99jvvvV7GyA4SG5Tt-ST5kKFKvOOwT_WrOg-ao5cz6Br8_YL2xW5dB231R4ZxfJlmrJY9WcuTQMZrWnYOOSj_5u2I4sgcnp7nrRF1l2xnieiym5ef23a-rSMBZi_leN6BAma6N1M7tu1N8cEw7wd_6FV6hqrcKFPGNm4HE5M-pmoWvzjBTo25rh_4atG5uEmDgmhmY6qiZc1eso__Fz29uJ4792kkpdGyPAcV8igzPMdKts1KiO9ThrDRH25Knwxa1tOWwezkeId-OuZRwC4LhGuZseww5TxzQtyN8mMWbLDPCmWya56F-i0jag4i-r2Z26VEmhpSywbACetljEsaVDJF2ZdIrIFJqjYLa9zhcKbD9GZPHZt0HKilXDp8DomCQHHWsIapwCiG6WLGOhVc0-0VJ28dBqIowyCQScefePKIIk7Oa0T4UzPXcXiXYNQktHGs9VrF2ux4De-SbxDRdJy1IRSH8_NQ4SFtUelO8IG27B_8Ywbonn9yRsoojmuS-fXPuWSJMHDjPcSYwJqQyxjfPjGCLsnQWRZCNT5Ft6cfcSDRZWoCqKK2qLfqcGs-FR71HM0G6ipAuVI_2IyfalS-fXqTPCncyFsOqp4BRhhxLFu8UxTZ5-HQyKH3fX75mZCD21d9RObyWv8g2T0HtEBLEf2eR8I8zCUcOf98asCoT2CU4AtOhVwqrE2u1qNKupc5Nvw","DataProtected":true} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/web.config b/src/Services/Identity/Identity.API/web.config deleted file mode 100644 index a2cf1fe26..000000000 --- a/src/Services/Identity/Identity.API/web.config +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Application/Behaviors/LoggingBehavior.cs b/src/Services/Ordering/Ordering.API/Application/Behaviors/LoggingBehavior.cs index 9ed7d1772..838697c8c 100644 --- a/src/Services/Ordering/Ordering.API/Application/Behaviors/LoggingBehavior.cs +++ b/src/Services/Ordering/Ordering.API/Application/Behaviors/LoggingBehavior.cs @@ -6,9 +6,9 @@ public class LoggingBehavior : IPipelineBehavior Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) { - _logger.LogInformation("----- Handling command {CommandName} ({@Command})", request.GetGenericTypeName(), request); + _logger.LogInformation("Handling command {CommandName} ({@Command})", request.GetGenericTypeName(), request); var response = await next(); - _logger.LogInformation("----- Command {CommandName} handled - response: {@Response}", request.GetGenericTypeName(), response); + _logger.LogInformation("Command {CommandName} handled - response: {@Response}", request.GetGenericTypeName(), response); return response; } diff --git a/src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehaviour.cs b/src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehavior.cs similarity index 68% rename from src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehaviour.cs rename to src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehavior.cs index e9deb1cf7..acebd9cda 100644 --- a/src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehaviour.cs +++ b/src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehavior.cs @@ -2,15 +2,15 @@ using Microsoft.Extensions.Logging; -public class TransactionBehaviour : IPipelineBehavior where TRequest : IRequest +public class TransactionBehavior : IPipelineBehavior where TRequest : IRequest { - private readonly ILogger> _logger; + private readonly ILogger> _logger; private readonly OrderingContext _dbContext; private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; - public TransactionBehaviour(OrderingContext dbContext, + public TransactionBehavior(OrderingContext dbContext, IOrderingIntegrationEventService orderingIntegrationEventService, - ILogger> logger) + ILogger> logger) { _dbContext = dbContext ?? throw new ArgumentException(nameof(OrderingContext)); _orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentException(nameof(orderingIntegrationEventService)); @@ -36,13 +36,13 @@ public class TransactionBehaviour : IPipelineBehavior> { new ("TransactionContext", transaction.TransactionId) })) { - _logger.LogInformation("----- Begin transaction {TransactionId} for {CommandName} ({@Command})", transaction.TransactionId, typeName, request); + _logger.LogInformation("Begin transaction {TransactionId} for {CommandName} ({@Command})", transaction.TransactionId, typeName, request); response = await next(); - _logger.LogInformation("----- Commit transaction {TransactionId} for {CommandName}", transaction.TransactionId, typeName); + _logger.LogInformation("Commit transaction {TransactionId} for {CommandName}", transaction.TransactionId, typeName); await _dbContext.CommitTransactionAsync(transaction); @@ -56,7 +56,7 @@ public class TransactionBehaviour : IPipelineBehavior : IPipelineBehavior v.Validate(request)) diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/CancelOrderCommandHandler.cs b/src/Services/Ordering/Ordering.API/Application/Commands/CancelOrderCommandHandler.cs index 74535c3f8..024f5ade4 100644 --- a/src/Services/Ordering/Ordering.API/Application/Commands/CancelOrderCommandHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/Commands/CancelOrderCommandHandler.cs @@ -43,6 +43,6 @@ public class CancelOrderIdentifiedCommandHandler : IdentifiedCommandHandler { - public string BuyerId { get; private set; } public IEnumerable Items { get; private set; } diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderDraftCommandHandler.cs b/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderDraftCommandHandler.cs index be3860d5c..aaf982c29 100644 --- a/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderDraftCommandHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderDraftCommandHandler.cs @@ -1,6 +1,6 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; -using static Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands.CreateOrderCommand; +using Microsoft.eShopOnContainers.Services.Ordering.API.Extensions; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; // Regular CommandHandler @@ -42,5 +42,19 @@ public record OrderDraftDTO Total = order.GetTotal() }; } +} + +public record OrderItemDTO +{ + public int ProductId { get; init; } + + public string ProductName { get; init; } -} \ No newline at end of file + public decimal UnitPrice { get; init; } + + public decimal Discount { get; init; } + + public int Units { get; init; } + + public string PictureUrl { get; init; } +} diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/IdentifiedCommandHandler.cs b/src/Services/Ordering/Ordering.API/Application/Commands/IdentifiedCommandHandler.cs index 38c8a356d..0852bdc10 100644 --- a/src/Services/Ordering/Ordering.API/Application/Commands/IdentifiedCommandHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/Commands/IdentifiedCommandHandler.cs @@ -6,7 +6,7 @@ /// /// Type of the command handler that performs the operation if request is not duplicated /// Return value of the inner command handler -public class IdentifiedCommandHandler : IRequestHandler, R> +public abstract class IdentifiedCommandHandler : IRequestHandler, R> where T : IRequest { private readonly IMediator _mediator; @@ -18,19 +18,17 @@ public class IdentifiedCommandHandler : IRequestHandler> logger) { + ArgumentNullException.ThrowIfNull(logger); _mediator = mediator; _requestManager = requestManager; - _logger = logger ?? throw new System.ArgumentNullException(nameof(logger)); + _logger = logger; } /// /// Creates the result value to return if a previous request was found /// /// - protected virtual R CreateResultForDuplicateRequest() - { - return default(R); - } + protected abstract R CreateResultForDuplicateRequest(); /// /// This method handles the command. It just ensures that no other request exists with the same ID, and if this is the case @@ -79,7 +77,7 @@ public class IdentifiedCommandHandler : IRequestHandler : IRequestHandler : IRequestHandler +public class SetStockConfirmedOrderStatusIdentifiedCommandHandler : IdentifiedCommandHandler { - public SetStockConfirmedOrderStatusIdenfifiedCommandHandler( + public SetStockConfirmedOrderStatusIdentifiedCommandHandler( IMediator mediator, IRequestManager requestManager, ILogger> logger) @@ -46,6 +46,6 @@ public class SetStockConfirmedOrderStatusIdenfifiedCommandHandler : IdentifiedCo protected override bool CreateResultForDuplicateRequest() { - return true; // Ignore duplicate requests for processing order. + return true; // Ignore duplicate requests for processing order. } } diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/SetStockRejectedOrderStatusCommandHandler.cs b/src/Services/Ordering/Ordering.API/Application/Commands/SetStockRejectedOrderStatusCommandHandler.cs index fcef729e6..72a2a27fe 100644 --- a/src/Services/Ordering/Ordering.API/Application/Commands/SetStockRejectedOrderStatusCommandHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/Commands/SetStockRejectedOrderStatusCommandHandler.cs @@ -35,9 +35,9 @@ public class SetStockRejectedOrderStatusCommandHandler : IRequestHandler +public class SetStockRejectedOrderStatusIdentifiedCommandHandler : IdentifiedCommandHandler { - public SetStockRejectedOrderStatusIdenfifiedCommandHandler( + public SetStockRejectedOrderStatusIdentifiedCommandHandler( IMediator mediator, IRequestManager requestManager, ILogger> logger) @@ -47,6 +47,6 @@ public class SetStockRejectedOrderStatusIdenfifiedCommandHandler : IdentifiedCom protected override bool CreateResultForDuplicateRequest() { - return true; // Ignore duplicate requests for processing order. + return true; // Ignore duplicate requests for processing order. } } diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/ShipOrderCommandHandler.cs b/src/Services/Ordering/Ordering.API/Application/Commands/ShipOrderCommandHandler.cs index 58b09f53b..8887aaadf 100644 --- a/src/Services/Ordering/Ordering.API/Application/Commands/ShipOrderCommandHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/Commands/ShipOrderCommandHandler.cs @@ -43,6 +43,6 @@ public class ShipOrderIdentifiedCommandHandler : IdentifiedCommandHandler -{ - private readonly IOrderRepository _orderRepository; - private readonly ILoggerFactory _logger; - - public UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler( - IOrderRepository orderRepository, ILoggerFactory logger) - { - _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - // Domain Logic comment: - // When the Buyer and Buyer's payment method have been created or verified that they existed, - // then we can update the original Order with the BuyerId and PaymentId (foreign keys) - public async Task Handle(BuyerAndPaymentMethodVerifiedDomainEvent buyerPaymentMethodVerifiedEvent, CancellationToken cancellationToken) - { - var orderToUpdate = await _orderRepository.GetAsync(buyerPaymentMethodVerifiedEvent.OrderId); - orderToUpdate.SetBuyerId(buyerPaymentMethodVerifiedEvent.Buyer.Id); - orderToUpdate.SetPaymentId(buyerPaymentMethodVerifiedEvent.Payment.Id); - - _logger.CreateLogger() - .LogTrace("Order with Id: {OrderId} has been successfully updated with a payment method {PaymentMethod} ({Id})", - buyerPaymentMethodVerifiedEvent.OrderId, nameof(buyerPaymentMethodVerifiedEvent.Payment), buyerPaymentMethodVerifiedEvent.Payment.Id); - } -} diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCancelled/OrderCancelledDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCancelledDomainEventHandler.cs similarity index 55% rename from src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCancelled/OrderCancelledDomainEventHandler.cs rename to src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCancelledDomainEventHandler.cs index dd954c2ff..dc9944c83 100644 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCancelled/OrderCancelledDomainEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCancelledDomainEventHandler.cs @@ -1,16 +1,16 @@ -namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.DomainEventHandlers.OrderCancelled; +namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.DomainEventHandlers; -public class OrderCancelledDomainEventHandler +public partial class OrderCancelledDomainEventHandler : INotificationHandler { private readonly IOrderRepository _orderRepository; private readonly IBuyerRepository _buyerRepository; - private readonly ILoggerFactory _logger; + private readonly ILogger _logger; private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; public OrderCancelledDomainEventHandler( IOrderRepository orderRepository, - ILoggerFactory logger, + ILogger logger, IBuyerRepository buyerRepository, IOrderingIntegrationEventService orderingIntegrationEventService) { @@ -20,16 +20,14 @@ public class OrderCancelledDomainEventHandler _orderingIntegrationEventService = orderingIntegrationEventService; } - public async Task Handle(OrderCancelledDomainEvent orderCancelledDomainEvent, CancellationToken cancellationToken) + public async Task Handle(OrderCancelledDomainEvent domainEvent, CancellationToken cancellationToken) { - _logger.CreateLogger() - .LogTrace("Order with Id: {OrderId} has been successfully updated to status {Status} ({Id})", - orderCancelledDomainEvent.Order.Id, nameof(OrderStatus.Cancelled), OrderStatus.Cancelled.Id); + OrderingApiTrace.LogOrderStatusUpdated(_logger, domainEvent.Order.Id, nameof(OrderStatus.Cancelled), OrderStatus.Cancelled.Id); - var order = await _orderRepository.GetAsync(orderCancelledDomainEvent.Order.Id); + var order = await _orderRepository.GetAsync(domainEvent.Order.Id); var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString()); - var orderStatusChangedToCancelledIntegrationEvent = new OrderStatusChangedToCancelledIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name); - await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToCancelledIntegrationEvent); + var integrationEvent = new OrderStatusChangedToCancelledIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name); + await _orderingIntegrationEventService.AddAndSaveEventAsync(integrationEvent); } } diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderShipped/OrderShippedDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderShippedDomainEventHandler.cs similarity index 58% rename from src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderShipped/OrderShippedDomainEventHandler.cs rename to src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderShippedDomainEventHandler.cs index 445c7e04f..3bdb035e7 100644 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderShipped/OrderShippedDomainEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderShippedDomainEventHandler.cs @@ -1,4 +1,4 @@ -namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.DomainEventHandlers.OrderShipped; +namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.DomainEventHandlers; public class OrderShippedDomainEventHandler : INotificationHandler @@ -6,11 +6,11 @@ public class OrderShippedDomainEventHandler private readonly IOrderRepository _orderRepository; private readonly IBuyerRepository _buyerRepository; private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; - private readonly ILoggerFactory _logger; + private readonly ILogger _logger; public OrderShippedDomainEventHandler( IOrderRepository orderRepository, - ILoggerFactory logger, + ILogger logger, IBuyerRepository buyerRepository, IOrderingIntegrationEventService orderingIntegrationEventService) { @@ -20,16 +20,14 @@ public class OrderShippedDomainEventHandler _orderingIntegrationEventService = orderingIntegrationEventService; } - public async Task Handle(OrderShippedDomainEvent orderShippedDomainEvent, CancellationToken cancellationToken) + public async Task Handle(OrderShippedDomainEvent domainEvent, CancellationToken cancellationToken) { - _logger.CreateLogger() - .LogTrace("Order with Id: {OrderId} has been successfully updated to status {Status} ({Id})", - orderShippedDomainEvent.Order.Id, nameof(OrderStatus.Shipped), OrderStatus.Shipped.Id); + OrderingApiTrace.LogOrderStatusUpdated(_logger, domainEvent.Order.Id, nameof(OrderStatus.Shipped), OrderStatus.Shipped.Id); - var order = await _orderRepository.GetAsync(orderShippedDomainEvent.Order.Id); + var order = await _orderRepository.GetAsync(domainEvent.Order.Id); var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString()); - var orderStatusChangedToShippedIntegrationEvent = new OrderStatusChangedToShippedIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name); - await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToShippedIntegrationEvent); + var integrationEvent = new OrderStatusChangedToShippedIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name); + await _orderingIntegrationEventService.AddAndSaveEventAsync(integrationEvent); } } diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/SendEmailToCustomerWhenOrderStartedDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/SendEmailToCustomerWhenOrderStartedDomainEventHandler.cs deleted file mode 100644 index 720b656d6..000000000 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/SendEmailToCustomerWhenOrderStartedDomainEventHandler.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.DomainEventHandlers.OrderStartedEvent; - -public class SendEmailToCustomerWhenOrderStartedDomainEventHandler -//: IAsyncNotificationHandler -{ - public SendEmailToCustomerWhenOrderStartedDomainEventHandler() - { - - } - - //public async Task Handle(OrderStartedDomainEvent orderNotification) - //{ - // //TBD - Send email logic - //} -} diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs deleted file mode 100644 index da5037259..000000000 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs +++ /dev/null @@ -1,55 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.DomainEventHandlers.OrderStartedEvent; - -public class ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler - : INotificationHandler -{ - private readonly ILoggerFactory _logger; - private readonly IBuyerRepository _buyerRepository; - private readonly IIdentityService _identityService; - private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; - - public ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler( - ILoggerFactory logger, - IBuyerRepository buyerRepository, - IIdentityService identityService, - IOrderingIntegrationEventService orderingIntegrationEventService) - { - _buyerRepository = buyerRepository ?? throw new ArgumentNullException(nameof(buyerRepository)); - _identityService = identityService ?? throw new ArgumentNullException(nameof(identityService)); - _orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentNullException(nameof(orderingIntegrationEventService)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public async Task Handle(OrderStartedDomainEvent orderStartedEvent, CancellationToken cancellationToken) - { - var cardTypeId = (orderStartedEvent.CardTypeId != 0) ? orderStartedEvent.CardTypeId : 1; - var buyer = await _buyerRepository.FindAsync(orderStartedEvent.UserId); - bool buyerOriginallyExisted = (buyer == null) ? false : true; - - if (!buyerOriginallyExisted) - { - buyer = new Buyer(orderStartedEvent.UserId, orderStartedEvent.UserName); - } - - buyer.VerifyOrAddPaymentMethod(cardTypeId, - $"Payment Method on {DateTime.UtcNow}", - orderStartedEvent.CardNumber, - orderStartedEvent.CardSecurityNumber, - orderStartedEvent.CardHolderName, - orderStartedEvent.CardExpiration, - orderStartedEvent.Order.Id); - - var buyerUpdated = buyerOriginallyExisted ? - _buyerRepository.Update(buyer) : - _buyerRepository.Add(buyer); - - await _buyerRepository.UnitOfWork - .SaveEntitiesAsync(cancellationToken); - - var orderStatusChangedToSubmittedIntegrationEvent = new OrderStatusChangedToSubmittedIntegrationEvent(orderStartedEvent.Order.Id, orderStartedEvent.Order.OrderStatus.Name, buyer.Name); - await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToSubmittedIntegrationEvent); - _logger.CreateLogger() - .LogTrace("Buyer {BuyerId} and related payment method were validated or updated for orderId: {OrderId}.", - buyerUpdated.Id, orderStartedEvent.Order.Id); - } -} diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderGracePeriodConfirmed/OrderStatusChangedToAwaitingValidationDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStatusChangedToAwaitingValidationDomainEventHandler.cs similarity index 53% rename from src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderGracePeriodConfirmed/OrderStatusChangedToAwaitingValidationDomainEventHandler.cs rename to src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStatusChangedToAwaitingValidationDomainEventHandler.cs index 56ae38bba..4b8553f50 100644 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderGracePeriodConfirmed/OrderStatusChangedToAwaitingValidationDomainEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStatusChangedToAwaitingValidationDomainEventHandler.cs @@ -1,15 +1,16 @@ -namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.DomainEventHandlers.OrderGracePeriodConfirmed; +namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.DomainEventHandlers; public class OrderStatusChangedToAwaitingValidationDomainEventHandler : INotificationHandler { private readonly IOrderRepository _orderRepository; - private readonly ILoggerFactory _logger; + private readonly ILogger _logger; private readonly IBuyerRepository _buyerRepository; private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; public OrderStatusChangedToAwaitingValidationDomainEventHandler( - IOrderRepository orderRepository, ILoggerFactory logger, + IOrderRepository orderRepository, + ILogger logger, IBuyerRepository buyerRepository, IOrderingIntegrationEventService orderingIntegrationEventService) { @@ -19,21 +20,17 @@ public class OrderStatusChangedToAwaitingValidationDomainEventHandler _orderingIntegrationEventService = orderingIntegrationEventService; } - public async Task Handle(OrderStatusChangedToAwaitingValidationDomainEvent orderStatusChangedToAwaitingValidationDomainEvent, CancellationToken cancellationToken) + public async Task Handle(OrderStatusChangedToAwaitingValidationDomainEvent domainEvent, CancellationToken cancellationToken) { - _logger.CreateLogger() - .LogTrace("Order with Id: {OrderId} has been successfully updated to status {Status} ({Id})", - orderStatusChangedToAwaitingValidationDomainEvent.OrderId, nameof(OrderStatus.AwaitingValidation), OrderStatus.AwaitingValidation.Id); - - var order = await _orderRepository.GetAsync(orderStatusChangedToAwaitingValidationDomainEvent.OrderId); + OrderingApiTrace.LogOrderStatusUpdated(_logger, domainEvent.OrderId, nameof(OrderStatus.AwaitingValidation), OrderStatus.AwaitingValidation.Id); + var order = await _orderRepository.GetAsync(domainEvent.OrderId); var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString()); - var orderStockList = orderStatusChangedToAwaitingValidationDomainEvent.OrderItems + var orderStockList = domainEvent.OrderItems .Select(orderItem => new OrderStockItem(orderItem.ProductId, orderItem.GetUnits())); - var orderStatusChangedToAwaitingValidationIntegrationEvent = new OrderStatusChangedToAwaitingValidationIntegrationEvent( - order.Id, order.OrderStatus.Name, buyer.Name, orderStockList); - await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToAwaitingValidationIntegrationEvent); + var integrationEvent = new OrderStatusChangedToAwaitingValidationIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name, orderStockList); + await _orderingIntegrationEventService.AddAndSaveEventAsync(integrationEvent); } } diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderPaid/OrderStatusChangedToPaidDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStatusChangedToPaidDomainEventHandler.cs similarity index 55% rename from src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderPaid/OrderStatusChangedToPaidDomainEventHandler.cs rename to src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStatusChangedToPaidDomainEventHandler.cs index 2f2cf8e38..761313a4d 100644 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderPaid/OrderStatusChangedToPaidDomainEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStatusChangedToPaidDomainEventHandler.cs @@ -1,19 +1,17 @@ -namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.DomainEventHandlers.OrderPaid; +namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.DomainEventHandlers; -public class OrderStatusChangedToPaidDomainEventHandler - : INotificationHandler +public class OrderStatusChangedToPaidDomainEventHandler : INotificationHandler { private readonly IOrderRepository _orderRepository; - private readonly ILoggerFactory _logger; + private readonly ILogger _logger; private readonly IBuyerRepository _buyerRepository; private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; - public OrderStatusChangedToPaidDomainEventHandler( - IOrderRepository orderRepository, ILoggerFactory logger, + IOrderRepository orderRepository, + ILogger logger, IBuyerRepository buyerRepository, - IOrderingIntegrationEventService orderingIntegrationEventService - ) + IOrderingIntegrationEventService orderingIntegrationEventService) { _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); @@ -21,24 +19,22 @@ public class OrderStatusChangedToPaidDomainEventHandler _orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentNullException(nameof(orderingIntegrationEventService)); } - public async Task Handle(OrderStatusChangedToPaidDomainEvent orderStatusChangedToPaidDomainEvent, CancellationToken cancellationToken) + public async Task Handle(OrderStatusChangedToPaidDomainEvent domainEvent, CancellationToken cancellationToken) { - _logger.CreateLogger() - .LogTrace("Order with Id: {OrderId} has been successfully updated to status {Status} ({Id})", - orderStatusChangedToPaidDomainEvent.OrderId, nameof(OrderStatus.Paid), OrderStatus.Paid.Id); + OrderingApiTrace.LogOrderStatusUpdated(_logger, domainEvent.OrderId, nameof(OrderStatus.Paid), OrderStatus.Paid.Id); - var order = await _orderRepository.GetAsync(orderStatusChangedToPaidDomainEvent.OrderId); + var order = await _orderRepository.GetAsync(domainEvent.OrderId); var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString()); - var orderStockList = orderStatusChangedToPaidDomainEvent.OrderItems + var orderStockList = domainEvent.OrderItems .Select(orderItem => new OrderStockItem(orderItem.ProductId, orderItem.GetUnits())); - var orderStatusChangedToPaidIntegrationEvent = new OrderStatusChangedToPaidIntegrationEvent( - orderStatusChangedToPaidDomainEvent.OrderId, + var integrationEvent = new OrderStatusChangedToPaidIntegrationEvent( + domainEvent.OrderId, order.OrderStatus.Name, buyer.Name, orderStockList); - await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToPaidIntegrationEvent); + await _orderingIntegrationEventService.AddAndSaveEventAsync(integrationEvent); } } diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStockConfirmed/OrderStatusChangedToStockConfirmedDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStatusChangedToStockConfirmedDomainEventHandler.cs similarity index 58% rename from src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStockConfirmed/OrderStatusChangedToStockConfirmedDomainEventHandler.cs rename to src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStatusChangedToStockConfirmedDomainEventHandler.cs index 142b6449d..26aae4736 100644 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStockConfirmed/OrderStatusChangedToStockConfirmedDomainEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStatusChangedToStockConfirmedDomainEventHandler.cs @@ -1,17 +1,17 @@ -namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.DomainEventHandlers.OrderStockConfirmed; +namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.DomainEventHandlers; public class OrderStatusChangedToStockConfirmedDomainEventHandler : INotificationHandler { private readonly IOrderRepository _orderRepository; private readonly IBuyerRepository _buyerRepository; - private readonly ILoggerFactory _logger; + private readonly ILogger _logger; private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; public OrderStatusChangedToStockConfirmedDomainEventHandler( IOrderRepository orderRepository, IBuyerRepository buyerRepository, - ILoggerFactory logger, + ILogger logger, IOrderingIntegrationEventService orderingIntegrationEventService) { _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); @@ -20,16 +20,14 @@ public class OrderStatusChangedToStockConfirmedDomainEventHandler _orderingIntegrationEventService = orderingIntegrationEventService; } - public async Task Handle(OrderStatusChangedToStockConfirmedDomainEvent orderStatusChangedToStockConfirmedDomainEvent, CancellationToken cancellationToken) + public async Task Handle(OrderStatusChangedToStockConfirmedDomainEvent domainEvent, CancellationToken cancellationToken) { - _logger.CreateLogger() - .LogTrace("Order with Id: {OrderId} has been successfully updated to status {Status} ({Id})", - orderStatusChangedToStockConfirmedDomainEvent.OrderId, nameof(OrderStatus.StockConfirmed), OrderStatus.StockConfirmed.Id); + OrderingApiTrace.LogOrderStatusUpdated(_logger, domainEvent.OrderId, nameof(OrderStatus.StockConfirmed), OrderStatus.StockConfirmed.Id); - var order = await _orderRepository.GetAsync(orderStatusChangedToStockConfirmedDomainEvent.OrderId); + var order = await _orderRepository.GetAsync(domainEvent.OrderId); var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString()); - var orderStatusChangedToStockConfirmedIntegrationEvent = new OrderStatusChangedToStockConfirmedIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name); - await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToStockConfirmedIntegrationEvent); + var integrationEvent = new OrderStatusChangedToStockConfirmedIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name); + await _orderingIntegrationEventService.AddAndSaveEventAsync(integrationEvent); } } diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler.cs new file mode 100644 index 000000000..5c14ac65c --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler.cs @@ -0,0 +1,26 @@ +namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.DomainEventHandlers; + +public class UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler : INotificationHandler +{ + private readonly IOrderRepository _orderRepository; + private readonly ILogger _logger; + + public UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler( + IOrderRepository orderRepository, + ILogger logger) + { + _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + // Domain Logic comment: + // When the Buyer and Buyer's payment method have been created or verified that they existed, + // then we can update the original Order with the BuyerId and PaymentId (foreign keys) + public async Task Handle(BuyerAndPaymentMethodVerifiedDomainEvent domainEvent, CancellationToken cancellationToken) + { + var orderToUpdate = await _orderRepository.GetAsync(domainEvent.OrderId); + orderToUpdate.SetBuyerId(domainEvent.Buyer.Id); + orderToUpdate.SetPaymentId(domainEvent.Payment.Id); + OrderingApiTrace.LogOrderPaymentMethodUpdated(_logger, domainEvent.OrderId, nameof(domainEvent.Payment), domainEvent.Payment.Id); + } +} diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs new file mode 100644 index 000000000..a613e3794 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs @@ -0,0 +1,50 @@ +namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.DomainEventHandlers; + +public class ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler + : INotificationHandler +{ + private readonly ILogger _logger; + private readonly IBuyerRepository _buyerRepository; + private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; + + public ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler( + ILogger logger, + IBuyerRepository buyerRepository, + IOrderingIntegrationEventService orderingIntegrationEventService) + { + _buyerRepository = buyerRepository ?? throw new ArgumentNullException(nameof(buyerRepository)); + _orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentNullException(nameof(orderingIntegrationEventService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public async Task Handle(OrderStartedDomainEvent domainEvent, CancellationToken cancellationToken) + { + var cardTypeId = domainEvent.CardTypeId != 0 ? domainEvent.CardTypeId : 1; + var buyer = await _buyerRepository.FindAsync(domainEvent.UserId); + var buyerExisted = buyer is not null; + + if (!buyerExisted) + { + buyer = new Buyer(domainEvent.UserId, domainEvent.UserName); + } + + buyer.VerifyOrAddPaymentMethod(cardTypeId, + $"Payment Method on {DateTime.UtcNow}", + domainEvent.CardNumber, + domainEvent.CardSecurityNumber, + domainEvent.CardHolderName, + domainEvent.CardExpiration, + domainEvent.Order.Id); + + var buyerUpdated = buyerExisted ? + _buyerRepository.Update(buyer) : + _buyerRepository.Add(buyer); + + await _buyerRepository.UnitOfWork + .SaveEntitiesAsync(cancellationToken); + + var integrationEvent = new OrderStatusChangedToSubmittedIntegrationEvent(domainEvent.Order.Id, domainEvent.Order.OrderStatus.Name, buyer.Name); + await _orderingIntegrationEventService.AddAndSaveEventAsync(integrationEvent); + OrderingApiTrace.LogOrderBuyerAndPaymentValidatedOrUpdated(_logger, buyerUpdated.Id, domainEvent.Order.Id); + } +} diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/GracePeriodConfirmedIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/GracePeriodConfirmedIntegrationEventHandler.cs index 55a8a39b9..fdab02458 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/GracePeriodConfirmedIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/GracePeriodConfirmedIntegrationEventHandler.cs @@ -23,14 +23,14 @@ public class GracePeriodConfirmedIntegrationEventHandler : IIntegrationEventHand /// public async Task Handle(GracePeriodConfirmedIntegrationEvent @event) { - using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + using (_logger.BeginScope(new List> { new ("IntegrationEventContext", @event.Id) })) { - _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + _logger.LogInformation("Handling integration event: {IntegrationEventId} - ({@IntegrationEvent})", @event.Id, @event); var command = new SetAwaitingValidationOrderStatusCommand(@event.OrderId); _logger.LogInformation( - "----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", + "Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", command.GetGenericTypeName(), nameof(command.OrderNumber), command.OrderNumber, diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentFailedIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentFailedIntegrationEventHandler.cs index 4170ac18f..6982a5a25 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentFailedIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentFailedIntegrationEventHandler.cs @@ -16,14 +16,14 @@ public class OrderPaymentFailedIntegrationEventHandler : public async Task Handle(OrderPaymentFailedIntegrationEvent @event) { - using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + using (_logger.BeginScope(new List> { new ("IntegrationEventContext", @event.Id) })) { - _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + _logger.LogInformation("Handling integration event: {IntegrationEventId} - ({@IntegrationEvent})", @event.Id, @event); var command = new CancelOrderCommand(@event.OrderId); _logger.LogInformation( - "----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", + "Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", command.GetGenericTypeName(), nameof(command.OrderNumber), command.OrderNumber, diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentSucceededIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentSucceededIntegrationEventHandler.cs index af9817696..31606ead2 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentSucceededIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentSucceededIntegrationEventHandler.cs @@ -16,14 +16,14 @@ public class OrderPaymentSucceededIntegrationEventHandler : public async Task Handle(OrderPaymentSucceededIntegrationEvent @event) { - using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + using (_logger.BeginScope(new List> { new ("IntegrationEventContext", @event.Id) })) { - _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + _logger.LogInformation("Handling integration event: {IntegrationEventId} - ({@IntegrationEvent})", @event.Id, @event); var command = new SetPaidOrderStatusCommand(@event.OrderId); _logger.LogInformation( - "----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", + "Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", command.GetGenericTypeName(), nameof(command.OrderNumber), command.OrderNumber, diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockConfirmedIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockConfirmedIntegrationEventHandler.cs index 0c9557a5b..a9faea465 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockConfirmedIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockConfirmedIntegrationEventHandler.cs @@ -1,4 +1,4 @@ -namespace Ordering.API.Application.IntegrationEvents.EventHandling; +namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.IntegrationEvents.EventHandling; public class OrderStockConfirmedIntegrationEventHandler : IIntegrationEventHandler @@ -16,14 +16,14 @@ public class OrderStockConfirmedIntegrationEventHandler : public async Task Handle(OrderStockConfirmedIntegrationEvent @event) { - using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + using (_logger.BeginScope(new List> { new ("IntegrationEventContext", @event.Id) })) { - _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + _logger.LogInformation("Handling integration event: {IntegrationEventId} - ({@IntegrationEvent})", @event.Id, @event); var command = new SetStockConfirmedOrderStatusCommand(@event.OrderId); _logger.LogInformation( - "----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", + "Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", command.GetGenericTypeName(), nameof(command.OrderNumber), command.OrderNumber, diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockRejectedIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockRejectedIntegrationEventHandler.cs index 4fa61b9b5..0d47e7a84 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockRejectedIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockRejectedIntegrationEventHandler.cs @@ -14,9 +14,9 @@ public class OrderStockRejectedIntegrationEventHandler : IIntegrationEventHandle public async Task Handle(OrderStockRejectedIntegrationEvent @event) { - using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + using (_logger.BeginScope(new List> { new ("IntegrationEventContext", @event.Id) })) { - _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + _logger.LogInformation("Handling integration event: {IntegrationEventId} - ({@IntegrationEvent})", @event.Id, @event); var orderStockRejectedItems = @event.OrderStockItems .FindAll(c => !c.HasStock) @@ -26,7 +26,7 @@ public class OrderStockRejectedIntegrationEventHandler : IIntegrationEventHandle var command = new SetStockRejectedOrderStatusCommand(@event.OrderId, orderStockRejectedItems); _logger.LogInformation( - "----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", + "Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", command.GetGenericTypeName(), nameof(command.OrderNumber), command.OrderNumber, diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs index a5a15c06c..731bc986f 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs @@ -24,15 +24,15 @@ public class UserCheckoutAcceptedIntegrationEventHandler : IIntegrationEventHand /// public async Task Handle(UserCheckoutAcceptedIntegrationEvent @event) { - using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + using (_logger.BeginScope(new List> { new ("IntegrationEventContext", @event.Id) })) { - _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + _logger.LogInformation("Handling integration event: {IntegrationEventId} - ({@IntegrationEvent})", @event.Id, @event); var result = false; if (@event.RequestId != Guid.Empty) { - using (LogContext.PushProperty("IdentifiedCommandId", @event.RequestId)) + using (_logger.BeginScope(new List> { new ("IdentifiedCommandId", @event.RequestId) })) { var createOrderCommand = new CreateOrderCommand(@event.Basket.Items, @event.UserId, @event.UserName, @event.City, @event.Street, @event.State, @event.Country, @event.ZipCode, @@ -42,7 +42,7 @@ public class UserCheckoutAcceptedIntegrationEventHandler : IIntegrationEventHand var requestCreateOrder = new IdentifiedCommand(createOrderCommand, @event.RequestId); _logger.LogInformation( - "----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", + "Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", requestCreateOrder.GetGenericTypeName(), nameof(requestCreateOrder.Id), requestCreateOrder.Id, @@ -52,7 +52,7 @@ public class UserCheckoutAcceptedIntegrationEventHandler : IIntegrationEventHand if (result) { - _logger.LogInformation("----- CreateOrderCommand suceeded - RequestId: {RequestId}", @event.RequestId); + _logger.LogInformation("CreateOrderCommand suceeded - RequestId: {RequestId}", @event.RequestId); } else { diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs index e2545cae5..29e9a7ab4 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs @@ -27,7 +27,7 @@ public class OrderingIntegrationEventService : IOrderingIntegrationEventService foreach (var logEvt in pendingLogEvents) { - _logger.LogInformation("----- Publishing integration event: {IntegrationEventId} from {AppName} - ({@IntegrationEvent})", logEvt.EventId, Program.AppName, logEvt.IntegrationEvent); + _logger.LogInformation("Publishing integration event: {IntegrationEventId} - ({@IntegrationEvent})", logEvt.EventId, logEvt.IntegrationEvent); try { @@ -37,7 +37,7 @@ public class OrderingIntegrationEventService : IOrderingIntegrationEventService } catch (Exception ex) { - _logger.LogError(ex, "ERROR publishing integration event: {IntegrationEventId} from {AppName}", logEvt.EventId, Program.AppName); + _logger.LogError(ex, "Error publishing integration event: {IntegrationEventId}", logEvt.EventId); await _eventLogService.MarkEventAsFailedAsync(logEvt.EventId); } @@ -46,7 +46,7 @@ public class OrderingIntegrationEventService : IOrderingIntegrationEventService public async Task AddAndSaveEventAsync(IntegrationEvent evt) { - _logger.LogInformation("----- Enqueuing integration event {IntegrationEventId} to repository ({@IntegrationEvent})", evt.Id, evt); + _logger.LogInformation("Enqueuing integration event {IntegrationEventId} to repository ({@IntegrationEvent})", evt.Id, evt); await _eventLogService.SaveEventAsync(evt, _orderingContext.GetCurrentTransaction()); } diff --git a/src/Services/Ordering/Ordering.API/Application/Validations/CancelOrderCommandValidator.cs b/src/Services/Ordering/Ordering.API/Application/Validations/CancelOrderCommandValidator.cs index 7d901b1fe..80fbcfcc4 100644 --- a/src/Services/Ordering/Ordering.API/Application/Validations/CancelOrderCommandValidator.cs +++ b/src/Services/Ordering/Ordering.API/Application/Validations/CancelOrderCommandValidator.cs @@ -6,6 +6,6 @@ public class CancelOrderCommandValidator : AbstractValidator { RuleFor(order => order.OrderNumber).NotEmpty().WithMessage("No orderId found"); - logger.LogTrace("----- INSTANCE CREATED - {ClassName}", GetType().Name); + logger.LogTrace("INSTANCE CREATED - {ClassName}", GetType().Name); } } diff --git a/src/Services/Ordering/Ordering.API/Application/Validations/CreateOrderCommandValidator.cs b/src/Services/Ordering/Ordering.API/Application/Validations/CreateOrderCommandValidator.cs index eb61082e7..51953b25d 100644 --- a/src/Services/Ordering/Ordering.API/Application/Validations/CreateOrderCommandValidator.cs +++ b/src/Services/Ordering/Ordering.API/Application/Validations/CreateOrderCommandValidator.cs @@ -18,7 +18,7 @@ public class CreateOrderCommandValidator : AbstractValidator RuleFor(command => command.CardTypeId).NotEmpty(); RuleFor(command => command.OrderItems).Must(ContainOrderItems).WithMessage("No order items found"); - logger.LogTrace("----- INSTANCE CREATED - {ClassName}", GetType().Name); + logger.LogTrace("INSTANCE CREATED - {ClassName}", GetType().Name); } private bool BeValidExpirationDate(DateTime dateTime) diff --git a/src/Services/Ordering/Ordering.API/Application/Validations/IdentifiedCommandValidator.cs b/src/Services/Ordering/Ordering.API/Application/Validations/IdentifiedCommandValidator.cs index bde2d771f..4d98fc14c 100644 --- a/src/Services/Ordering/Ordering.API/Application/Validations/IdentifiedCommandValidator.cs +++ b/src/Services/Ordering/Ordering.API/Application/Validations/IdentifiedCommandValidator.cs @@ -6,6 +6,6 @@ public class IdentifiedCommandValidator : AbstractValidator command.Id).NotEmpty(); - logger.LogTrace("----- INSTANCE CREATED - {ClassName}", GetType().Name); + logger.LogTrace("INSTANCE CREATED - {ClassName}", GetType().Name); } } diff --git a/src/Services/Ordering/Ordering.API/Application/Validations/ShipOrderCommandValidator.cs b/src/Services/Ordering/Ordering.API/Application/Validations/ShipOrderCommandValidator.cs index 0341c6028..dea55a4e3 100644 --- a/src/Services/Ordering/Ordering.API/Application/Validations/ShipOrderCommandValidator.cs +++ b/src/Services/Ordering/Ordering.API/Application/Validations/ShipOrderCommandValidator.cs @@ -6,6 +6,6 @@ public class ShipOrderCommandValidator : AbstractValidator { RuleFor(order => order.OrderNumber).NotEmpty().WithMessage("No orderId found"); - logger.LogTrace("----- INSTANCE CREATED - {ClassName}", GetType().Name); + logger.LogTrace("INSTANCE CREATED - {ClassName}", GetType().Name); } } diff --git a/src/Services/Ordering/Ordering.API/Controllers/HomeController.cs b/src/Services/Ordering/Ordering.API/Controllers/HomeController.cs deleted file mode 100644 index 601b7ab6d..000000000 --- a/src/Services/Ordering/Ordering.API/Controllers/HomeController.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers; - -public class HomeController : Controller -{ - // GET: // - public IActionResult Index() - { - return new RedirectResult("~/swagger"); - } -} diff --git a/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs b/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs index 2cfd4063f..5b9ca5f70 100644 --- a/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs +++ b/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs @@ -40,7 +40,7 @@ public class OrdersController : ControllerBase var requestCancelOrder = new IdentifiedCommand(command, guid); _logger.LogInformation( - "----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", + "Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", requestCancelOrder.GetGenericTypeName(), nameof(requestCancelOrder.Command.OrderNumber), requestCancelOrder.Command.OrderNumber, @@ -70,7 +70,7 @@ public class OrdersController : ControllerBase var requestShipOrder = new IdentifiedCommand(command, guid); _logger.LogInformation( - "----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", + "Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", requestShipOrder.GetGenericTypeName(), nameof(requestShipOrder.Command.OrderNumber), requestShipOrder.Command.OrderNumber, @@ -132,7 +132,7 @@ public class OrdersController : ControllerBase public async Task> CreateOrderDraftFromBasketDataAsync([FromBody] CreateOrderDraftCommand createOrderDraftCommand) { _logger.LogInformation( - "----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", + "Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", createOrderDraftCommand.GetGenericTypeName(), nameof(createOrderDraftCommand.BuyerId), createOrderDraftCommand.BuyerId, diff --git a/src/Services/Ordering/Ordering.API/Dockerfile b/src/Services/Ordering/Ordering.API/Dockerfile index f3dc3ad49..bb3268238 100644 --- a/src/Services/Ordering/Ordering.API/Dockerfile +++ b/src/Services/Ordering/Ordering.API/Dockerfile @@ -32,6 +32,7 @@ COPY "Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj" COPY "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" COPY "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" COPY "Services/Payment/Payment.API/Payment.API.csproj" "Services/Payment/Payment.API/Payment.API.csproj" +COPY "Services/Services.Common/Services.Common.csproj" "Services/Services.Common/Services.Common.csproj" COPY "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" COPY "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" COPY "Web/WebhookClient/WebhookClient.csproj" "Web/WebhookClient/WebhookClient.csproj" diff --git a/src/Services/Ordering/Ordering.API/Extensions/BasketItemExtensions.cs b/src/Services/Ordering/Ordering.API/Extensions/BasketItemExtensions.cs index 0f6122a13..918780f4a 100644 --- a/src/Services/Ordering/Ordering.API/Extensions/BasketItemExtensions.cs +++ b/src/Services/Ordering/Ordering.API/Extensions/BasketItemExtensions.cs @@ -1,7 +1,7 @@ -namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Models; +namespace Microsoft.eShopOnContainers.Services.Ordering.API.Extensions; using System.Collections.Generic; -using static Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands.CreateOrderCommand; +using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Models; public static class BasketItemExtensions { diff --git a/src/Services/Ordering/Ordering.API/Extensions/Extensions.cs b/src/Services/Ordering/Ordering.API/Extensions/Extensions.cs new file mode 100644 index 000000000..52de0f4cf --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Extensions/Extensions.cs @@ -0,0 +1,76 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; + +internal static class Extensions +{ + public static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration) + { + var hcBuilder = services.AddHealthChecks(); + + hcBuilder + .AddSqlServer(_ => + configuration.GetRequiredConnectionString("OrderingDB"), + name: "OrderingDB-check", + tags: new string[] { "ready" }); + + return services; + } + + public static IServiceCollection AddDbContexts(this IServiceCollection services, IConfiguration configuration) + { + static void ConfigureSqlOptions(SqlServerDbContextOptionsBuilder sqlOptions) + { + sqlOptions.MigrationsAssembly(typeof(Program).Assembly.FullName); + + // 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.AddDbContext(options => + { + options.UseSqlServer(configuration.GetRequiredConnectionString("OrderingDB"), ConfigureSqlOptions); + }); + + services.AddDbContext(options => + { + options.UseSqlServer(configuration.GetRequiredConnectionString("OrderingDB"), ConfigureSqlOptions); + }); + + return services; + } + + public static IServiceCollection AddIntegrationServices(this IServiceCollection services) + { + services.AddTransient(); + services.AddTransient>( + sp => (DbConnection c) => new IntegrationEventLogService(c)); + + services.AddTransient(); + + return services; + } + + public static IServiceCollection AddApplicationOptions(this IServiceCollection services, IConfiguration configuration) + { + 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; + } +} diff --git a/src/Services/Ordering/Ordering.API/Extensions/LinqSelectExtensions.cs b/src/Services/Ordering/Ordering.API/Extensions/LinqSelectExtensions.cs index c36385d6c..a0bd830d7 100644 --- a/src/Services/Ordering/Ordering.API/Extensions/LinqSelectExtensions.cs +++ b/src/Services/Ordering/Ordering.API/Extensions/LinqSelectExtensions.cs @@ -13,7 +13,7 @@ public static class LinqSelectExtensions } catch (Exception ex) { - returnedValue = new SelectTryResult(element, default(TResult), ex); + returnedValue = new SelectTryResult(element, default, ex); } yield return returnedValue; } diff --git a/src/Services/Ordering/Ordering.API/Extensions/OrderingApiTrace.cs b/src/Services/Ordering/Ordering.API/Extensions/OrderingApiTrace.cs new file mode 100644 index 000000000..dfd2a78e5 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Extensions/OrderingApiTrace.cs @@ -0,0 +1,13 @@ +namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.DomainEventHandlers; + +internal static partial class OrderingApiTrace +{ + [LoggerMessage(EventId = 1, EventName = "OrderStatusUpdated", Level = LogLevel.Trace, Message = "Order with Id: {OrderId} has been successfully updated to status {Status} ({Id})")] + public static partial void LogOrderStatusUpdated(ILogger logger, int orderId, string status, int id); + + [LoggerMessage(EventId = 2, EventName = "PaymentMethodUpdated", Level = LogLevel.Trace, Message = "Order with Id: {OrderId} has been successfully updated with a payment method {PaymentMethod} ({Id})")] + public static partial void LogOrderPaymentMethodUpdated(ILogger logger, int orderId, string paymentMethod, int id); + + [LoggerMessage(EventId = 3, EventName = "BuyerAndPaymentValidatedOrUpdated", Level = LogLevel.Trace, Message = "Buyer {BuyerId} and related payment method were validated or updated for order Id: {OrderId}.")] + public static partial void LogOrderBuyerAndPaymentValidatedOrUpdated(ILogger logger, int buyerId, int orderId); +} diff --git a/src/Services/Ordering/Ordering.API/GlobalUsings.cs b/src/Services/Ordering/Ordering.API/GlobalUsings.cs index 67ab88d35..70255b36a 100644 --- a/src/Services/Ordering/Ordering.API/GlobalUsings.cs +++ b/src/Services/Ordering/Ordering.API/GlobalUsings.cs @@ -1,82 +1,58 @@ -global using ApiModels = Microsoft.eShopOnContainers.Services.Ordering.API.Application.Models; -global using AppCommand = Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; -global using Autofac.Extensions.DependencyInjection; -global using Autofac; -global using Azure.Core; +global using System; +global using System.Collections.Generic; +global using System.Data.Common; +global using System.Data.SqlClient; +global using System.IO; +global using System.Linq; +global using System.Net; +global using System.Runtime.Serialization; +global using System.Threading; +global using System.Threading.Tasks; global using Azure.Identity; global using Dapper; global using FluentValidation; global using Google.Protobuf.Collections; global using Grpc.Core; -global using HealthChecks.UI.Client; global using MediatR; global using Microsoft.AspNetCore.Authorization; global using Microsoft.AspNetCore.Builder; -global using Microsoft.AspNetCore.Diagnostics.HealthChecks; global using Microsoft.AspNetCore.Hosting; global using Microsoft.AspNetCore.Http; -global using Microsoft.AspNetCore.Mvc.Authorization; -global using Microsoft.AspNetCore.Mvc.Filters; global using Microsoft.AspNetCore.Mvc; -global using Microsoft.AspNetCore.Server.Kestrel.Core; -global using Microsoft.AspNetCore; -global using Microsoft.EntityFrameworkCore.Design; global using Microsoft.EntityFrameworkCore; +global using Microsoft.EntityFrameworkCore.Design; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Extensions; -global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; -global using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; -global using Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus; -global using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services; global using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; +global using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services; +global using Microsoft.eShopOnContainers.Services.Ordering.API; global using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Behaviors; global using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; -global using Microsoft.eShopOnContainers.Services.Ordering.API.Application.DomainEventHandlers.OrderStartedEvent; -global using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.AutofacModules; -global using Microsoft.eShopOnContainers.Services.Ordering.API.Application.IntegrationEvents.Events; global using Microsoft.eShopOnContainers.Services.Ordering.API.Application.IntegrationEvents; +global using Microsoft.eShopOnContainers.Services.Ordering.API.Application.IntegrationEvents.EventHandling; +global using Microsoft.eShopOnContainers.Services.Ordering.API.Application.IntegrationEvents.Events; global using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Models; global using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries; global using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Validations; -global using Microsoft.eShopOnContainers.Services.Ordering.API.Controllers; global using Microsoft.eShopOnContainers.Services.Ordering.API.Extensions; -global using GrpcOrdering; -global using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.ActionResults; -global using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Filters; -global using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services; global using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure; -global using Microsoft.eShopOnContainers.Services.Ordering.API; +global using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services; global using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate; global using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; global using Microsoft.eShopOnContainers.Services.Ordering.Domain.Events; global using Microsoft.eShopOnContainers.Services.Ordering.Domain.Exceptions; global using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork; +global using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure; global using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency; global using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories; -global using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.Diagnostics.HealthChecks; -global using Microsoft.Extensions.Hosting; global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.Options; -global using Microsoft.OpenApi.Models; -global using Polly.Retry; global using Polly; -global using RabbitMQ.Client; -global using Serilog.Context; -global using Serilog; +global using Polly.Retry; +global using Services.Common; global using Swashbuckle.AspNetCore.SwaggerGen; -global using System.Collections.Generic; -global using System.Data.Common; -global using System.Data.SqlClient; -global using System.IdentityModel.Tokens.Jwt; -global using System.IO; -global using System.Linq; -global using System.Net; -global using System.Reflection; -global using System.Runtime.Serialization; -global using System.Threading.Tasks; -global using System.Threading; -global using System; +global using AppCommand = Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; +global using ApiModels = Microsoft.eShopOnContainers.Services.Ordering.API.Application.Models; diff --git a/src/Services/Ordering/Ordering.API/Grpc/OrderingService.cs b/src/Services/Ordering/Ordering.API/Grpc/OrderingService.cs index 969a09d46..23525d5bf 100644 --- a/src/Services/Ordering/Ordering.API/Grpc/OrderingService.cs +++ b/src/Services/Ordering/Ordering.API/Grpc/OrderingService.cs @@ -1,4 +1,9 @@ -namespace GrpcOrdering; +using GrpcOrdering; + +using OrderDraftDTO = GrpcOrdering.OrderDraftDTO; +using CreateOrderDraftCommand = GrpcOrdering.CreateOrderDraftCommand; +using BasketItem = GrpcOrdering.BasketItem; +using OrderItemDTO = GrpcOrdering.OrderItemDTO; public class OrderingService : OrderingGrpc.OrderingGrpcBase { @@ -15,7 +20,7 @@ public class OrderingService : OrderingGrpc.OrderingGrpcBase { _logger.LogInformation("Begin grpc call from method {Method} for ordering get order draft {CreateOrderDraftCommand}", context.Method, createOrderDraftCommand); _logger.LogTrace( - "----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", + "Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", createOrderDraftCommand.GetGenericTypeName(), nameof(createOrderDraftCommand.BuyerId), createOrderDraftCommand.BuyerId, @@ -25,7 +30,6 @@ public class OrderingService : OrderingGrpc.OrderingGrpcBase createOrderDraftCommand.BuyerId, this.MapBasketItems(createOrderDraftCommand.Items)); - var data = await _mediator.Send(command); if (data != null) diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/ActionResults/InternalServerErrorObjectResult.cs b/src/Services/Ordering/Ordering.API/Infrastructure/ActionResults/InternalServerErrorObjectResult.cs deleted file mode 100644 index d886bf371..000000000 --- a/src/Services/Ordering/Ordering.API/Infrastructure/ActionResults/InternalServerErrorObjectResult.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.ActionResults; - -public class InternalServerErrorObjectResult : ObjectResult -{ - public InternalServerErrorObjectResult(object error) - : base(error) - { - StatusCode = StatusCodes.Status500InternalServerError; - } -} diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/Auth/AuthorizationHeaderParameterOperationFilter.cs b/src/Services/Ordering/Ordering.API/Infrastructure/Auth/AuthorizationHeaderParameterOperationFilter.cs deleted file mode 100644 index dbf8cf97c..000000000 --- a/src/Services/Ordering/Ordering.API/Infrastructure/Auth/AuthorizationHeaderParameterOperationFilter.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Auth; - -public class AuthorizationHeaderParameterOperationFilter : IOperationFilter -{ - public void Apply(OpenApiOperation operation, OperationFilterContext context) - { - var filterPipeline = context.ApiDescription.ActionDescriptor.FilterDescriptors; - var isAuthorized = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is AuthorizeFilter); - var allowAnonymous = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is IAllowAnonymousFilter); - - if (isAuthorized && !allowAnonymous) - { - if (operation.Parameters == null) - operation.Parameters = new List(); - - - operation.Parameters.Add(new OpenApiParameter - { - Name = "Authorization", - In = ParameterLocation.Header, - Description = "access token", - Required = true - }); - } - } - -} diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/ApplicationModule.cs b/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/ApplicationModule.cs deleted file mode 100644 index 1ab775042..000000000 --- a/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/ApplicationModule.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.AutofacModules; - -public class ApplicationModule - : Autofac.Module -{ - - public string QueriesConnectionString { get; } - - public ApplicationModule(string qconstr) - { - QueriesConnectionString = qconstr; - - } - - protected override void Load(ContainerBuilder builder) - { - - builder.Register(c => new OrderQueries(QueriesConnectionString)) - .As() - .InstancePerLifetimeScope(); - - builder.RegisterType() - .As() - .InstancePerLifetimeScope(); - - builder.RegisterType() - .As() - .InstancePerLifetimeScope(); - - builder.RegisterType() - .As() - .InstancePerLifetimeScope(); - - builder.RegisterAssemblyTypes(typeof(CreateOrderCommandHandler).GetTypeInfo().Assembly) - .AsClosedTypesOf(typeof(IIntegrationEventHandler<>)); - - } -} diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs b/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs deleted file mode 100644 index 5aa77e06c..000000000 --- a/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.AutofacModules; - -public class MediatorModule : Autofac.Module -{ - protected override void Load(ContainerBuilder builder) - { - builder.RegisterAssemblyTypes(typeof(IMediator).GetTypeInfo().Assembly) - .AsImplementedInterfaces(); - - // Register all the Command classes (they implement IRequestHandler) in assembly holding the Commands - builder.RegisterAssemblyTypes(typeof(CreateOrderCommand).GetTypeInfo().Assembly) - .AsClosedTypesOf(typeof(IRequestHandler<,>)); - - // Register the DomainEventHandler classes (they implement INotificationHandler<>) in assembly holding the Domain Events - builder.RegisterAssemblyTypes(typeof(ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler).GetTypeInfo().Assembly) - .AsClosedTypesOf(typeof(INotificationHandler<>)); - - // Register the Command's Validators (Validators based on FluentValidation library) - builder - .RegisterAssemblyTypes(typeof(CreateOrderCommandValidator).GetTypeInfo().Assembly) - .Where(t => t.IsClosedTypeOf(typeof(IValidator<>))) - .AsImplementedInterfaces(); - - builder.RegisterGeneric(typeof(LoggingBehavior<,>)).As(typeof(IPipelineBehavior<,>)); - builder.RegisterGeneric(typeof(ValidatorBehavior<,>)).As(typeof(IPipelineBehavior<,>)); - builder.RegisterGeneric(typeof(TransactionBehaviour<,>)).As(typeof(IPipelineBehavior<,>)); - - } -} diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs b/src/Services/Ordering/Ordering.API/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs deleted file mode 100644 index b4689bf9a..000000000 --- a/src/Services/Ordering/Ordering.API/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Filters; - -public class AuthorizeCheckOperationFilter : IOperationFilter -{ - public void Apply(OpenApiOperation operation, OperationFilterContext context) - { - // Check for authorize attribute - var hasAuthorize = context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType().Any() || - context.MethodInfo.GetCustomAttributes(true).OfType().Any(); - - if (!hasAuthorize) return; - - operation.Responses.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" }); - operation.Responses.TryAdd("403", new OpenApiResponse { Description = "Forbidden" }); - - var oAuthScheme = new OpenApiSecurityScheme - { - Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" } - }; - - operation.Security = new List - { - new() - { - [ oAuthScheme ] = new [] { "orderingapi" } - } - }; - } -} diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs b/src/Services/Ordering/Ordering.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs deleted file mode 100644 index eef48c502..000000000 --- a/src/Services/Ordering/Ordering.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs +++ /dev/null @@ -1,60 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Filters; - -public class HttpGlobalExceptionFilter : IExceptionFilter -{ - private readonly IWebHostEnvironment env; - private readonly ILogger logger; - - public HttpGlobalExceptionFilter(IWebHostEnvironment env, ILogger logger) - { - this.env = env; - this.logger = logger; - } - - public void OnException(ExceptionContext context) - { - logger.LogError(new EventId(context.Exception.HResult), - context.Exception, - context.Exception.Message); - - if (context.Exception.GetType() == typeof(OrderingDomainException)) - { - var problemDetails = new ValidationProblemDetails() - { - Instance = context.HttpContext.Request.Path, - Status = StatusCodes.Status400BadRequest, - Detail = "Please refer to the errors property for additional details." - }; - - problemDetails.Errors.Add("DomainValidations", new string[] { context.Exception.Message.ToString() }); - - context.Result = new BadRequestObjectResult(problemDetails); - context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; - } - else - { - var json = new JsonErrorResponse - { - Messages = new[] { "An error occur.Try it again." } - }; - - if (env.IsDevelopment()) - { - json.DeveloperMessage = context.Exception; - } - - // Result asigned to a result object but in destiny the response is empty. This is a known bug of .net core 1.1 - // It will be fixed in .net core 1.1.2. See https://github.com/aspnet/Mvc/issues/5594 for more information - context.Result = new InternalServerErrorObjectResult(json); - context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; - } - context.ExceptionHandled = true; - } - - private class JsonErrorResponse - { - public string[] Messages { get; set; } - - public object DeveloperMessage { get; set; } - } -} diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/IntegrationEventMigrations/IntegrationEventLogContextDesignTimeFactory.cs b/src/Services/Ordering/Ordering.API/Infrastructure/IntegrationEventMigrations/IntegrationEventLogContextDesignTimeFactory.cs index 3195d9714..52fe8f27a 100644 --- a/src/Services/Ordering/Ordering.API/Infrastructure/IntegrationEventMigrations/IntegrationEventLogContextDesignTimeFactory.cs +++ b/src/Services/Ordering/Ordering.API/Infrastructure/IntegrationEventMigrations/IntegrationEventLogContextDesignTimeFactory.cs @@ -1,4 +1,4 @@ -namespace Catalog.API.Infrastructure.IntegrationEventMigrations +namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.IntegrationEventMigrations { public class IntegrationEventLogContextDesignTimeFactory : IDesignTimeDbContextFactory { diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/OrderingContextSeed.cs b/src/Services/Ordering/Ordering.API/Infrastructure/OrderingContextSeed.cs index c392bb00d..d7f8663fd 100644 --- a/src/Services/Ordering/Ordering.API/Infrastructure/OrderingContextSeed.cs +++ b/src/Services/Ordering/Ordering.API/Infrastructure/OrderingContextSeed.cs @@ -59,7 +59,7 @@ public class OrderingContextSeed } catch (Exception ex) { - log.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); + log.LogError(ex, "Error reading CSV headers"); return GetPredefinedCardTypes(); } @@ -67,13 +67,13 @@ public class OrderingContextSeed return File.ReadAllLines(csvFileCardTypes) .Skip(1) // skip header column .SelectTry(x => CreateCardType(x, ref id)) - .OnCaughtException(ex => { log.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return null; }) + .OnCaughtException(ex => { log.LogError(ex, "Error creating card while seeding database"); return null; }) .Where(x => x != null); } private CardType CreateCardType(string value, ref int id) { - if (String.IsNullOrEmpty(value)) + if (string.IsNullOrEmpty(value)) { throw new Exception("Orderstatus is null or empty"); } @@ -103,7 +103,7 @@ public class OrderingContextSeed } catch (Exception ex) { - log.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); + log.LogError(ex, "Error reading CSV headers"); return GetPredefinedOrderStatus(); } @@ -111,13 +111,13 @@ public class OrderingContextSeed return File.ReadAllLines(csvFileOrderStatus) .Skip(1) // skip header row .SelectTry(x => CreateOrderStatus(x, ref id)) - .OnCaughtException(ex => { log.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return null; }) + .OnCaughtException(ex => { log.LogError(ex, "Error creating order status while seeding database"); return null; }) .Where(x => x != null); } private OrderStatus CreateOrderStatus(string value, ref int id) { - if (String.IsNullOrEmpty(value)) + if (string.IsNullOrEmpty(value)) { throw new Exception("Orderstatus is null or empty"); } @@ -167,7 +167,7 @@ public class OrderingContextSeed sleepDurationProvider: retry => TimeSpan.FromSeconds(5), onRetry: (exception, timeSpan, retry, ctx) => { - logger.LogWarning(exception, "[{prefix}] Exception {ExceptionType} with message {Message} detected on attempt {retry} of {retries}", prefix, exception.GetType().Name, exception.Message, retry, retries); + logger.LogWarning(exception, "[{prefix}] Error seeding database (attempt {retry} of {retries})", prefix, retry, retries); } ); } diff --git a/src/Services/Ordering/Ordering.API/Ordering.API.csproj b/src/Services/Ordering/Ordering.API/Ordering.API.csproj index 573ce2665..0ba10cd65 100644 --- a/src/Services/Ordering/Ordering.API/Ordering.API.csproj +++ b/src/Services/Ordering/Ordering.API/Ordering.API.csproj @@ -3,70 +3,38 @@ net7.0 aspnet-Ordering.API-20161122013547 - $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; ..\..\..\..\docker-compose.dcproj - false - true + Microsoft.eShopOnContainers.Services.Ordering.API - - PreserveNewest - PreserveNewest + + + - - - + - - - - + - - - - - - - - - + - - - - - - - - - - - - - - - - - @@ -76,4 +44,13 @@ - + + + + PreserveNewest + true + PreserveNewest + + + + \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Program.cs b/src/Services/Ordering/Ordering.API/Program.cs index a1a00bb31..56d76de33 100644 --- a/src/Services/Ordering/Ordering.API/Program.cs +++ b/src/Services/Ordering/Ordering.API/Program.cs @@ -1,122 +1,63 @@ -using Autofac.Core; -using Microsoft.Azure.Amqp.Framing; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; +var builder = WebApplication.CreateBuilder(args); -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; - }); +builder.AddServiceDefaults(); + +builder.Services.AddGrpc(); +builder.Services.AddControllers(); - options.Listen(IPAddress.Any, ports.grpcPort, listenOptions => - { - listenOptions.Protocols = HttpProtocols.Http2; - }); +builder.Services.AddHealthChecks(builder.Configuration); +builder.Services.AddDbContexts(builder.Configuration); +builder.Services.AddApplicationOptions(builder.Configuration); +builder.Services.AddIntegrationServices(); +var services = builder.Services; + +services.AddMediatR(cfg => +{ + cfg.RegisterServicesFromAssemblyContaining(typeof(Program)); + + cfg.AddOpenBehavior(typeof(LoggingBehavior<,>)); + cfg.AddOpenBehavior(typeof(ValidatorBehavior<,>)); + cfg.AddOpenBehavior(typeof(TransactionBehavior<,>)); }); -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"]))); +// Register the command validators for the validator behavior (validators based on FluentValidation library) +services.AddSingleton, CancelOrderCommandValidator>(); +services.AddSingleton, CreateOrderCommandValidator>(); +services.AddSingleton>, IdentifiedCommandValidator>(); +services.AddSingleton, ShipOrderCommandValidator>(); + +services.AddScoped(sp => new OrderQueries(builder.Configuration.GetConnectionString("OrderingDB"))); +services.AddScoped(); +services.AddScoped(); +services.AddScoped(); + +// Add integration event handlers. +services.AddTransient, GracePeriodConfirmedIntegrationEventHandler>(); +services.AddTransient, OrderPaymentFailedIntegrationEventHandler>(); +services.AddTransient, OrderPaymentSucceededIntegrationEventHandler>(); +services.AddTransient, OrderStockConfirmedIntegrationEventHandler>(); +services.AddTransient, OrderStockRejectedIntegrationEventHandler>(); +services.AddTransient, UserCheckoutAcceptedIntegrationEventHandler>(); + 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.UseServiceDefaults(); + 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 + +var eventBus = app.Services.GetRequiredService(); + +eventBus.Subscribe>(); +eventBus.Subscribe>(); +eventBus.Subscribe>(); +eventBus.Subscribe>(); +eventBus.Subscribe>(); +eventBus.Subscribe>(); + +using (var scope = app.Services.CreateScope()) { - Log.Information("Applying migrations ({ApplicationContext})...", Program.AppName); - using var scope = app.Services.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); var env = app.Services.GetService(); var settings = app.Services.GetService>(); @@ -126,331 +67,6 @@ try 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); - await app.RunAsync(); - - return 0; -} -catch (Exception ex) -{ - Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", Program.AppName); - return 1; -} -finally -{ - Log.CloseAndFlush(); -} -void ConfigureEventBus(IApplicationBuilder app) -{ - var eventBus = app.ApplicationServices.GetRequiredService(); - - 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() - .MinimumLevel.Verbose() - .Enrich.WithProperty("ApplicationContext", Program.AppName) - .Enrich.FromLogContext() - .WriteTo.Console() - .WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) - .WriteTo.Http(string.IsNullOrWhiteSpace(logstashUrl) ? "http://logstash:8080" : logstashUrl, null) - .ReadFrom.Configuration(configuration) - .CreateLogger(); -} -(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 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; - } -} +await app.RunAsync(); diff --git a/src/Services/Ordering/Ordering.API/Properties/launchSettings.json b/src/Services/Ordering/Ordering.API/Properties/launchSettings.json index 9d9a76490..64d021926 100644 --- a/src/Services/Ordering/Ordering.API/Properties/launchSettings.json +++ b/src/Services/Ordering/Ordering.API/Properties/launchSettings.json @@ -1,25 +1,9 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:55102/", - "sslPort": 0 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "/swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Microsoft.eShopOnContainers.Services.Ordering.API": { + "Ordering.API": { "commandName": "Project", "launchBrowser": true, - "launchUrl": "http://localhost:55102/", + "applicationUrl": "http://localhost:5224/", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/src/Services/Ordering/Ordering.API/appsettings.Development.json b/src/Services/Ordering/Ordering.API/appsettings.Development.json new file mode 100644 index 000000000..af4699517 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/appsettings.Development.json @@ -0,0 +1,5 @@ +{ + "ConnectionStrings": { + "OrderingDB": "Server=tcp:127.0.0.1,5433;Initial Catalog=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;Encrypt=false" + } +} \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/appsettings.json b/src/Services/Ordering/Ordering.API/appsettings.json index a10f3358b..091b53626 100644 --- a/src/Services/Ordering/Ordering.API/appsettings.json +++ b/src/Services/Ordering/Ordering.API/appsettings.json @@ -1,32 +1,54 @@ { - "ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;TrustServerCertificate=true", - "IdentityUrl": "http://localhost:5105", - "UseCustomizationData": false, - "Serilog": { - "SeqServerUrl": null, - "LogstashgUrl": null, - "MinimumLevel": { + "Logging": { + "LogLevel": { "Default": "Information", - "Override": { - "Microsoft": "Warning", - "Microsoft.eShopOnContainers": "Information", - "System": "Warning" + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "Kestrel": { + "Endpoints": { + "Http": { + "Url": "http://localhost:5224" + }, + "gRPC": { + "Url": "http://localhost:6224", + "Protocols": "Http2" } } }, - "AzureServiceBusEnabled": false, - "SubscriptionClientName": "Ordering", - "GracePeriodTime": "1", - "CheckUpdateTime": "30000", + "OpenApi": { + "Endpoint": { + "Name": "Ordering.API V1" + }, + "Document": { + "Description": "The Ordering Service HTTP API", + "Title": "eShopOnContainers - Ordering HTTP API", + "Version": "v1" + }, + "Auth": { + "ClientId": "orderingswaggerui", + "AppName": "Ordering Swagger UI" + } + }, + "ConnectionStrings": { + "EventBus": "localhost" + }, + "EventBus": { + "SubscriptionClientName": "Ordering", + "RetryCount": 5 + }, "ApplicationInsights": { "InstrumentationKey": "" }, - "EventBusRetryCount": 5, - "EventBusConnection": "localhost", - "UseVault": false, - "Vault": { - "Name": "eshop", - "ClientId": "your-client-id", - "ClientSecret": "your-client-secret" - } + "Identity": { + "Url": "http://localhost:5223", + "Audience": "orders", + "Scopes": { + "orders": "Ordering API" + } + }, + "UseCustomizationData": false, + "GracePeriodTime": "1", + "CheckUpdateTime": "30000" } diff --git a/src/Services/Ordering/Ordering.API/web.config b/src/Services/Ordering/Ordering.API/web.config deleted file mode 100644 index 3d49211e5..000000000 --- a/src/Services/Ordering/Ordering.API/web.config +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.BackgroundTasks/Dockerfile b/src/Services/Ordering/Ordering.BackgroundTasks/Dockerfile index 506238148..3bebc9661 100644 --- a/src/Services/Ordering/Ordering.BackgroundTasks/Dockerfile +++ b/src/Services/Ordering/Ordering.BackgroundTasks/Dockerfile @@ -32,6 +32,7 @@ COPY "Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj" COPY "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" COPY "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" COPY "Services/Payment/Payment.API/Payment.API.csproj" "Services/Payment/Payment.API/Payment.API.csproj" +COPY "Services/Services.Common/Services.Common.csproj" "Services/Services.Common/Services.Common.csproj" COPY "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" COPY "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" COPY "Web/WebhookClient/WebhookClient.csproj" "Web/WebhookClient/WebhookClient.csproj" diff --git a/src/Services/Ordering/Ordering.BackgroundTasks/Extensions/CustomExtensionMethods.cs b/src/Services/Ordering/Ordering.BackgroundTasks/Extensions/CustomExtensionMethods.cs index 53d3f0aea..b305e7920 100644 --- a/src/Services/Ordering/Ordering.BackgroundTasks/Extensions/CustomExtensionMethods.cs +++ b/src/Services/Ordering/Ordering.BackgroundTasks/Extensions/CustomExtensionMethods.cs @@ -1,142 +1,25 @@ -using Autofac; -using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; -using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; -using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; -using Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Diagnostics.HealthChecks; -using Microsoft.Extensions.Logging; -using RabbitMQ.Client; -using Serilog; +namespace Ordering.BackgroundTasks.Extensions; -namespace Ordering.BackgroundTasks.Extensions +public static class CustomExtensionMethods { - public static class CustomExtensionMethods + public static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration) { - public static IServiceCollection AddCustomHealthCheck(this IServiceCollection services, IConfiguration configuration) - { - var hcBuilder = services.AddHealthChecks(); - - hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy()); - - hcBuilder.AddSqlServer( - configuration["ConnectionString"], - name: "OrderingTaskDB-check", - tags: new string[] { "orderingtaskdb" }); - - if (configuration.GetValue("AzureServiceBusEnabled")) - { - hcBuilder.AddAzureServiceBusTopic( - configuration["EventBusConnection"], - topicName: "eshop_event_bus", - name: "orderingtask-servicebus-check", - tags: new string[] { "servicebus" }); - } - else - { - hcBuilder.AddRabbitMQ( - $"amqp://{configuration["EventBusConnection"]}", - name: "orderingtask-rabbitmqbus-check", - tags: new string[] { "rabbitmqbus" }); - } - - return services; - } - - public static IServiceCollection AddEventBus(this IServiceCollection services, IConfiguration configuration) - { - var subscriptionClientName = configuration["SubscriptionClientName"]; - - if (configuration.GetValue("AzureServiceBusEnabled")) - { - services.AddSingleton(sp => - { - var serviceBusConnectionString = configuration["EventBusConnection"]; - - return new DefaultServiceBusPersisterConnection(serviceBusConnectionString); - }); - - 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 logger = sp.GetRequiredService>(); - - var factory = new ConnectionFactory() - { - HostName = configuration["EventBusConnection"], - DispatchConsumersAsync = true - }; - - if (!string.IsNullOrEmpty(configuration["EventBusUserName"])) - { - factory.UserName = configuration["EventBusUserName"]; - } + var hcBuilder = services.AddHealthChecks(); - if (!string.IsNullOrEmpty(configuration["EventBusPassword"])) - { - factory.Password = configuration["EventBusPassword"]; - } + hcBuilder.AddSqlServer(_ => + configuration.GetRequiredConnectionString("OrderingDB"), + name: "OrderingTaskDB-check", + tags: new string[] { "live", "ready" }); - var retryCount = 5; - - if (!string.IsNullOrEmpty(configuration["EventBusRetryCount"])) - { - retryCount = int.Parse(configuration["EventBusRetryCount"]); - } - - return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount); - }); - - services.AddSingleton(sp => - { - 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; - } + return services; + } - public static ILoggingBuilder UseSerilog(this ILoggingBuilder builder, IConfiguration configuration) + public static IServiceCollection AddApplicationOptions(this IServiceCollection services, IConfiguration configuration) + { + return services.Configure(configuration) + .Configure(o => { - var seqServerUrl = configuration["Serilog:SeqServerUrl"]; - var logstashUrl = configuration["Serilog:LogstashgUrl"]; - - Log.Logger = new LoggerConfiguration() - .MinimumLevel.Verbose() - .Enrich.WithProperty("ApplicationContext", Program.AppName) - .Enrich.FromLogContext() - .WriteTo.Console() - .WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) - .WriteTo.Http(string.IsNullOrWhiteSpace(logstashUrl) ? "http://logstash:8080" : logstashUrl, null) - .ReadFrom.Configuration(configuration) - .CreateLogger(); - - return builder; - } + o.ConnectionString = configuration.GetRequiredConnectionString("OrderingDB"); + }); } } diff --git a/src/Services/Ordering/Ordering.BackgroundTasks/GlobalUsings.cs b/src/Services/Ordering/Ordering.BackgroundTasks/GlobalUsings.cs index 43b0981b8..72205c528 100644 --- a/src/Services/Ordering/Ordering.BackgroundTasks/GlobalUsings.cs +++ b/src/Services/Ordering/Ordering.BackgroundTasks/GlobalUsings.cs @@ -1,17 +1,10 @@ -global using Autofac.Extensions.DependencyInjection; -global using Microsoft.AspNetCore.Hosting; -global using Microsoft.Extensions.Configuration; -global using Microsoft.Extensions.Hosting; -global using Ordering.BackgroundTasks.Extensions; -global using Serilog; -global using System.IO; -global using HealthChecks.UI.Client; +global using System; global using Microsoft.AspNetCore.Builder; -global using Microsoft.AspNetCore.Diagnostics.HealthChecks; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Hosting; global using Microsoft.Extensions.Logging; +global using Ordering.BackgroundTasks; global using Ordering.BackgroundTasks.Extensions; global using Ordering.BackgroundTasks.Services; -global using System; -global using Ordering.BackgroundTasks; \ No newline at end of file +global using Services.Common; diff --git a/src/Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj b/src/Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj index 0b81c1cfa..85328e8bd 100644 --- a/src/Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj +++ b/src/Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj @@ -3,33 +3,16 @@ net7.0 dotnet-Ordering.BackgroundTasks-9D3E1DD6-405B-447F-8AAB-1708B36D260E - false Linux - - - - - - - - - - - - - - - - - + diff --git a/src/Services/Ordering/Ordering.BackgroundTasks/Program.cs b/src/Services/Ordering/Ordering.BackgroundTasks/Program.cs index ef12360db..7d408b4b8 100644 --- a/src/Services/Ordering/Ordering.BackgroundTasks/Program.cs +++ b/src/Services/Ordering/Ordering.BackgroundTasks/Program.cs @@ -1,74 +1,13 @@ -var appName = "Ordering.BackgroundTasks"; -var builder = WebApplication.CreateBuilder(new WebApplicationOptions -{ - Args = args, - ApplicationName = typeof(Program).Assembly.FullName -}); -builder.Configuration.SetBasePath(Directory.GetCurrentDirectory()); -builder.Configuration.AddJsonFile("appsettings.json", optional: true); -builder.Configuration.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true); -builder.Configuration.AddEnvironmentVariables(); -builder.Host.UseSerilog(CreateSerilogLogger(builder.Configuration)); -builder.Services.AddCustomHealthCheck(builder.Configuration) - .Configure(builder.Configuration) - .AddOptions() - .AddHostedService() - .AddEventBus(builder.Configuration); -var app = builder.Build(); -if (app.Environment.IsDevelopment()) -{ - app.UseDeveloperExceptionPage(); -} -else -{ - app.UseExceptionHandler("/Home/Error"); -} -app.UseRouting(); +var builder = WebApplication.CreateBuilder(args); -app.MapHealthChecks("/hc", new HealthCheckOptions() -{ - Predicate = _ => true, - ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse -}); -app.MapHealthChecks("/liveness", new HealthCheckOptions -{ - Predicate = r => r.Name.Contains("self") -}); +builder.AddServiceDefaults(); -try -{ - Log.Information("Starting web host ({ApplicationContext})...", Program.AppName); - await app.RunAsync(); +builder.Services.AddHealthChecks(builder.Configuration); +builder.Services.AddApplicationOptions(builder.Configuration); +builder.Services.AddHostedService(); - return 0; -} -catch (Exception ex) -{ - Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", Program.AppName); - return 1; -} -finally -{ - Log.CloseAndFlush(); -} +var app = builder.Build(); -Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) -{ - var seqServerUrl = configuration["Serilog:SeqServerUrl"]; - var logstashUrl = configuration["Serilog:LogstashgUrl"]; - return new LoggerConfiguration() - .MinimumLevel.Verbose() - .Enrich.WithProperty("ApplicationContext", Program.AppName) - .Enrich.FromLogContext() - .WriteTo.Console() - .WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) - .WriteTo.Http(string.IsNullOrWhiteSpace(logstashUrl) ? "http://logstash:8080" : logstashUrl, null) - .ReadFrom.Configuration(configuration) - .CreateLogger(); -} +app.UseServiceDefaults(); -public partial class Program -{ - public static string Namespace = typeof(Program).Assembly.GetName().Name; - public static string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1); -} +await app.RunAsync(); diff --git a/src/Services/Ordering/Ordering.BackgroundTasks/Services/GracePeriodManagerService.cs b/src/Services/Ordering/Ordering.BackgroundTasks/Services/GracePeriodManagerService.cs index b494377fb..5f6409546 100644 --- a/src/Services/Ordering/Ordering.BackgroundTasks/Services/GracePeriodManagerService.cs +++ b/src/Services/Ordering/Ordering.BackgroundTasks/Services/GracePeriodManagerService.cs @@ -1,14 +1,11 @@ -using Dapper; -using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Ordering.BackgroundTasks.Events; -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Data.SqlClient; using System.Threading; using System.Threading.Tasks; +using Dapper; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.Extensions.Options; +using Ordering.BackgroundTasks.Events; namespace Ordering.BackgroundTasks.Services { @@ -36,14 +33,7 @@ namespace Ordering.BackgroundTasks.Services _logger.LogDebug("GracePeriodManagerService background task is doing background work."); CheckConfirmedGracePeriodOrders(); - try - { - await Task.Delay(_settings.CheckUpdateTime, stoppingToken); - } - catch (TaskCanceledException exception) - { - _logger.LogCritical(exception, "TaskCanceledException Error", exception.Message); - } + await Task.Delay(_settings.CheckUpdateTime, stoppingToken); } _logger.LogDebug("GracePeriodManagerService background task is stopping."); @@ -59,7 +49,7 @@ namespace Ordering.BackgroundTasks.Services { var confirmGracePeriodEvent = new GracePeriodConfirmedIntegrationEvent(orderId); - _logger.LogInformation("----- Publishing integration event: {IntegrationEventId} from {AppName} - ({@IntegrationEvent})", confirmGracePeriodEvent.Id, Program.AppName, confirmGracePeriodEvent); + _logger.LogInformation("Publishing integration event: {IntegrationEventId} - ({@IntegrationEvent})", confirmGracePeriodEvent.Id, confirmGracePeriodEvent); _eventBus.Publish(confirmGracePeriodEvent); } @@ -81,7 +71,7 @@ namespace Ordering.BackgroundTasks.Services } catch (SqlException exception) { - _logger.LogCritical(exception, "FATAL ERROR: Database connections could not be opened: {Message}", exception.Message); + _logger.LogCritical(exception, "Fatal error establishing database connection"); } diff --git a/src/Services/Ordering/Ordering.BackgroundTasks/appsettings.Development.json b/src/Services/Ordering/Ordering.BackgroundTasks/appsettings.Development.json index e203e9407..19fe83b47 100644 --- a/src/Services/Ordering/Ordering.BackgroundTasks/appsettings.Development.json +++ b/src/Services/Ordering/Ordering.BackgroundTasks/appsettings.Development.json @@ -5,5 +5,8 @@ "System": "Information", "Microsoft": "Information" } + }, + "ConnectionStrings": { + "OrderingDB": "Server=tcp:127.0.0.1,5433;Initial Catalog=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;Encrypt=false" } } diff --git a/src/Services/Ordering/Ordering.BackgroundTasks/appsettings.json b/src/Services/Ordering/Ordering.BackgroundTasks/appsettings.json index 88e5d6858..bb061ce40 100644 --- a/src/Services/Ordering/Ordering.BackgroundTasks/appsettings.json +++ b/src/Services/Ordering/Ordering.BackgroundTasks/appsettings.json @@ -1,26 +1,17 @@ { - "ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;", - "Serilog": { - "SeqServerUrl": null, - "LogstashgUrl": null, - "MinimumLevel": { + "Logging": { + "LogLevel": { "Default": "Information", - "Override": { - "Microsoft": "Warning", - "Microsoft.eShopOnContainers": "Information", - "System": "Warning" - } + "Microsoft.AspNetCore": "Warning" } }, - "SubscriptionClientName": "BackgroundTasks", - "GracePeriodTime": "1", - "CheckUpdateTime": "1000", - "ApplicationInsights": { - "InstrumentationKey": "" + "ConnectionStrings": { + "EventBus": "localhost" + }, + "EventBus": { + "SubscriptionClientName": "BackgroundTasks", + "RetryCount": 5 }, - "AzureServiceBusEnabled": false, - "EventBusRetryCount": 5, - "EventBusConnection": "", - "EventBusUserName": "", - "EventBusPassword": "" + "GracePeriodTime": "1", + "CheckUpdateTime": "1000" } \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/PaymentMethod.cs b/src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/PaymentMethod.cs index e4e490488..2a95e98b9 100644 --- a/src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/PaymentMethod.cs +++ b/src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/PaymentMethod.cs @@ -1,7 +1,6 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate; -public class PaymentMethod - : Entity +public class PaymentMethod : Entity { private string _alias; private string _cardNumber; @@ -12,12 +11,10 @@ public class PaymentMethod private int _cardTypeId; public CardType CardType { get; private set; } - protected PaymentMethod() { } public PaymentMethod(int cardTypeId, string alias, string cardNumber, string securityNumber, string cardHolderName, DateTime expiration) { - _cardNumber = !string.IsNullOrWhiteSpace(cardNumber) ? cardNumber : throw new OrderingDomainException(nameof(cardNumber)); _securityNumber = !string.IsNullOrWhiteSpace(securityNumber) ? securityNumber : throw new OrderingDomainException(nameof(securityNumber)); _cardHolderName = !string.IsNullOrWhiteSpace(cardHolderName) ? cardHolderName : throw new OrderingDomainException(nameof(cardHolderName)); diff --git a/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Address.cs b/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Address.cs index c58f81b16..0d4b819ae 100644 --- a/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Address.cs +++ b/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Address.cs @@ -4,11 +4,11 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O public class Address : ValueObject { - public String Street { get; private set; } - public String City { get; private set; } - public String State { get; private set; } - public String Country { get; private set; } - public String ZipCode { get; private set; } + public string Street { get; private set; } + public string City { get; private set; } + public string State { get; private set; } + public string Country { get; private set; } + public string ZipCode { get; private set; } public Address() { } diff --git a/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/OrderStatus.cs b/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/OrderStatus.cs index aae09bc6a..8c3cc50fb 100644 --- a/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/OrderStatus.cs +++ b/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/OrderStatus.cs @@ -23,11 +23,11 @@ public class OrderStatus public static OrderStatus FromName(string name) { var state = List() - .SingleOrDefault(s => String.Equals(s.Name, name, StringComparison.CurrentCultureIgnoreCase)); + .SingleOrDefault(s => string.Equals(s.Name, name, StringComparison.CurrentCultureIgnoreCase)); if (state == null) { - throw new OrderingDomainException($"Possible values for OrderStatus: {String.Join(",", List().Select(s => s.Name))}"); + throw new OrderingDomainException($"Possible values for OrderStatus: {string.Join(",", List().Select(s => s.Name))}"); } return state; @@ -39,7 +39,7 @@ public class OrderStatus if (state == null) { - throw new OrderingDomainException($"Possible values for OrderStatus: {String.Join(",", List().Select(s => s.Name))}"); + throw new OrderingDomainException($"Possible values for OrderStatus: {string.Join(",", List().Select(s => s.Name))}"); } return state; diff --git a/src/Services/Ordering/Ordering.Domain/SeedWork/Entity.cs b/src/Services/Ordering/Ordering.Domain/SeedWork/Entity.cs index a8e5c23ad..1a577361a 100644 --- a/src/Services/Ordering/Ordering.Domain/SeedWork/Entity.cs +++ b/src/Services/Ordering/Ordering.Domain/SeedWork/Entity.cs @@ -37,7 +37,7 @@ public abstract class Entity public bool IsTransient() { - return this.Id == default(Int32); + return this.Id == default; } public override bool Equals(object obj) diff --git a/src/Services/Ordering/Ordering.Domain/SeedWork/IUnitOfWork.cs b/src/Services/Ordering/Ordering.Domain/SeedWork/IUnitOfWork.cs index b9c121655..408815e11 100644 --- a/src/Services/Ordering/Ordering.Domain/SeedWork/IUnitOfWork.cs +++ b/src/Services/Ordering/Ordering.Domain/SeedWork/IUnitOfWork.cs @@ -2,6 +2,6 @@ public interface IUnitOfWork : IDisposable { - Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)); - Task SaveEntitiesAsync(CancellationToken cancellationToken = default(CancellationToken)); + Task SaveChangesAsync(CancellationToken cancellationToken = default); + Task SaveEntitiesAsync(CancellationToken cancellationToken = default); } diff --git a/src/Services/Ordering/Ordering.FunctionalTests/HttpClientExtensions.cs b/src/Services/Ordering/Ordering.FunctionalTests/HttpClientExtensions.cs deleted file mode 100644 index a4a0dd9c2..000000000 --- a/src/Services/Ordering/Ordering.FunctionalTests/HttpClientExtensions.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Ordering.FunctionalTests; - -static class HttpClientExtensions -{ - public static HttpClient CreateIdempotentClient(this TestServer server) - { - var client = server.CreateClient(); - client.DefaultRequestHeaders.Add("x-requestid", Guid.NewGuid().ToString()); - return client; - } -} diff --git a/src/Services/Ordering/Ordering.FunctionalTests/Ordering.FunctionalTests.csproj b/src/Services/Ordering/Ordering.FunctionalTests/Ordering.FunctionalTests.csproj index 662bfa1f0..9d998af72 100644 --- a/src/Services/Ordering/Ordering.FunctionalTests/Ordering.FunctionalTests.csproj +++ b/src/Services/Ordering/Ordering.FunctionalTests/Ordering.FunctionalTests.csproj @@ -7,15 +7,11 @@ - - - - - - Always + + PreserveNewest - + @@ -28,7 +24,6 @@ - diff --git a/src/Services/Ordering/Ordering.FunctionalTests/OrderingScenarioBase.cs b/src/Services/Ordering/Ordering.FunctionalTests/OrderingScenarioBase.cs index a0b31568b..f3fba40e3 100644 --- a/src/Services/Ordering/Ordering.FunctionalTests/OrderingScenarioBase.cs +++ b/src/Services/Ordering/Ordering.FunctionalTests/OrderingScenarioBase.cs @@ -1,36 +1,39 @@ -namespace Ordering.FunctionalTests; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.Hosting; + +namespace Ordering.FunctionalTests; public class OrderingScenarioBase { - public TestServer CreateServer() + private class OrderingApplication : WebApplicationFactory { - var path = Assembly.GetAssembly(typeof(OrderingScenarioBase)) - .Location; + public TestServer CreateServer() + { + return Server; + } - var hostBuilder = new WebHostBuilder() - .UseContentRoot(Path.GetDirectoryName(path)) - .ConfigureAppConfiguration(cb => + protected override IHost CreateHost(IHostBuilder builder) + { + builder.ConfigureServices(services => { - cb.AddJsonFile("appsettings.json", optional: false) - .AddEnvironmentVariables(); + services.AddSingleton(); }); - var testServer = new TestServer(hostBuilder); - - testServer.Host - .MigrateDbContext((context, services) => + builder.ConfigureAppConfiguration(c => { - var env = services.GetService(); - var settings = services.GetService>(); - var logger = services.GetService>(); + var directory = Path.GetDirectoryName(typeof(OrderingScenarioBase).Assembly.Location)!; - new OrderingContextSeed() - .SeedAsync(context, env, settings, logger) - .Wait(); - }) - .MigrateDbContext((_, __) => { }); + c.AddJsonFile(Path.Combine(directory, "appsettings.Ordering.json"), optional: false); + }); - return testServer; + return base.CreateHost(builder); + } + } + + public TestServer CreateServer() + { + var factory = new OrderingApplication(); + return factory.CreateServer(); } public static class Get @@ -48,4 +51,17 @@ public class OrderingScenarioBase public static string CancelOrder = "api/v1/orders/cancel"; public static string ShipOrder = "api/v1/orders/ship"; } + + private class AuthStartupFilter : IStartupFilter + { + public Action Configure(Action next) + { + return app => + { + app.UseMiddleware(); + + next(app); + }; + } + } } diff --git a/src/Services/Ordering/Ordering.FunctionalTests/OrderingScenarios.cs b/src/Services/Ordering/Ordering.FunctionalTests/OrderingScenarios.cs index 6c7ed17a9..85dd4ab3d 100644 --- a/src/Services/Ordering/Ordering.FunctionalTests/OrderingScenarios.cs +++ b/src/Services/Ordering/Ordering.FunctionalTests/OrderingScenarios.cs @@ -1,7 +1,6 @@ using System.Net; using System.Text; using System.Text.Json; -using WebMVC.Services.ModelDTOs; using Xunit; namespace Ordering.FunctionalTests @@ -16,6 +15,7 @@ namespace Ordering.FunctionalTests var response = await server.CreateClient() .GetAsync(Get.Orders); + var s = await response.Content.ReadAsStringAsync(); response.EnsureSuccessStatusCode(); } @@ -23,10 +23,14 @@ namespace Ordering.FunctionalTests public async Task Cancel_order_no_order_created_bad_request_response() { using var server = CreateServer(); - var content = new StringContent(BuildOrder(), UTF8Encoding.UTF8, "application/json"); - var response = await server.CreateIdempotentClient() + var content = new StringContent(BuildOrder(), UTF8Encoding.UTF8, "application/json") + { + Headers = { { "x-requestid", Guid.NewGuid().ToString() } } + }; + var response = await server.CreateClient() .PutAsync(Put.CancelOrder, content); + var s = await response.Content.ReadAsStringAsync(); Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); } @@ -34,8 +38,11 @@ namespace Ordering.FunctionalTests public async Task Ship_order_no_order_created_bad_request_response() { using var server = CreateServer(); - var content = new StringContent(BuildOrder(), UTF8Encoding.UTF8, "application/json"); - var response = await server.CreateIdempotentClient() + var content = new StringContent(BuildOrder(), UTF8Encoding.UTF8, "application/json") + { + Headers = { { "x-requestid", Guid.NewGuid().ToString() } } + }; + var response = await server.CreateClient() .PutAsync(Put.ShipOrder, content); Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); @@ -43,7 +50,7 @@ namespace Ordering.FunctionalTests string BuildOrder() { - var order = new OrderDTO() + var order = new { OrderNumber = "-1" }; diff --git a/src/Services/Ordering/Ordering.FunctionalTests/appsettings.json b/src/Services/Ordering/Ordering.FunctionalTests/appsettings.Ordering.json similarity index 60% rename from src/Services/Ordering/Ordering.FunctionalTests/appsettings.json rename to src/Services/Ordering/Ordering.FunctionalTests/appsettings.Ordering.json index fa0fae4af..3557ebc49 100644 --- a/src/Services/Ordering/Ordering.FunctionalTests/appsettings.json +++ b/src/Services/Ordering/Ordering.FunctionalTests/appsettings.Ordering.json @@ -1,6 +1,9 @@ { + "ConnectionStrings": { + "OrderingDB": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;Encrypt=False;TrustServerCertificate=true", + "EventBus": "localhost" + }, "CheckUpdateTime": "30000", - "ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;Encrypt=False;TrustServerCertificate=true", "EventBusConnection": "localhost", "ExternalCatalogBaseUrl": "http://localhost:5101", "GracePeriodTime": "1", diff --git a/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs b/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs index 8693664c3..66af0bbd5 100644 --- a/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs +++ b/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs @@ -38,7 +38,7 @@ public class OrderingContext : DbContext, IUnitOfWork modelBuilder.ApplyConfiguration(new BuyerEntityTypeConfiguration()); } - public async Task SaveEntitiesAsync(CancellationToken cancellationToken = default(CancellationToken)) + public async Task SaveEntitiesAsync(CancellationToken cancellationToken = default) { // Dispatch Domain Events collection. // Choices: @@ -120,12 +120,12 @@ public class OrderingContextDesignFactory : IDesignTimeDbContextFactory CreateStream(IStreamRequest request, CancellationToken cancellationToken = default) { - return default(IAsyncEnumerable); + return default; } public IAsyncEnumerable CreateStream(object request, CancellationToken cancellationToken = default) { - return default(IAsyncEnumerable); + return default; } public Task Publish(TNotification notification, CancellationToken cancellationToken = default) where TNotification : INotification @@ -140,7 +140,7 @@ public class OrderingContextDesignFactory : IDesignTimeDbContextFactory Send(IRequest request, CancellationToken cancellationToken = default) { - return Task.FromResult(default(TResponse)); + return Task.FromResult(default); } public Task Send(object request, CancellationToken cancellationToken = default) diff --git a/src/Services/Ordering/Ordering.SignalrHub/AutofacModules/ApplicationModule.cs b/src/Services/Ordering/Ordering.SignalrHub/AutofacModules/ApplicationModule.cs deleted file mode 100644 index 9d28154ea..000000000 --- a/src/Services/Ordering/Ordering.SignalrHub/AutofacModules/ApplicationModule.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Ordering.SignalrHub.AutofacModules; - -public class ApplicationModule - : Autofac.Module -{ - - public string QueriesConnectionString { get; } - - public ApplicationModule() - { - } - - protected override void Load(ContainerBuilder builder) - { - - builder.RegisterAssemblyTypes(typeof(OrderStatusChangedToAwaitingValidationIntegrationEvent).GetTypeInfo().Assembly) - .AsClosedTypesOf(typeof(IIntegrationEventHandler<>)); - - } -} diff --git a/src/Services/Ordering/Ordering.SignalrHub/Dockerfile b/src/Services/Ordering/Ordering.SignalrHub/Dockerfile index ef1417617..12529612d 100644 --- a/src/Services/Ordering/Ordering.SignalrHub/Dockerfile +++ b/src/Services/Ordering/Ordering.SignalrHub/Dockerfile @@ -32,6 +32,7 @@ COPY "Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj" COPY "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" COPY "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" COPY "Services/Payment/Payment.API/Payment.API.csproj" "Services/Payment/Payment.API/Payment.API.csproj" +COPY "Services/Services.Common/Services.Common.csproj" "Services/Services.Common/Services.Common.csproj" COPY "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" COPY "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" COPY "Web/WebhookClient/WebhookClient.csproj" "Web/WebhookClient/WebhookClient.csproj" diff --git a/src/Services/Ordering/Ordering.SignalrHub/Extensions/Extensions.cs b/src/Services/Ordering/Ordering.SignalrHub/Extensions/Extensions.cs new file mode 100644 index 000000000..40b8d1ff6 --- /dev/null +++ b/src/Services/Ordering/Ordering.SignalrHub/Extensions/Extensions.cs @@ -0,0 +1,42 @@ +using Microsoft.AspNetCore.Http; + +internal static class Extensions +{ + public static IServiceCollection AddSignalR(this IServiceCollection services, IConfiguration configuration) + { + if (configuration.GetConnectionString("redis") is string redisConnection) + { + // TODO: Add a redis health check + services.AddSignalR().AddStackExchangeRedis(redisConnection); + } + else + { + services.AddSignalR(); + } + + // Configure hub auth (grab the token from the query string) + return services.Configure(JwtBearerDefaults.AuthenticationScheme, options => + { + options.Events = new JwtBearerEvents + { + OnMessageReceived = context => + { + var accessToken = context.Request.Query["access_token"]; + + var endpoint = context.HttpContext.GetEndpoint(); + + // Make sure this is a Hub endpoint. + if (endpoint?.Metadata.GetMetadata() is null) + { + return Task.CompletedTask; + } + + context.Token = accessToken; + + return Task.CompletedTask; + } + }; + }); + } + +} diff --git a/src/Services/Ordering/Ordering.SignalrHub/GlobalUsings.cs b/src/Services/Ordering/Ordering.SignalrHub/GlobalUsings.cs index b913f56bf..16b8b6bda 100644 --- a/src/Services/Ordering/Ordering.SignalrHub/GlobalUsings.cs +++ b/src/Services/Ordering/Ordering.SignalrHub/GlobalUsings.cs @@ -1,33 +1,17 @@ -global using Autofac.Extensions.DependencyInjection; -global using Autofac; -global using HealthChecks.UI.Client; +global using System; +global using System.Collections.Generic; +global using System.Threading.Tasks; global using Microsoft.AspNetCore.Authentication.JwtBearer; global using Microsoft.AspNetCore.Authorization; global using Microsoft.AspNetCore.Builder; -global using Microsoft.AspNetCore.Diagnostics.HealthChecks; -global using Microsoft.AspNetCore.Hosting; global using Microsoft.AspNetCore.SignalR; -global using Microsoft.AspNetCore; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; -global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; -global using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; -global using Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus; -global using Microsoft.eShopOnContainers.Services.Ordering.SignalrHub.IntegrationEvents.Events; +global using Microsoft.eShopOnContainers.Services.Ordering.SignalrHub; global using Microsoft.eShopOnContainers.Services.Ordering.SignalrHub.IntegrationEvents; +global using Microsoft.eShopOnContainers.Services.Ordering.SignalrHub.IntegrationEvents.EventHandling; +global using Microsoft.eShopOnContainers.Services.Ordering.SignalrHub.IntegrationEvents.Events; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.Diagnostics.HealthChecks; global using Microsoft.Extensions.Logging; -global using Microsoft.eShopOnContainers.Services.Ordering.SignalrHub.AutofacModules; -global using Microsoft.eShopOnContainers.Services.Ordering.SignalrHub.IntegrationEvents.EventHandling; -global using Microsoft.eShopOnContainers.Services.Ordering.SignalrHub; -global using RabbitMQ.Client; -global using Serilog.Context; -global using Serilog; -global using System.IdentityModel.Tokens.Jwt; -global using System.IO; -global using System.Reflection; -global using System.Threading.Tasks; -global using System; -global using Microsoft.Extensions.Hosting; +global using Services.Common; diff --git a/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToAwaitingValidationIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToAwaitingValidationIntegrationEventHandler.cs index 341b781ab..4210830dc 100644 --- a/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToAwaitingValidationIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToAwaitingValidationIntegrationEventHandler.cs @@ -16,9 +16,9 @@ public class OrderStatusChangedToAwaitingValidationIntegrationEventHandler : IIn public async Task Handle(OrderStatusChangedToAwaitingValidationIntegrationEvent @event) { - using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + using (_logger.BeginScope(new List> { new ("IntegrationEventContext", @event.Id) })) { - _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + _logger.LogInformation("Handling integration event: {IntegrationEventId} - ({@IntegrationEvent})", @event.Id, @event); await _hubContext.Clients .Group(@event.BuyerName) diff --git a/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToCancelledIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToCancelledIntegrationEventHandler.cs index aba5091b9..d0642702a 100644 --- a/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToCancelledIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToCancelledIntegrationEventHandler.cs @@ -1,4 +1,6 @@ -namespace Microsoft.eShopOnContainers.Services.Ordering.SignalrHub.IntegrationEvents.EventHandling; +using System.Collections.Generic; + +namespace Microsoft.eShopOnContainers.Services.Ordering.SignalrHub.IntegrationEvents.EventHandling; public class OrderStatusChangedToCancelledIntegrationEventHandler : IIntegrationEventHandler { @@ -16,13 +18,13 @@ public class OrderStatusChangedToCancelledIntegrationEventHandler : IIntegration public async Task Handle(OrderStatusChangedToCancelledIntegrationEvent @event) { - using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + using (_logger.BeginScope(new List> { new ("IntegrationEventContext", @event.Id) })) { - _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + _logger.LogInformation("Handling integration event: {IntegrationEventId} - ({@IntegrationEvent})", @event.Id, @event); await _hubContext.Clients .Group(@event.BuyerName) .SendAsync("UpdatedOrderState", new { OrderId = @event.OrderId, Status = @event.OrderStatus }); } } -} \ No newline at end of file +} diff --git a/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToPaidIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToPaidIntegrationEventHandler.cs index 03fc04356..5b96fd7da 100644 --- a/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToPaidIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToPaidIntegrationEventHandler.cs @@ -1,4 +1,6 @@ -namespace Microsoft.eShopOnContainers.Services.Ordering.SignalrHub.IntegrationEvents.EventHandling; +using System.Collections.Generic; + +namespace Microsoft.eShopOnContainers.Services.Ordering.SignalrHub.IntegrationEvents.EventHandling; public class OrderStatusChangedToPaidIntegrationEventHandler : IIntegrationEventHandler { @@ -16,9 +18,9 @@ public class OrderStatusChangedToPaidIntegrationEventHandler : IIntegrationEvent public async Task Handle(OrderStatusChangedToPaidIntegrationEvent @event) { - using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + using (_logger.BeginScope(new List> { new ("IntegrationEventContext", @event.Id) })) { - _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + _logger.LogInformation("Handling integration event: {IntegrationEventId} - ({@IntegrationEvent})", @event.Id, @event); await _hubContext.Clients .Group(@event.BuyerName) diff --git a/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToShippedIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToShippedIntegrationEventHandler.cs index 663f7ae3a..283c6f8a2 100644 --- a/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToShippedIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToShippedIntegrationEventHandler.cs @@ -13,12 +13,11 @@ public class OrderStatusChangedToShippedIntegrationEventHandler : IIntegrationEv _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } - public async Task Handle(OrderStatusChangedToShippedIntegrationEvent @event) { - using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + using (_logger.BeginScope(new List> { new ("IntegrationEventContext", @event.Id) })) { - _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + _logger.LogInformation("Handling integration event: {IntegrationEventId} - ({@IntegrationEvent})", @event.Id, @event); await _hubContext.Clients .Group(@event.BuyerName) diff --git a/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToStockConfirmedIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToStockConfirmedIntegrationEventHandler.cs index aa662065d..eea86552d 100644 --- a/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToStockConfirmedIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToStockConfirmedIntegrationEventHandler.cs @@ -1,4 +1,6 @@ -namespace Microsoft.eShopOnContainers.Services.Ordering.SignalrHub.IntegrationEvents.EventHandling; +using System.Collections.Generic; + +namespace Microsoft.eShopOnContainers.Services.Ordering.SignalrHub.IntegrationEvents.EventHandling; public class OrderStatusChangedToStockConfirmedIntegrationEventHandler : IIntegrationEventHandler @@ -17,9 +19,9 @@ public class OrderStatusChangedToStockConfirmedIntegrationEventHandler : public async Task Handle(OrderStatusChangedToStockConfirmedIntegrationEvent @event) { - using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + using (_logger.BeginScope(new List> { new ("IntegrationEventContext", @event.Id) })) { - _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + _logger.LogInformation("Handling integration event: {IntegrationEventId} - ({@IntegrationEvent})", @event.Id, @event); await _hubContext.Clients .Group(@event.BuyerName) diff --git a/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToSubmittedIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToSubmittedIntegrationEventHandler.cs index 0f8042013..7a536971c 100644 --- a/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToSubmittedIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToSubmittedIntegrationEventHandler.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; + namespace Microsoft.eShopOnContainers.Services.Ordering.SignalrHub.IntegrationEvents.EventHandling; public class OrderStatusChangedToSubmittedIntegrationEventHandler : @@ -17,9 +19,9 @@ public class OrderStatusChangedToSubmittedIntegrationEventHandler : public async Task Handle(OrderStatusChangedToSubmittedIntegrationEvent @event) { - using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + using (_logger.BeginScope(new List> { new ("IntegrationEventContext", @event.Id) })) { - _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + _logger.LogInformation("Handling integration event: {IntegrationEventId} - ({@IntegrationEvent})", @event.Id, @event); await _hubContext.Clients .Group(@event.BuyerName) diff --git a/src/Services/Ordering/Ordering.SignalrHub/NotificationHub.cs b/src/Services/Ordering/Ordering.SignalrHub/NotificationHub.cs index 9f545bba4..b22960096 100644 --- a/src/Services/Ordering/Ordering.SignalrHub/NotificationHub.cs +++ b/src/Services/Ordering/Ordering.SignalrHub/NotificationHub.cs @@ -3,7 +3,6 @@ [Authorize] public class NotificationsHub : Hub { - public override async Task OnConnectedAsync() { await Groups.AddToGroupAsync(Context.ConnectionId, Context.User.Identity.Name); diff --git a/src/Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj b/src/Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj index 92e65dd6b..a870e20ee 100644 --- a/src/Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj +++ b/src/Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj @@ -3,38 +3,15 @@ net7.0 ..\..\..\..\docker-compose.dcproj - false - true - - - - - - - - - - - - - - - - - - - - - - + diff --git a/src/Services/Ordering/Ordering.SignalrHub/Program.cs b/src/Services/Ordering/Ordering.SignalrHub/Program.cs index ffbb5b8dc..1b0abd099 100644 --- a/src/Services/Ordering/Ordering.SignalrHub/Program.cs +++ b/src/Services/Ordering/Ordering.SignalrHub/Program.cs @@ -1,261 +1,29 @@ -var appName = "Ordering.SignalrHub"; -var builder = WebApplication.CreateBuilder(new WebApplicationOptions -{ - Args = args, - ApplicationName = typeof(Program).Assembly.FullName, - ContentRootPath = Directory.GetCurrentDirectory() -}); -builder.Configuration.SetBasePath(Directory.GetCurrentDirectory()); -builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true); -builder.Configuration.AddEnvironmentVariables(); -builder.WebHost.CaptureStartupErrors(false); -builder.Host.UseSerilog(CreateSerilogLogger(builder.Configuration)); +var builder = WebApplication.CreateBuilder(args); -builder.Services - .AddCustomHealthCheck(builder.Configuration) - .AddCors(options => - { - options.AddPolicy("CorsPolicy", - builder => builder - .AllowAnyMethod() - .AllowAnyHeader() - .SetIsOriginAllowed((host) => true) - .AllowCredentials()); - }); -if (builder.Configuration.GetValue("IsClusterEnv") == bool.TrueString) -{ - builder.Services - .AddSignalR() - .AddStackExchangeRedis(builder.Configuration["SignalrStoreConnectionString"]); -} -else -{ - builder.Services.AddSignalR(); -} +builder.AddServiceDefaults(); -if (builder.Configuration.GetValue("AzureServiceBusEnabled")) -{ - builder.Services.AddSingleton(sp => - { - var serviceBusConnectionString = builder.Configuration["EventBusConnection"]; +builder.Services.AddSignalR(builder.Configuration); - var subscriptionClientName = builder.Configuration["SubscriptionClientName"]; +builder.Services.AddSingleton, OrderStatusChangedToAwaitingValidationIntegrationEventHandler>(); +builder.Services.AddSingleton, OrderStatusChangedToCancelledIntegrationEventHandler>(); +builder.Services.AddSingleton, OrderStatusChangedToPaidIntegrationEventHandler>(); +builder.Services.AddSingleton, OrderStatusChangedToShippedIntegrationEventHandler>(); +builder.Services.AddSingleton, OrderStatusChangedToStockConfirmedIntegrationEventHandler>(); +builder.Services.AddSingleton, OrderStatusChangedToSubmittedIntegrationEventHandler>(); - return new DefaultServiceBusPersisterConnection(serviceBusConnectionString); - }); -} -else -{ - builder.Services.AddSingleton(sp => - { - var logger = sp.GetRequiredService>(); - - - var factory = new ConnectionFactory() - { - HostName = builder.Configuration["EventBusConnection"], - DispatchConsumersAsync = true - }; - - if (!string.IsNullOrEmpty(builder.Configuration["EventBusUserName"])) - { - factory.UserName = builder.Configuration["EventBusUserName"]; - } - - if (!string.IsNullOrEmpty(builder.Configuration["EventBusPassword"])) - { - factory.Password = builder.Configuration["EventBusPassword"]; - } - - var retryCount = 5; - if (!string.IsNullOrEmpty(builder.Configuration["EventBusRetryCount"])) - { - retryCount = int.Parse(builder.Configuration["EventBusRetryCount"]); - } - - return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount); - }); -} -ConfigureAuthService(builder.Services, builder.Configuration); -RegisterEventBus(builder.Services, builder.Configuration); -builder.Services.AddOptions(); -builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); -builder.Host.ConfigureContainer(conBuilder => conBuilder.RegisterModule(new ApplicationModule())); var app = builder.Build(); -if (app.Environment.IsDevelopment()) -{ - app.UseDeveloperExceptionPage(); -} -else -{ - app.UseExceptionHandler("/Home/Error"); -} -var pathBase = builder.Configuration["PATH_BASE"]; -if (!string.IsNullOrEmpty(pathBase)) -{ - app.UsePathBase(pathBase); -} -app.UseRouting(); -app.UseCors("CorsPolicy"); -app.UseAuthentication(); -app.UseAuthorization(); -app.MapHealthChecks("/hc", new HealthCheckOptions() -{ - Predicate = _ => true, - ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse -}); -app.MapHealthChecks("/liveness", new HealthCheckOptions -{ - Predicate = r => r.Name.Contains("self") -}); -app.MapHub("/hub/notificationhub"); - -ConfigureEventBus(app); -try -{ - Log.Information("Starting web host ({ApplicationContext})...", Program.AppName); - await app.RunAsync(); - return 0; -} -catch (Exception ex) -{ - Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", Program.AppName); - return 1; -} -finally -{ - Log.CloseAndFlush(); -} -void ConfigureEventBus(IApplicationBuilder app) -{ - var eventBus = app.ApplicationServices.GetRequiredService(); +app.UseServiceDefaults(); - eventBus.Subscribe(); - eventBus.Subscribe(); - eventBus.Subscribe(); - eventBus.Subscribe(); - eventBus.Subscribe(); - eventBus.Subscribe(); -} -void ConfigureAuthService(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.signalrhub"; - options.TokenValidationParameters.ValidateAudience = false; - options.Events = new JwtBearerEvents - { - OnMessageReceived = context => - { - var accessToken = context.Request.Query["access_token"]; - - var path = context.HttpContext.Request.Path; - if (!string.IsNullOrEmpty(accessToken) && (path.StartsWithSegments("/hub/notificationhub"))) - { - context.Token = accessToken; - } - return Task.CompletedTask; - } - }; - }); - services.AddAuthorization(options => - { - options.AddPolicy("ApiScope", policy => - { - policy.RequireAuthenticatedUser(); - policy.RequireClaim("scope", "orders.signalrhub"); - }); - }); -} -void RegisterEventBus(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(); -} -static Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) -{ - var seqServerUrl = configuration["Serilog:SeqServerUrl"]; - var logstashUrl = configuration["Serilog:LogstashgUrl"]; - return new LoggerConfiguration() - .MinimumLevel.Verbose() - .Enrich.WithProperty("ApplicationContext", Program.AppName) - .Enrich.FromLogContext() - .WriteTo.Console() - .WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) - .WriteTo.Http(string.IsNullOrWhiteSpace(logstashUrl) ? "http://logstash:8080" : logstashUrl, null) - .ReadFrom.Configuration(configuration) - .CreateLogger(); -} -public partial class Program -{ - public static string Namespace = typeof(Program).Assembly.GetName().Name; - public static string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1); -} -public static class CustomExtensionMethods -{ - public static IServiceCollection AddCustomHealthCheck(this IServiceCollection services, IConfiguration configuration) - { - var hcBuilder = services.AddHealthChecks(); +app.MapHub("/hub/notificationhub"); - hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy()); +var eventBus = app.Services.GetRequiredService(); - if (configuration.GetValue("AzureServiceBusEnabled")) - { - hcBuilder - .AddAzureServiceBusTopic( - configuration["EventBusConnection"], - topicName: "eshop_event_bus", - name: "signalr-servicebus-check", - tags: new string[] { "servicebus" }); - } - else - { - hcBuilder - .AddRabbitMQ( - $"amqp://{configuration["EventBusConnection"]}", - name: "signalr-rabbitmqbus-check", - tags: new string[] { "rabbitmqbus" }); - } +eventBus.Subscribe(); +eventBus.Subscribe(); +eventBus.Subscribe(); +eventBus.Subscribe(); +eventBus.Subscribe(); +eventBus.Subscribe(); - return services; - } -} +await app.RunAsync(); diff --git a/src/Services/Ordering/Ordering.SignalrHub/Properties/launchSettings.json b/src/Services/Ordering/Ordering.SignalrHub/Properties/launchSettings.json index 3ff683a08..f0a9157cf 100644 --- a/src/Services/Ordering/Ordering.SignalrHub/Properties/launchSettings.json +++ b/src/Services/Ordering/Ordering.SignalrHub/Properties/launchSettings.json @@ -1,27 +1,12 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:51311/", - "sslPort": 0 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, "Ordering.SignalrHub": { "commandName": "Project", - "launchBrowser": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5225/", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:51312/" + } } } } diff --git a/src/Services/Ordering/Ordering.SignalrHub/appsettings.json b/src/Services/Ordering/Ordering.SignalrHub/appsettings.json index e43c354a3..887e4afdd 100644 --- a/src/Services/Ordering/Ordering.SignalrHub/appsettings.json +++ b/src/Services/Ordering/Ordering.SignalrHub/appsettings.json @@ -1,19 +1,19 @@ { - "IdentityUrl": "http://localhost:5105", - "Serilog": { - "SeqServerUrl": null, - "LogstashgUrl": null, - "MinimumLevel": { + "Logging": { + "LogLevel": { "Default": "Information", - "Override": { - "Microsoft": "Warning", - "Microsoft.eShopOnContainers": "Information", - "System": "Warning" - } + "Microsoft.AspNetCore": "Warning" } }, - "AzureServiceBusEnabled": false, - "SubscriptionClientName": "Ordering.signalrhub", - "EventBusRetryCount": 5, - "EventBusConnection": "localhost" + "Identity": { + "Audience": "orders.signalrhub", + "Url": "http://localhost:5223" + }, + "EventBus": { + "SubscriptionClientName": "Ordering.signalrhub", + "RetryCount": 5 + }, + "ConnectionStrings": { + "EventBus": "localhost" + } } \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.UnitTests/Application/IdentifiedCommandHandlerTest.cs b/src/Services/Ordering/Ordering.UnitTests/Application/IdentifiedCommandHandlerTest.cs index 1b7d38864..4c7c276e6 100644 --- a/src/Services/Ordering/Ordering.UnitTests/Application/IdentifiedCommandHandlerTest.cs +++ b/src/Services/Ordering/Ordering.UnitTests/Application/IdentifiedCommandHandlerTest.cs @@ -23,17 +23,16 @@ public class IdentifiedCommandHandlerTest _requestManager.Setup(x => x.ExistAsync(It.IsAny())) .Returns(Task.FromResult(false)); - _mediator.Setup(x => x.Send(It.IsAny>(), default(System.Threading.CancellationToken))) + _mediator.Setup(x => x.Send(It.IsAny>(), default)) .Returns(Task.FromResult(true)); - //Act - var handler = new IdentifiedCommandHandler(_mediator.Object, _requestManager.Object, _loggerMock.Object); - var cltToken = new System.Threading.CancellationToken(); - var result = await handler.Handle(fakeOrderCmd, cltToken); + // Act + var handler = new CreateOrderIdentifiedCommandHandler(_mediator.Object, _requestManager.Object, _loggerMock.Object); + var result = await handler.Handle(fakeOrderCmd, CancellationToken.None); - //Assert + // Assert Assert.True(result); - _mediator.Verify(x => x.Send(It.IsAny>(), default(System.Threading.CancellationToken)), Times.Once()); + _mediator.Verify(x => x.Send(It.IsAny>(), default), Times.Once()); } [Fact] @@ -46,17 +45,15 @@ public class IdentifiedCommandHandlerTest _requestManager.Setup(x => x.ExistAsync(It.IsAny())) .Returns(Task.FromResult(true)); - _mediator.Setup(x => x.Send(It.IsAny>(), default(System.Threading.CancellationToken))) + _mediator.Setup(x => x.Send(It.IsAny>(), default)) .Returns(Task.FromResult(true)); - //Act - var handler = new IdentifiedCommandHandler(_mediator.Object, _requestManager.Object, _loggerMock.Object); - var cltToken = new System.Threading.CancellationToken(); - var result = await handler.Handle(fakeOrderCmd, cltToken); + // Act + var handler = new CreateOrderIdentifiedCommandHandler(_mediator.Object, _requestManager.Object, _loggerMock.Object); + var result = await handler.Handle(fakeOrderCmd, CancellationToken.None); - //Assert - Assert.False(result); - _mediator.Verify(x => x.Send(It.IsAny>(), default(System.Threading.CancellationToken)), Times.Never()); + // Assert + _mediator.Verify(x => x.Send(It.IsAny>(), default), Times.Never()); } private CreateOrderCommand FakeOrderRequest(Dictionary args = null) diff --git a/src/Services/Ordering/Ordering.UnitTests/Application/NewOrderCommandHandlerTest.cs b/src/Services/Ordering/Ordering.UnitTests/Application/NewOrderCommandHandlerTest.cs index 184a5f5d2..734435cc6 100644 --- a/src/Services/Ordering/Ordering.UnitTests/Application/NewOrderCommandHandlerTest.cs +++ b/src/Services/Ordering/Ordering.UnitTests/Application/NewOrderCommandHandlerTest.cs @@ -31,7 +31,7 @@ public class NewOrderRequestHandlerTest _orderRepositoryMock.Setup(orderRepo => orderRepo.GetAsync(It.IsAny())) .Returns(Task.FromResult(FakeOrder())); - _orderRepositoryMock.Setup(buyerRepo => buyerRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken))) + _orderRepositoryMock.Setup(buyerRepo => buyerRepo.UnitOfWork.SaveChangesAsync(default)) .Returns(Task.FromResult(1)); _identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(buyerId); diff --git a/src/Services/Ordering/Ordering.UnitTests/Application/OrdersWebApiTest.cs b/src/Services/Ordering/Ordering.UnitTests/Application/OrdersWebApiTest.cs index cfa14a3ec..bb8f8844b 100644 --- a/src/Services/Ordering/Ordering.UnitTests/Application/OrdersWebApiTest.cs +++ b/src/Services/Ordering/Ordering.UnitTests/Application/OrdersWebApiTest.cs @@ -21,7 +21,7 @@ public class OrdersWebApiTest public async Task Cancel_order_with_requestId_success() { //Arrange - _mediatorMock.Setup(x => x.Send(It.IsAny>(), default(CancellationToken))) + _mediatorMock.Setup(x => x.Send(It.IsAny>(), default)) .Returns(Task.FromResult(true)); //Act @@ -29,7 +29,7 @@ public class OrdersWebApiTest var actionResult = await orderController.CancelOrderAsync(new CancelOrderCommand(1), Guid.NewGuid().ToString()) as OkResult; //Assert - Assert.Equal(actionResult.StatusCode, (int)System.Net.HttpStatusCode.OK); + Assert.Equal((int)System.Net.HttpStatusCode.OK, actionResult.StatusCode); } @@ -37,22 +37,22 @@ public class OrdersWebApiTest public async Task Cancel_order_bad_request() { //Arrange - _mediatorMock.Setup(x => x.Send(It.IsAny>(), default(CancellationToken))) + _mediatorMock.Setup(x => x.Send(It.IsAny>(), default)) .Returns(Task.FromResult(true)); //Act var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object, _loggerMock.Object); - var actionResult = await orderController.CancelOrderAsync(new CancelOrderCommand(1), String.Empty) as BadRequestResult; + var actionResult = await orderController.CancelOrderAsync(new CancelOrderCommand(1), string.Empty) as BadRequestResult; //Assert - Assert.Equal(actionResult.StatusCode, (int)System.Net.HttpStatusCode.BadRequest); + Assert.Equal((int)System.Net.HttpStatusCode.BadRequest, actionResult.StatusCode); } [Fact] public async Task Ship_order_with_requestId_success() { //Arrange - _mediatorMock.Setup(x => x.Send(It.IsAny>(), default(System.Threading.CancellationToken))) + _mediatorMock.Setup(x => x.Send(It.IsAny>(), default)) .Returns(Task.FromResult(true)); //Act @@ -60,7 +60,7 @@ public class OrdersWebApiTest var actionResult = await orderController.ShipOrderAsync(new ShipOrderCommand(1), Guid.NewGuid().ToString()) as OkResult; //Assert - Assert.Equal(actionResult.StatusCode, (int)System.Net.HttpStatusCode.OK); + Assert.Equal((int)System.Net.HttpStatusCode.OK, actionResult.StatusCode); } @@ -68,15 +68,15 @@ public class OrdersWebApiTest public async Task Ship_order_bad_request() { //Arrange - _mediatorMock.Setup(x => x.Send(It.IsAny>(), default(System.Threading.CancellationToken))) + _mediatorMock.Setup(x => x.Send(It.IsAny>(), default)) .Returns(Task.FromResult(true)); //Act var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object, _loggerMock.Object); - var actionResult = await orderController.ShipOrderAsync(new ShipOrderCommand(1), String.Empty) as BadRequestResult; + var actionResult = await orderController.ShipOrderAsync(new ShipOrderCommand(1), string.Empty) as BadRequestResult; //Assert - Assert.Equal(actionResult.StatusCode, (int)System.Net.HttpStatusCode.BadRequest); + Assert.Equal((int)System.Net.HttpStatusCode.BadRequest, actionResult.StatusCode); } [Fact] diff --git a/src/Services/Payment/Payment.API/Dockerfile b/src/Services/Payment/Payment.API/Dockerfile index f2d45cbf1..d1bcc6bda 100644 --- a/src/Services/Payment/Payment.API/Dockerfile +++ b/src/Services/Payment/Payment.API/Dockerfile @@ -32,6 +32,7 @@ COPY "Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj" COPY "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" COPY "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" COPY "Services/Payment/Payment.API/Payment.API.csproj" "Services/Payment/Payment.API/Payment.API.csproj" +COPY "Services/Services.Common/Services.Common.csproj" "Services/Services.Common/Services.Common.csproj" COPY "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" COPY "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" COPY "Web/WebhookClient/WebhookClient.csproj" "Web/WebhookClient/WebhookClient.csproj" diff --git a/src/Services/Payment/Payment.API/GlobalUsings.cs b/src/Services/Payment/Payment.API/GlobalUsings.cs index 01ba3682b..a29434a61 100644 --- a/src/Services/Payment/Payment.API/GlobalUsings.cs +++ b/src/Services/Payment/Payment.API/GlobalUsings.cs @@ -1,29 +1,11 @@ -global using Autofac.Extensions.DependencyInjection; -global using Autofac; -global using Azure.Core; -global using Azure.Identity; -global using HealthChecks.UI.Client; -global using Microsoft.AspNetCore.Diagnostics.HealthChecks; +global using System.Threading.Tasks; global using Microsoft.AspNetCore.Builder; -global using Microsoft.AspNetCore; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; -global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; -global using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; -global using Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus; +global using Microsoft.eShopOnContainers.Payment.API; +global using Microsoft.eShopOnContainers.Payment.API.IntegrationEvents.EventHandling; global using Microsoft.eShopOnContainers.Payment.API.IntegrationEvents.Events; -global using Microsoft.Extensions.Diagnostics.HealthChecks; -global using Microsoft.Extensions.Options; -global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.Logging; -global using Microsoft.eShopOnContainers.Payment.API.IntegrationEvents.EventHandling; -global using Microsoft.eShopOnContainers.Payment.API; -global using RabbitMQ.Client; -global using Serilog.Context; -global using Serilog; -global using System.Threading.Tasks; -global using System; -global using System.IO; -global using Microsoft.AspNetCore.Hosting; -global using Microsoft.Extensions.Hosting; +global using Microsoft.Extensions.Options; +global using Services.Common; diff --git a/src/Services/Payment/Payment.API/IntegrationEvents/EventHandling/OrderStatusChangedToStockConfirmedIntegrationEventHandler.cs b/src/Services/Payment/Payment.API/IntegrationEvents/EventHandling/OrderStatusChangedToStockConfirmedIntegrationEventHandler.cs index 8c8fac9d0..c68ab0959 100644 --- a/src/Services/Payment/Payment.API/IntegrationEvents/EventHandling/OrderStatusChangedToStockConfirmedIntegrationEventHandler.cs +++ b/src/Services/Payment/Payment.API/IntegrationEvents/EventHandling/OrderStatusChangedToStockConfirmedIntegrationEventHandler.cs @@ -1,4 +1,6 @@ -namespace Microsoft.eShopOnContainers.Payment.API.IntegrationEvents.EventHandling; +using System.Collections.Generic; + +namespace Microsoft.eShopOnContainers.Payment.API.IntegrationEvents.EventHandling; public class OrderStatusChangedToStockConfirmedIntegrationEventHandler : IIntegrationEventHandler @@ -21,9 +23,9 @@ public class OrderStatusChangedToStockConfirmedIntegrationEventHandler : public async Task Handle(OrderStatusChangedToStockConfirmedIntegrationEvent @event) { - using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + using (_logger.BeginScope(new List> { new ("IntegrationEventContext", @event.Id) })) { - _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + _logger.LogInformation("Handling integration event: {IntegrationEventId} - ({@IntegrationEvent})", @event.Id, @event); IntegrationEvent orderPaymentIntegrationEvent; @@ -42,7 +44,7 @@ public class OrderStatusChangedToStockConfirmedIntegrationEventHandler : orderPaymentIntegrationEvent = new OrderPaymentFailedIntegrationEvent(@event.OrderId); } - _logger.LogInformation("----- Publishing integration event: {IntegrationEventId} from {AppName} - ({@IntegrationEvent})", orderPaymentIntegrationEvent.Id, Program.AppName, orderPaymentIntegrationEvent); + _logger.LogInformation("Publishing integration event: {IntegrationEventId} - ({@IntegrationEvent})", orderPaymentIntegrationEvent.Id, orderPaymentIntegrationEvent); _eventBus.Publish(orderPaymentIntegrationEvent); diff --git a/src/Services/Payment/Payment.API/Payment.API.csproj b/src/Services/Payment/Payment.API/Payment.API.csproj index 61cfd2050..6dc523a2e 100644 --- a/src/Services/Payment/Payment.API/Payment.API.csproj +++ b/src/Services/Payment/Payment.API/Payment.API.csproj @@ -3,36 +3,10 @@ net7.0 ..\..\..\..\docker-compose.dcproj - $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; - false - true - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/src/Services/Payment/Payment.API/Program.cs b/src/Services/Payment/Payment.API/Program.cs index acfc73217..ee68397db 100644 --- a/src/Services/Payment/Payment.API/Program.cs +++ b/src/Services/Payment/Payment.API/Program.cs @@ -1,222 +1,17 @@ -var appName = "Payment.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.CaptureStartupErrors(false); -builder.Host.UseSerilog(CreateSerilogLogger(builder.Configuration)); -builder.Services - .AddCustomHealthCheck(builder.Configuration); -builder.Services.Configure(builder.Configuration); -builder.Services.AddApplicationInsightsTelemetry(builder.Configuration); -if (builder.Configuration.GetValue("AzureServiceBusEnabled")) -{ - builder.Services.AddSingleton(sp => - { - var serviceBusConnectionString = builder.Configuration["EventBusConnection"]; - var subscriptionClientName = builder.Configuration["SubscriptionClientName"]; - - return new DefaultServiceBusPersisterConnection(serviceBusConnectionString); - }); -} -else -{ - builder.Services.AddSingleton(sp => - { - var logger = sp.GetRequiredService>(); - var factory = new ConnectionFactory() - { - HostName = builder.Configuration["EventBusConnection"], - DispatchConsumersAsync = true - }; +var builder = WebApplication.CreateBuilder(args); - if (!string.IsNullOrEmpty(builder.Configuration["EventBusUserName"])) - { - factory.UserName = builder.Configuration["EventBusUserName"]; - } +builder.AddServiceDefaults(); - if (!string.IsNullOrEmpty(builder.Configuration["EventBusPassword"])) - { - factory.Password = builder.Configuration["EventBusPassword"]; - } +builder.Services.Configure(builder.Configuration); - var retryCount = 5; - if (!string.IsNullOrEmpty(builder.Configuration["EventBusRetryCount"])) - { - retryCount = int.Parse(builder.Configuration["EventBusRetryCount"]); - } +builder.Services.AddTransient(); - return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount); - }); -} -RegisterEventBus(builder.Services); 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); -} -ConfigureEventBus(app); - -app.UseRouting(); -app.MapHealthChecks("/hc", new HealthCheckOptions() -{ - Predicate = _ => true, - ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse -}); -app.MapHealthChecks("/liveness", new HealthCheckOptions -{ - Predicate = r => r.Name.Contains("self") -}); -try -{ - Log.Information("Starting web host ({ApplicationContext})...", Program.AppName); - await app.RunAsync(); - - return 0; -} -catch (Exception ex) -{ - Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", Program.AppName); - return 1; -} -finally -{ - Log.CloseAndFlush(); -} - -void RegisterEventBus(IServiceCollection services) -{ - if (builder.Configuration.GetValue("AzureServiceBusEnabled")) - { - services.AddSingleton(sp => - { - var serviceBusPersisterConnection = sp.GetRequiredService(); - var logger = sp.GetRequiredService>(); - var eventBusSubcriptionsManager = sp.GetRequiredService(); - string subscriptionName = builder.Configuration["SubscriptionClientName"]; - - return new EventBusServiceBus(serviceBusPersisterConnection, logger, - eventBusSubcriptionsManager, sp, subscriptionName); - }); - } - else - { - services.AddSingleton(sp => - { - var subscriptionClientName = builder.Configuration["SubscriptionClientName"]; - var rabbitMQPersistentConnection = sp.GetRequiredService(); - var logger = sp.GetRequiredService>(); - var eventBusSubcriptionsManager = sp.GetRequiredService(); - - var retryCount = 5; - if (!string.IsNullOrEmpty(builder.Configuration["EventBusRetryCount"])) - { - retryCount = int.Parse(builder.Configuration["EventBusRetryCount"]); - } - - return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, sp, eventBusSubcriptionsManager, subscriptionClientName, retryCount); - }); - } - - services.AddTransient(); - services.AddSingleton(); -} - -void ConfigureEventBus(IApplicationBuilder app) -{ - var eventBus = app.ApplicationServices.GetRequiredService(); - eventBus.Subscribe(); -} - -Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) -{ - var seqServerUrl = configuration["Serilog:SeqServerUrl"]; - var logstashUrl = configuration["Serilog:LogstashgUrl"]; - return new LoggerConfiguration() - .MinimumLevel.Verbose() - .Enrich.WithProperty("ApplicationContext", Program.AppName) - .Enrich.FromLogContext() - .WriteTo.Console() - .WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) - .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(); -} -public partial class Program -{ - public static string Namespace = typeof(Program).Assembly.GetName().Name; - public static string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1); -} -public static class CustomExtensionMethods -{ - public static IServiceCollection AddCustomHealthCheck(this IServiceCollection services, IConfiguration configuration) - { - var hcBuilder = services.AddHealthChecks(); +app.UseServiceDefaults(); - hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy()); +var eventBus = app.Services.GetRequiredService(); - if (configuration.GetValue("AzureServiceBusEnabled")) - { - hcBuilder - .AddAzureServiceBusTopic( - configuration["EventBusConnection"], - topicName: "eshop_event_bus", - name: "payment-servicebus-check", - tags: new string[] { "servicebus" }); - } - else - { - hcBuilder - .AddRabbitMQ( - $"amqp://{configuration["EventBusConnection"]}", - name: "payment-rabbitmqbus-check", - tags: new string[] { "rabbitmqbus" }); - } +eventBus.Subscribe(); - return services; - } -} +await app.RunAsync(); diff --git a/src/Services/Payment/Payment.API/Properties/launchSettings.json b/src/Services/Payment/Payment.API/Properties/launchSettings.json index 5eac4c092..8d137d73f 100644 --- a/src/Services/Payment/Payment.API/Properties/launchSettings.json +++ b/src/Services/Payment/Payment.API/Properties/launchSettings.json @@ -1,29 +1,12 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:63336/", - "sslPort": 0 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "api/values", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, "Payment.API": { "commandName": "Project", - "launchBrowser": true, - "launchUrl": "api/values", + "launchBrowser": false, + "applicationUrl": "http://localhost:5226", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:3331" + } } } } \ No newline at end of file diff --git a/src/Services/Payment/Payment.API/appsettings.Development.json b/src/Services/Payment/Payment.API/appsettings.Development.json index fa8ce71a9..e2f168cc8 100644 --- a/src/Services/Payment/Payment.API/appsettings.Development.json +++ b/src/Services/Payment/Payment.API/appsettings.Development.json @@ -1,6 +1,8 @@ { "Logging": { - "IncludeScopes": false, + "Console": { + "IncludeScopes": false + }, "LogLevel": { "Default": "Debug", "System": "Information", diff --git a/src/Services/Payment/Payment.API/appsettings.json b/src/Services/Payment/Payment.API/appsettings.json index 9964a8bd2..71e1aa6af 100644 --- a/src/Services/Payment/Payment.API/appsettings.json +++ b/src/Services/Payment/Payment.API/appsettings.json @@ -1,21 +1,16 @@ { - "Serilog": { - "SeqServerUrl": null, - "LogstashgUrl": null, - "MinimumLevel": { + "Logging": { + "LogLevel": { "Default": "Information", - "Override": { - "Microsoft": "Warning", - "Microsoft.eShopOnContainers": "Information", - "System": "Warning" - } + "Microsoft.AspNetCore": "Warning" } }, - "PaymentSucceeded": true, - "AzureServiceBusEnabled": false, - "SubscriptionClientName": "Payment", - "ApplicationInsights": { - "InstrumentationKey": "" + "ConnectionStrings": { + "EventBus": "localhost" }, - "EventBusRetryCount": 5 -} + "EventBus": { + "SubscriptionClientName": "Payment", + "RetryCount": 5 + }, + "PaymentSucceeded": true +} \ No newline at end of file diff --git a/src/Services/Basket/Basket.API/Infrastructure/Middlewares/AuthorizeCheckOperationFilter.cs b/src/Services/Services.Common/AuthorizeCheckOperationFilter.cs similarity index 58% rename from src/Services/Basket/Basket.API/Infrastructure/Middlewares/AuthorizeCheckOperationFilter.cs rename to src/Services/Services.Common/AuthorizeCheckOperationFilter.cs index 9608ec0a7..21785ae16 100644 --- a/src/Services/Basket/Basket.API/Infrastructure/Middlewares/AuthorizeCheckOperationFilter.cs +++ b/src/Services/Services.Common/AuthorizeCheckOperationFilter.cs @@ -1,7 +1,18 @@ -namespace Basket.API.Infrastructure.Filters; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Configuration; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; -public class AuthorizeCheckOperationFilter : IOperationFilter +namespace Services.Common; +internal class AuthorizeCheckOperationFilter : IOperationFilter { + private readonly IConfiguration _configuration; + + public AuthorizeCheckOperationFilter(IConfiguration configuration) + { + _configuration = configuration; + } + public void Apply(OpenApiOperation operation, OperationFilterContext context) { // Check for authorize attribute @@ -18,11 +29,14 @@ public class AuthorizeCheckOperationFilter : IOperationFilter Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" } }; + var identitySection = _configuration.GetSection("Identity"); + var scopes = identitySection.GetRequiredSection("Scopes").GetChildren().Select(r => r.Key).ToArray(); + operation.Security = new List { new() { - [ oAuthScheme ] = new [] { "basketapi" } + [ oAuthScheme ] = scopes } }; } diff --git a/src/Services/Services.Common/CommonExtensions.cs b/src/Services/Services.Common/CommonExtensions.cs new file mode 100644 index 000000000..1d666edd2 --- /dev/null +++ b/src/Services/Services.Common/CommonExtensions.cs @@ -0,0 +1,447 @@ +using System.IdentityModel.Tokens.Jwt; +using Azure.Identity; +using HealthChecks.UI.Client; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.OpenApi.Models; +using RabbitMQ.Client; + +namespace Services.Common; + +public static class CommonExtensions +{ + public static WebApplicationBuilder AddServiceDefaults(this WebApplicationBuilder builder) + { + // Shared configuration via key vault + builder.Configuration.AddKeyVault(); + + // Shared app insights configuration + builder.Services.AddApplicationInsights(builder.Configuration); + + // Default health checks assume the event bus and self health checks + builder.Services.AddDefaultHealthChecks(builder.Configuration); + + // Add the event bus + builder.Services.AddEventBus(builder.Configuration); + + builder.Services.AddDefaultAuthentication(builder.Configuration); + + builder.Services.AddDefaultOpenApi(builder.Configuration); + + // Add the accessor + builder.Services.AddHttpContextAccessor(); + + return builder; + } + + public static WebApplication UseServiceDefaults(this WebApplication app) + { + if (!app.Environment.IsDevelopment()) + { + app.UseExceptionHandler("/Home/Error"); + } + + var pathBase = app.Configuration["PATH_BASE"]; + + if (!string.IsNullOrEmpty(pathBase)) + { + app.UsePathBase(pathBase); + app.UseRouting(); + + var identitySection = app.Configuration.GetSection("Identity"); + + if (identitySection.Exists()) + { + // We have to add the auth middleware to the pipeline here + app.UseAuthentication(); + app.UseAuthorization(); + } + } + + app.UseDefaultOpenApi(app.Configuration); + + app.MapDefaultHealthChecks(); + + return app; + } + + public static async Task CheckHealthAsync(this WebApplication app) + { + app.Logger.LogInformation("Running health checks..."); + + // Do a health check on startup, this will throw an exception if any of the checks fail + var report = await app.Services.GetRequiredService().CheckHealthAsync(); + + if (report.Status == HealthStatus.Unhealthy) + { + app.Logger.LogCritical("Health checks failed!"); + foreach (var entry in report.Entries) + { + if (entry.Value.Status == HealthStatus.Unhealthy) + { + app.Logger.LogCritical("{Check}: {Status}", entry.Key, entry.Value.Status); + } + } + + return false; + } + + return true; + } + + public static IApplicationBuilder UseDefaultOpenApi(this WebApplication app, IConfiguration configuration) + { + var openApiSection = configuration.GetSection("OpenApi"); + + if (!openApiSection.Exists()) + { + return app; + } + + app.UseSwagger(); + app.UseSwaggerUI(setup => + { + /// { + /// "OpenApi": { + /// "Endpoint: { + /// "Name": + /// }, + /// "Auth": { + /// "ClientId": .., + /// "AppName": .. + /// } + /// } + /// } + + var pathBase = configuration["PATH_BASE"]; + var authSection = openApiSection.GetSection("Auth"); + var endpointSection = openApiSection.GetRequiredSection("Endpoint"); + + var swaggerUrl = endpointSection["Url"] ?? $"{(!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty)}/swagger/v1/swagger.json"; + + setup.SwaggerEndpoint(swaggerUrl, endpointSection.GetRequiredValue("Name")); + + if (authSection.Exists()) + { + setup.OAuthClientId(authSection.GetRequiredValue("ClientId")); + setup.OAuthAppName(authSection.GetRequiredValue("AppName")); + } + }); + + // Add a redirect from the root of the app to the swagger endpoint + app.MapGet("/", () => Results.Redirect("/swagger")).ExcludeFromDescription(); + + return app; + } + + public static IServiceCollection AddDefaultOpenApi(this IServiceCollection services, IConfiguration configuration) + { + var openApi = configuration.GetSection("OpenApi"); + + if (!openApi.Exists()) + { + return services; + } + + services.AddEndpointsApiExplorer(); + + return services.AddSwaggerGen(options => + { + /// { + /// "OpenApi": { + /// "Document": { + /// "Title": .. + /// "Version": .. + /// "Description": .. + /// } + /// } + /// } + var document = openApi.GetRequiredSection("Document"); + + var version = document.GetRequiredValue("Version") ?? "v1"; + + options.SwaggerDoc(version, new OpenApiInfo + { + Title = document.GetRequiredValue("Title"), + Version = version, + Description = document.GetRequiredValue("Description") + }); + + var identitySection = configuration.GetSection("Identity"); + + if (!identitySection.Exists()) + { + // No identity section, so no authentication open api definition + return; + } + + // { + // "Identity": { + // "ExternalUrl": "http://identity", + // "Scopes": { + // "basket": "Basket API" + // } + // } + // } + + var identityUrlExternal = identitySection["ExternalUrl"] ?? identitySection.GetRequiredValue("Url"); + var scopes = identitySection.GetRequiredSection("Scopes").GetChildren().ToDictionary(p => p.Key, p => p.Value); + + options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme + { + Type = SecuritySchemeType.OAuth2, + Flows = new OpenApiOAuthFlows() + { + Implicit = new OpenApiOAuthFlow() + { + AuthorizationUrl = new Uri($"{identityUrlExternal}/connect/authorize"), + TokenUrl = new Uri($"{identityUrlExternal}/connect/token"), + Scopes = scopes, + } + } + }); + + options.OperationFilter(); + }); + } + + public static IServiceCollection AddDefaultAuthentication(this IServiceCollection services, IConfiguration configuration) + { + // { + // "Identity": { + // "Url": "http://identity", + // "Audience": "basket" + // } + // } + + var identitySection = configuration.GetSection("Identity"); + + if (!identitySection.Exists()) + { + // No identity section, so no authentication + return services; + } + + // prevent from mapping "sub" claim to nameidentifier. + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); + + services.AddAuthentication().AddJwtBearer(options => + { + var identityUrl = identitySection.GetRequiredValue("Url"); + var audience = identitySection.GetRequiredValue("Audience"); + + options.Authority = identityUrl; + options.RequireHttpsMetadata = false; + options.Audience = audience; + options.TokenValidationParameters.ValidateAudience = false; + }); + + return services; + } + + public static ConfigurationManager AddKeyVault(this ConfigurationManager configuration) + { + // { + // "Vault": { + // "Name": "myvault", + // "TenantId": "mytenantid", + // "ClientId": "myclientid", + // } + // } + + var vaultSection = configuration.GetSection("Vault"); + + if (!vaultSection.Exists()) + { + return configuration; + } + + var credential = new ClientSecretCredential( + vaultSection.GetRequiredValue("TenantId"), + vaultSection.GetRequiredValue("ClientId"), + vaultSection.GetRequiredValue("ClientSecret")); + + var name = vaultSection.GetRequiredValue("Name"); + + configuration.AddAzureKeyVault(new Uri($"https://{name}.vault.azure.net/"), credential); + + return configuration; + } + + public static IServiceCollection AddApplicationInsights(this IServiceCollection services, IConfiguration configuration) + { + var appInsightsSection = configuration.GetSection("ApplicationInsights"); + + // No instrumentation key, so no application insights + if (string.IsNullOrEmpty(appInsightsSection["InstrumentationKey"])) + { + return services; + } + + services.AddApplicationInsightsTelemetry(configuration); + services.AddApplicationInsightsKubernetesEnricher(); + return services; + } + + public static IHealthChecksBuilder AddDefaultHealthChecks(this IServiceCollection services, IConfiguration configuration) + { + var hcBuilder = services.AddHealthChecks(); + + // Health check for the application itself + hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy()); + + // { + // "EventBus": { + // "ProviderName": "ServiceBus | RabbitMQ", + // } + // } + + var eventBusSection = configuration.GetSection("EventBus"); + + if (!eventBusSection.Exists()) + { + return hcBuilder; + } + + return eventBusSection["ProviderName"]?.ToLowerInvariant() switch + { + "servicebus" => hcBuilder.AddAzureServiceBusTopic( + _ => configuration.GetRequiredConnectionString("EventBus"), + _ => "eshop_event_bus", + name: "servicebus", + tags: new string[] { "ready" }), + + _ => hcBuilder.AddRabbitMQ( + _ => $"amqp://{configuration.GetRequiredConnectionString("EventBus")}", + name: "rabbitmq", + tags: new string[] { "ready" }) + }; + } + + public static IServiceCollection AddEventBus(this IServiceCollection services, IConfiguration configuration) + { + // { + // "ConnectionStrings": { + // "EventBus": "..." + // }, + + // { + // "EventBus": { + // "ProviderName": "ServiceBus | RabbitMQ", + // ... + // } + // } + + // { + // "EventBus": { + // "ProviderName": "ServiceBus", + // "SubscriptionClientName": "eshop_event_bus" + // } + // } + + // { + // "EventBus": { + // "ProviderName": "RabbitMQ", + // "SubscriptionClientName": "...", + // "UserName": "...", + // "Password": "...", + // "RetryCount": 1 + // } + // } + + var eventBusSection = configuration.GetSection("EventBus"); + + if (!eventBusSection.Exists()) + { + return services; + } + + if (string.Equals(eventBusSection["ProviderName"], "ServiceBus", StringComparison.OrdinalIgnoreCase)) + { + services.AddSingleton(sp => + { + var serviceBusConnectionString = configuration.GetRequiredConnectionString("EventBus"); + + return new DefaultServiceBusPersisterConnection(serviceBusConnectionString); + }); + + services.AddSingleton(sp => + { + var serviceBusPersisterConnection = sp.GetRequiredService(); + var logger = sp.GetRequiredService>(); + var eventBusSubscriptionsManager = sp.GetRequiredService(); + string subscriptionName = eventBusSection.GetRequiredValue("SubscriptionClientName"); + + return new EventBusServiceBus(serviceBusPersisterConnection, logger, + eventBusSubscriptionsManager, sp, subscriptionName); + }); + } + else + { + services.AddSingleton(sp => + { + var logger = sp.GetRequiredService>(); + + var factory = new ConnectionFactory() + { + HostName = configuration.GetRequiredConnectionString("EventBus"), + DispatchConsumersAsync = true + }; + + if (!string.IsNullOrEmpty(eventBusSection["UserName"])) + { + factory.UserName = eventBusSection["UserName"]; + } + + if (!string.IsNullOrEmpty(eventBusSection["Password"])) + { + factory.Password = eventBusSection["Password"]; + } + + var retryCount = eventBusSection.GetValue("RetryCount", 5); + + return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount); + }); + + services.AddSingleton(sp => + { + var subscriptionClientName = eventBusSection.GetRequiredValue("SubscriptionClientName"); + var rabbitMQPersistentConnection = sp.GetRequiredService(); + var logger = sp.GetRequiredService>(); + var eventBusSubscriptionsManager = sp.GetRequiredService(); + var retryCount = eventBusSection.GetValue("RetryCount", 5); + + return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, sp, eventBusSubscriptionsManager, subscriptionClientName, retryCount); + }); + } + + services.AddSingleton(); + return services; + } + + public static void MapDefaultHealthChecks(this IEndpointRouteBuilder routes) + { + routes.MapHealthChecks("/hc", new HealthCheckOptions() + { + Predicate = _ => true, + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse + }); + + routes.MapHealthChecks("/liveness", new HealthCheckOptions + { + Predicate = r => r.Name.Contains("self") + }); + } +} diff --git a/src/Services/Services.Common/ConfigurationExtensions.cs b/src/Services/Services.Common/ConfigurationExtensions.cs new file mode 100644 index 000000000..8d8c11cdd --- /dev/null +++ b/src/Services/Services.Common/ConfigurationExtensions.cs @@ -0,0 +1,10 @@ +namespace Microsoft.Extensions.Configuration; + +public static class ConfigurationExtensions +{ + public static string GetRequiredValue(this IConfiguration configuration, string name) => + configuration[name] ?? throw new InvalidOperationException($"Configuration missing value for: {(configuration is IConfigurationSection s ? s.Path + ":" + name : name)}"); + + public static string GetRequiredConnectionString(this IConfiguration configuration, string name) => + configuration.GetConnectionString(name) ?? throw new InvalidOperationException($"Configuration missing value for: {(configuration is IConfigurationSection s ? s.Path + ":ConnectionStrings:" + name : "ConnectionStrings:" + name)}"); +} diff --git a/src/Services/Services.Common/HttpClientAuthorizationDelegatingHandler.cs b/src/Services/Services.Common/HttpClientAuthorizationDelegatingHandler.cs new file mode 100644 index 000000000..09b24573c --- /dev/null +++ b/src/Services/Services.Common/HttpClientAuthorizationDelegatingHandler.cs @@ -0,0 +1,31 @@ +using System.Net.Http.Headers; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; + +namespace Services.Common; + +public class HttpClientAuthorizationDelegatingHandler + : DelegatingHandler +{ + private readonly IHttpContextAccessor _httpContextAccessor; + + public HttpClientAuthorizationDelegatingHandler(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + if (_httpContextAccessor.HttpContext is HttpContext context) + { + var accessToken = await context.GetTokenAsync("access_token"); + + if (accessToken is not null) + { + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + } + } + + return await base.SendAsync(request, cancellationToken); + } +} diff --git a/src/Services/Services.Common/Services.Common.csproj b/src/Services/Services.Common/Services.Common.csproj new file mode 100644 index 000000000..8e73f0143 --- /dev/null +++ b/src/Services/Services.Common/Services.Common.csproj @@ -0,0 +1,56 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Services/Webhooks/Webhooks.API/Controllers/HomeController.cs b/src/Services/Webhooks/Webhooks.API/Controllers/HomeController.cs deleted file mode 100644 index 2a7951ec3..000000000 --- a/src/Services/Webhooks/Webhooks.API/Controllers/HomeController.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Webhooks.API.Controllers; - -public class HomeController : Controller -{ - // GET: // - public IActionResult Index() - { - return new RedirectResult("~/swagger"); - } - -} diff --git a/src/Services/Webhooks/Webhooks.API/Dockerfile b/src/Services/Webhooks/Webhooks.API/Dockerfile index 83f8af2cf..63d8fdb6b 100644 --- a/src/Services/Webhooks/Webhooks.API/Dockerfile +++ b/src/Services/Webhooks/Webhooks.API/Dockerfile @@ -32,6 +32,7 @@ COPY "Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj" COPY "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" COPY "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" COPY "Services/Payment/Payment.API/Payment.API.csproj" "Services/Payment/Payment.API/Payment.API.csproj" +COPY "Services/Services.Common/Services.Common.csproj" "Services/Services.Common/Services.Common.csproj" COPY "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" COPY "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" COPY "Web/WebhookClient/WebhookClient.csproj" "Web/WebhookClient/WebhookClient.csproj" diff --git a/src/Services/Webhooks/Webhooks.API/Extensions/Extensions.cs b/src/Services/Webhooks/Webhooks.API/Extensions/Extensions.cs new file mode 100644 index 000000000..b7dd7cb1d --- /dev/null +++ b/src/Services/Webhooks/Webhooks.API/Extensions/Extensions.cs @@ -0,0 +1,47 @@ +internal static class Extensions +{ + public static IServiceCollection AddDbContexts(this IServiceCollection services, IConfiguration configuration) + { + services.AddDbContext(options => + { + options.UseSqlServer(configuration.GetRequiredConnectionString("WebHooksDB"), + sqlServerOptionsAction: sqlOptions => + { + sqlOptions.MigrationsAssembly(typeof(Program).Assembly.FullName); + + //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 AddHealthChecks(this IServiceCollection services, IConfiguration configuration) + { + var hcBuilder = services.AddHealthChecks(); + + hcBuilder + .AddSqlServer(_ => + configuration.GetRequiredConnectionString("WebHooksDB"), + name: "WebhooksApiDb-check", + tags: new string[] { "ready" }); + + return services; + } + + public static IServiceCollection AddHttpClientServices(this IServiceCollection services) + { + // Add http client services + services.AddHttpClient("GrantClient") + .SetHandlerLifetime(TimeSpan.FromMinutes(5)); + + return services; + } + + public static IServiceCollection AddIntegrationServices(this IServiceCollection services) + { + return services.AddTransient>( + sp => (DbConnection c) => new IntegrationEventLogService(c)); + } +} diff --git a/src/Services/Webhooks/Webhooks.API/GlobalUsings.cs b/src/Services/Webhooks/Webhooks.API/GlobalUsings.cs index a2b8516f1..2ef5c1bd7 100644 --- a/src/Services/Webhooks/Webhooks.API/GlobalUsings.cs +++ b/src/Services/Webhooks/Webhooks.API/GlobalUsings.cs @@ -1,48 +1,28 @@ -global using Autofac.Extensions.DependencyInjection; -global using Autofac; -global using HealthChecks.UI.Client; -global using Microsoft.AspNetCore.Authentication.JwtBearer; +global using System; +global using System.Collections.Generic; +global using System.ComponentModel.DataAnnotations; +global using System.Data.Common; +global using System.Linq; +global using System.Net; +global using System.Net.Http; +global using System.Text; +global using System.Text.Json; +global using System.Threading.Tasks; global using Microsoft.AspNetCore.Authorization; global using Microsoft.AspNetCore.Builder; -global using Microsoft.AspNetCore.Diagnostics.HealthChecks; global using Microsoft.AspNetCore.Hosting; global using Microsoft.AspNetCore.Http; -global using Microsoft.AspNetCore.Mvc.Filters; global using Microsoft.AspNetCore.Mvc; -global using Microsoft.AspNetCore; -global using Microsoft.EntityFrameworkCore.Design; global using Microsoft.EntityFrameworkCore; +global using Microsoft.EntityFrameworkCore.Design; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; -global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; -global using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; -global using Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus; global using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.Diagnostics.HealthChecks; -global using Microsoft.Extensions.Hosting; global using Microsoft.Extensions.Logging; -global using Microsoft.OpenApi.Models; -global using RabbitMQ.Client; -global using Swashbuckle.AspNetCore.SwaggerGen; -global using System.Collections.Generic; -global using System.ComponentModel.DataAnnotations; -global using System.Data.Common; -global using System.IdentityModel.Tokens.Jwt; -global using System.Linq; -global using System.Net.Http; -global using System.Net; -global using System.Reflection; -global using System.Text.Json; -global using System.Text; -global using System.Threading.Tasks; -global using System.Threading; -global using System; -global using Webhooks.API.Exceptions; -global using Webhooks.API.Infrastructure.ActionResult; +global using Services.Common; global using Webhooks.API.Infrastructure; global using Webhooks.API.IntegrationEvents; global using Webhooks.API.Model; global using Webhooks.API.Services; -global using Webhooks.API; diff --git a/src/Services/Webhooks/Webhooks.API/Infrastructure/ActionResult/InternalServerErrorObjectResult.cs b/src/Services/Webhooks/Webhooks.API/Infrastructure/ActionResult/InternalServerErrorObjectResult.cs deleted file mode 100644 index d71e8b55f..000000000 --- a/src/Services/Webhooks/Webhooks.API/Infrastructure/ActionResult/InternalServerErrorObjectResult.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Webhooks.API.Infrastructure.ActionResult; - -class InternalServerErrorObjectResult : ObjectResult -{ - public InternalServerErrorObjectResult(object error) : base(error) - { - StatusCode = StatusCodes.Status500InternalServerError; - } -} diff --git a/src/Services/Webhooks/Webhooks.API/Infrastructure/AuthorizeCheckOperationFilter.cs b/src/Services/Webhooks/Webhooks.API/Infrastructure/AuthorizeCheckOperationFilter.cs deleted file mode 100644 index 13b0fcbdb..000000000 --- a/src/Services/Webhooks/Webhooks.API/Infrastructure/AuthorizeCheckOperationFilter.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace Webhooks.API.Infrastructure; - -public class AuthorizeCheckOperationFilter : IOperationFilter -{ - public void Apply(OpenApiOperation operation, OperationFilterContext context) - { - // Check for authorize attribute - var hasAuthorize = context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType().Any() || - context.MethodInfo.GetCustomAttributes(true).OfType().Any(); - - if (!hasAuthorize) return; - - operation.Responses.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" }); - operation.Responses.TryAdd("403", new OpenApiResponse { Description = "Forbidden" }); - - var oAuthScheme = new OpenApiSecurityScheme - { - Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" } - }; - - operation.Security = new List - { - new() - { - [ oAuthScheme ] = new [] { "webhooksapi" } - } - }; - } -} diff --git a/src/Services/Webhooks/Webhooks.API/Infrastructure/HttpGlobalExceptionFilter.cs b/src/Services/Webhooks/Webhooks.API/Infrastructure/HttpGlobalExceptionFilter.cs deleted file mode 100644 index 58e2e1656..000000000 --- a/src/Services/Webhooks/Webhooks.API/Infrastructure/HttpGlobalExceptionFilter.cs +++ /dev/null @@ -1,58 +0,0 @@ -namespace Webhooks.API.Infrastructure; - -public class HttpGlobalExceptionFilter : IExceptionFilter -{ - private readonly IWebHostEnvironment _env; - private readonly ILogger _logger; - - public HttpGlobalExceptionFilter(IWebHostEnvironment env, ILogger logger) - { - _env = env; - _logger = logger; - } - - public void OnException(ExceptionContext context) - { - _logger.LogError(new EventId(context.Exception.HResult), - context.Exception, - context.Exception.Message); - - if (context.Exception.GetType() == typeof(WebhooksDomainException)) - { - var problemDetails = new ValidationProblemDetails() - { - Instance = context.HttpContext.Request.Path, - Status = StatusCodes.Status400BadRequest, - Detail = "Please refer to the errors property for additional details." - }; - - problemDetails.Errors.Add("DomainValidations", new[] { context.Exception.Message }); - - context.Result = new BadRequestObjectResult(problemDetails); - context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; - } - else - { - var json = new JsonErrorResponse - { - Messages = new[] { "An error occurred." } - }; - - if (_env.IsDevelopment()) - { - json.DeveloperMessage = context.Exception; - } - - context.Result = new InternalServerErrorObjectResult(json); - context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; - } - context.ExceptionHandled = true; - } - - private class JsonErrorResponse - { - public string[] Messages { get; set; } - - public object DeveloperMessage { get; set; } - } -} diff --git a/src/Services/Webhooks/Webhooks.API/Program.cs b/src/Services/Webhooks/Webhooks.API/Program.cs index 027814057..bb1e49190 100644 --- a/src/Services/Webhooks/Webhooks.API/Program.cs +++ b/src/Services/Webhooks/Webhooks.API/Program.cs @@ -1,19 +1,34 @@ -CreateWebHostBuilder(args).Build() - .MigrateDbContext((_, __) => { }) - .Run(); - - -IWebHostBuilder CreateWebHostBuilder(string[] args) => - WebHost.CreateDefaultBuilder(args) - .UseStartup() - .ConfigureAppConfiguration((builderContext, config) => - { - config.AddEnvironmentVariables(); - }) - .ConfigureLogging((hostingContext, builder) => - { - builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); - builder.AddConsole(); - builder.AddDebug(); - builder.AddAzureWebAppDiagnostics(); - }); \ No newline at end of file +var builder = WebApplication.CreateBuilder(args); + +builder.AddServiceDefaults(); + +builder.Services.AddControllers(); +builder.Services.AddDbContexts(builder.Configuration); +builder.Services.AddHealthChecks(builder.Configuration); +builder.Services.AddHttpClientServices(); +builder.Services.AddIntegrationServices(); + +builder.Services.AddTransient(); +builder.Services.AddTransient(); +builder.Services.AddTransient(); +builder.Services.AddTransient(); + +builder.Services.AddTransient(); +builder.Services.AddTransient(); +builder.Services.AddTransient(); + +var app = builder.Build(); + +app.UseServiceDefaults(); + +app.MapControllers(); + +var eventBus = app.Services.GetRequiredService(); + +eventBus.Subscribe(); +eventBus.Subscribe(); +eventBus.Subscribe(); + +app.Services.MigrateDbContext((_, __) => { }); + +await app.RunAsync(); diff --git a/src/Services/Webhooks/Webhooks.API/Properties/launchSettings.json b/src/Services/Webhooks/Webhooks.API/Properties/launchSettings.json index 533291599..1fdcd55ad 100644 --- a/src/Services/Webhooks/Webhooks.API/Properties/launchSettings.json +++ b/src/Services/Webhooks/Webhooks.API/Properties/launchSettings.json @@ -1,27 +1,12 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:62486", - "sslPort": 0 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, "Webhooks.API": { "commandName": "Project", "launchBrowser": true, + "applicationUrl": "http://localhost:5227", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:5000" + } }, "Docker": { "commandName": "Docker", diff --git a/src/Services/Webhooks/Webhooks.API/Startup.cs b/src/Services/Webhooks/Webhooks.API/Startup.cs deleted file mode 100644 index 10a923972..000000000 --- a/src/Services/Webhooks/Webhooks.API/Startup.cs +++ /dev/null @@ -1,321 +0,0 @@ -namespace Webhooks.API; -public class Startup -{ - public IConfiguration Configuration { get; } - - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - - public IServiceProvider ConfigureServices(IServiceCollection services) - { - services - .AddAppInsight(Configuration) - .AddCustomRouting(Configuration) - .AddCustomDbContext(Configuration) - .AddSwagger(Configuration) - .AddCustomHealthCheck(Configuration) - .AddHttpClientServices(Configuration) - .AddIntegrationServices(Configuration) - .AddEventBus(Configuration) - .AddCustomAuthentication(Configuration) - .AddSingleton() - .AddTransient() - .AddTransient() - .AddTransient() - .AddTransient(); - - var container = new ContainerBuilder(); - container.Populate(services); - return new AutofacServiceProvider(container.Build()); - } - - public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) - { - var pathBase = Configuration["PATH_BASE"]; - - if (!string.IsNullOrEmpty(pathBase)) - { - loggerFactory.CreateLogger("init").LogDebug("Using PATH BASE '{PathBase}'", pathBase); - app.UsePathBase(pathBase); - } - - app.UseRouting(); - app.UseCors("CorsPolicy"); - ConfigureAuth(app); - - 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") - }); - }); - - app.UseSwagger() - .UseSwaggerUI(c => - { - c.SwaggerEndpoint($"{(!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty)}/swagger/v1/swagger.json", "Webhooks.API V1"); - c.OAuthClientId("webhooksswaggerui"); - c.OAuthAppName("WebHooks Service Swagger UI"); - }); - - ConfigureEventBus(app); - } - - protected virtual void ConfigureAuth(IApplicationBuilder app) - { - app.UseAuthentication(); - app.UseAuthorization(); - } - - protected virtual void ConfigureEventBus(IApplicationBuilder app) - { - var eventBus = app.ApplicationServices.GetRequiredService(); - eventBus.Subscribe(); - eventBus.Subscribe(); - eventBus.Subscribe(); - } -} - -internal static class CustomExtensionMethods -{ - public static IServiceCollection AddAppInsight(this IServiceCollection services, IConfiguration configuration) - { - services.AddApplicationInsightsTelemetry(configuration); - services.AddApplicationInsightsKubernetesEnricher(); - - return services; - } - - public static IServiceCollection AddCustomRouting(this IServiceCollection services, IConfiguration configuration) - { - services.AddControllers(options => - { - options.Filters.Add(typeof(HttpGlobalExceptionFilter)); - }); - - services.AddCors(options => - { - options.AddPolicy("CorsPolicy", - builder => builder - .SetIsOriginAllowed((host) => true) - .AllowAnyMethod() - .AllowAnyHeader() - .AllowCredentials()); - }); - - return services; - } - - public static IServiceCollection AddCustomDbContext(this IServiceCollection services, IConfiguration configuration) - { - services.AddEntityFrameworkSqlServer() - .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 AddSwagger(this IServiceCollection services, IConfiguration configuration) - { - services.AddSwaggerGen(options => - { - options.SwaggerDoc("v1", new OpenApiInfo - { - Title = "eShopOnContainers - Webhooks HTTP API", - Version = "v1", - Description = "The Webhooks Microservice HTTP API. This is a simple webhooks CRUD registration entrypoint" - }); - - 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() - { - { "webhooks", "Webhooks API" } - } - } - } - }); - - options.OperationFilter(); - }); - - 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 eventBusSubscriptionManager = sp.GetRequiredService(); - string subscriptionName = configuration["SubscriptionClientName"]; - - return new EventBusServiceBus(serviceBusPersisterConnection, logger, - eventBusSubscriptionManager, sp, subscriptionName); - }); - - } - else - { - services.AddSingleton(sp => - { - var subscriptionClientName = configuration["SubscriptionClientName"]; - var rabbitMQPersistentConnection = sp.GetRequiredService(); - var logger = sp.GetRequiredService>(); - var eventBusSubscriptionManager = sp.GetRequiredService(); - - var retryCount = 5; - if (!string.IsNullOrEmpty(configuration["EventBusRetryCount"])) - { - retryCount = int.Parse(configuration["EventBusRetryCount"]); - } - - return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, sp, eventBusSubscriptionManager, subscriptionClientName, retryCount); - }); - } - - services.AddSingleton(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - - return services; - } - - public static IServiceCollection AddCustomHealthCheck(this IServiceCollection services, IConfiguration configuration) - { - var hcBuilder = services.AddHealthChecks(); - - hcBuilder - .AddCheck("self", () => HealthCheckResult.Healthy()) - .AddSqlServer( - configuration["ConnectionString"], - name: "WebhooksApiDb-check", - tags: new string[] { "webhooksdb" }); - - return services; - } - - public static IServiceCollection AddHttpClientServices(this IServiceCollection services, IConfiguration configuration) - { - services.AddSingleton(); - services.AddHttpClient("extendedhandlerlifetime").SetHandlerLifetime(Timeout.InfiniteTimeSpan); - //add http client services - services.AddHttpClient("GrantClient") - .SetHandlerLifetime(TimeSpan.FromMinutes(5)); - return services; - } - - public static IServiceCollection AddIntegrationServices(this IServiceCollection services, IConfiguration configuration) - { - services.AddTransient>( - sp => (DbConnection c) => new IntegrationEventLogService(c)); - - if (configuration.GetValue("AzureServiceBusEnabled")) - { - services.AddSingleton(sp => - { - var subscriptionClientName = configuration["SubscriptionClientName"]; - return new DefaultServiceBusPersisterConnection(configuration["EventBusConnection"]); - }); - } - 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 AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration) - { - // prevent from mapping "sub" claim to nameidentifier. - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); - - var identityUrl = configuration.GetValue("IdentityUrl"); - - services.AddAuthentication(options => - { - options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; - - }).AddJwtBearer(options => - { - options.Authority = identityUrl; - options.RequireHttpsMetadata = false; - options.Audience = "webhooks"; - 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", "webhooks"); - }); - }); - return services; - } -} diff --git a/src/Services/Webhooks/Webhooks.API/Webhooks.API.csproj b/src/Services/Webhooks/Webhooks.API/Webhooks.API.csproj index 1bcdc8d69..979154074 100644 --- a/src/Services/Webhooks/Webhooks.API/Webhooks.API.csproj +++ b/src/Services/Webhooks/Webhooks.API/Webhooks.API.csproj @@ -2,34 +2,17 @@ net7.0 - InProcess Linux - $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; - false - true - - - - - - - - - - - - - - + diff --git a/src/Services/Webhooks/Webhooks.API/appsettings.Development.json b/src/Services/Webhooks/Webhooks.API/appsettings.Development.json index f4f8f9d26..ce8d5a4ff 100644 --- a/src/Services/Webhooks/Webhooks.API/appsettings.Development.json +++ b/src/Services/Webhooks/Webhooks.API/appsettings.Development.json @@ -6,5 +6,7 @@ "Microsoft": "Information" } }, - "ConnectionString": "Server=tcp:127.0.0.1,5433;Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word;TrustServerCertificate=true" + "ConnectionStrings": { + "WebHooksDB": "Server=tcp:127.0.0.1,5433;Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word;TrustServerCertificate=true" + } } diff --git a/src/Services/Webhooks/Webhooks.API/appsettings.json b/src/Services/Webhooks/Webhooks.API/appsettings.json index 200ea4c96..8d103c53e 100644 --- a/src/Services/Webhooks/Webhooks.API/appsettings.json +++ b/src/Services/Webhooks/Webhooks.API/appsettings.json @@ -1,10 +1,43 @@ { "Logging": { "LogLevel": { - "Default": "Warning" + "Default": "Information", + "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", - "SubscriptionClientName": "Webhooks", - "EventBusRetryCount": 5 + "OpenApi": { + "Endpoint": { + "Name": "Webhooks.API V1" + }, + "Document": { + "Description": "The Webhooks Microservice HTTP API. This is a simple webhooks CRUD registration entrypoint", + "Title": "eShopOnContainers - Webhooks HTTP API", + "Version": "v1" + }, + "Auth": { + "ClientId": "webhooksswaggerui", + "AppName": "WebHooks Service Swagger UI" + } + }, + "ConnectionStrings": { + "EventBus": "localhost" + }, + "EventBus": { + "SubscriptionClientName": "Webhooks", + "RetryCount": 5 + }, + "ApplicationInsights": { + "InstrumentationKey": "" + }, + "Identity": { + "Url": "http://localhost:5223", + "Audience": "webhooks", + "Scopes": { + "webhooks": "Webhooks API" + } + }, + "UseCustomizationData": false, + "GracePeriodTime": "1", + "CheckUpdateTime": "30000" } diff --git a/src/Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj b/src/Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj index 712a3ecfb..4e53734c4 100644 --- a/src/Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj +++ b/src/Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj @@ -10,44 +10,12 @@ false - - - - - - - - - - - - PreserveNewest - - - PreserveNewest - - - Always - - - Always - - - Always - - - Always - - - PreserveNewest - - - + - + all runtime; build; native; contentfiles; analyzers @@ -55,17 +23,14 @@ + + + - - - PreserveNewest - - - diff --git a/src/Tests/Services/Application.FunctionalTests/Extensions/HttpClientExtensions.cs b/src/Tests/Services/Application.FunctionalTests/Extensions/HttpClientExtensions.cs deleted file mode 100644 index b17edce91..000000000 --- a/src/Tests/Services/Application.FunctionalTests/Extensions/HttpClientExtensions.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace FunctionalTests.Extensions; - -static class HttpClientExtensions -{ - public static HttpClient CreateIdempotentClient(this TestServer server) - { - var client = server.CreateClient(); - client.DefaultRequestHeaders.Add("x-requestid", Guid.NewGuid().ToString()); - return client; - } -} diff --git a/src/Tests/Services/Application.FunctionalTests/GlobalUsings.cs b/src/Tests/Services/Application.FunctionalTests/GlobalUsings.cs index dd46c8aca..a56d1d34b 100644 --- a/src/Tests/Services/Application.FunctionalTests/GlobalUsings.cs +++ b/src/Tests/Services/Application.FunctionalTests/GlobalUsings.cs @@ -1,22 +1,8 @@ -global using Microsoft.AspNetCore.TestHost; -global using System; +global using System; global using System.Net.Http; global using Microsoft.AspNetCore.Http; global using System.Security.Claims; global using System.Threading.Tasks; -global using Microsoft.AspNetCore.Hosting; -global using Microsoft.Extensions.Configuration; -global using System.IO; -global using System.Reflection; -global using FunctionalTests.Middleware; -global using Microsoft.AspNetCore.Builder; -global using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; -global using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; -global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.Logging; -global using Microsoft.Extensions.Options; -global using FunctionalTests.Extensions; -global using FunctionalTests.Services.Basket; global using Microsoft.eShopOnContainers.Services.Basket.API.Model; global using Microsoft.eShopOnContainers.WebMVC.ViewModels; global using System.Text.Json; @@ -25,7 +11,4 @@ global using System.Linq; global using System.Text; global using WebMVC.Services.ModelDTOs; global using Xunit; -global using Microsoft.eShopOnContainers.Services.Ordering.API; -global using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure; -global using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure; -global using FunctionalTests.Services.Catalog; + diff --git a/src/Tests/Services/Application.FunctionalTests/Services/Basket/BasketScenariosBase.cs b/src/Tests/Services/Application.FunctionalTests/Services/Basket/BasketScenariosBase.cs deleted file mode 100644 index 0cb668b8a..000000000 --- a/src/Tests/Services/Application.FunctionalTests/Services/Basket/BasketScenariosBase.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace FunctionalTests.Services.Basket; - -public class BasketScenariosBase -{ - private const string ApiUrlBase = "api/v1/basket"; - - - public TestServer CreateServer() - { - var path = Assembly.GetAssembly(typeof(BasketScenariosBase)) - .Location; - - var hostBuilder = new WebHostBuilder() - .UseContentRoot(Path.GetDirectoryName(path)) - .ConfigureAppConfiguration(cb => - { - cb.AddJsonFile("Services/Basket/appsettings.json", optional: false) - .AddEnvironmentVariables(); - }); - - return new TestServer(hostBuilder); - } - - public static class Get - { - public static string GetBasket(int id) - { - return $"{ApiUrlBase}/{id}"; - } - - public static string GetBasketByCustomer(string customerId) - { - return $"{ApiUrlBase}/{customerId}"; - } - } - - public static class Post - { - public static string CreateBasket = $"{ApiUrlBase}/"; - public static string CheckoutOrder = $"{ApiUrlBase}/checkout"; - } -} diff --git a/src/Tests/Services/Application.FunctionalTests/Services/Basket/appsettings.json b/src/Tests/Services/Application.FunctionalTests/Services/Basket/appsettings.json deleted file mode 100644 index 3ed325899..000000000 --- a/src/Tests/Services/Application.FunctionalTests/Services/Basket/appsettings.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "Logging": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Debug", - "System": "Information", - "Microsoft": "Information" - } - }, - "IdentityUrl": "http://localhost:5105", - "IdentityUrlExternal": "http://localhost:5105", - "ConnectionString": "127.0.0.1", - "isTest": "true", - "EventBusConnection": "localhost", - "SubscriptionClientName": "Basket" -} diff --git a/src/Tests/Services/Application.FunctionalTests/Services/Catalog/CatalogScenariosBase.cs b/src/Tests/Services/Application.FunctionalTests/Services/Catalog/CatalogScenariosBase.cs deleted file mode 100644 index 0fcf32e3c..000000000 --- a/src/Tests/Services/Application.FunctionalTests/Services/Catalog/CatalogScenariosBase.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace FunctionalTests.Services.Catalog; -using Microsoft.eShopOnContainers.Services.Catalog.API; - -public class CatalogScenariosBase -{ - public TestServer CreateServer() - { - var path = Assembly.GetAssembly(typeof(CatalogScenariosBase)) - .Location; - - var hostBuilder = new WebHostBuilder() - .UseContentRoot(Path.GetDirectoryName(path)) - .ConfigureAppConfiguration(cb => - { - cb.AddJsonFile("Services/Catalog/appsettings.json", optional: false) - .AddEnvironmentVariables(); - }); - - var testServer = new TestServer(hostBuilder); - - testServer.Host - .MigrateDbContext((context, services) => - { - var env = services.GetService(); - var settings = services.GetService>(); - var logger = services.GetService>(); - - new CatalogContextSeed() - .SeedAsync(context, env, settings, logger) - .Wait(); - }) - .MigrateDbContext((_, __) => { }); - - return testServer; - } - - public static class Get - { - public static string Orders = "api/v1/orders"; - - public static string Items = "api/v1/catalog/items"; - - public static string ProductByName(string name) - { - return $"api/v1/catalog/items/withname/{name}"; - } - } - - public static class Put - { - public static string UpdateCatalogProduct = "api/v1/catalog/items"; - } -} diff --git a/src/Tests/Services/Application.FunctionalTests/Services/Catalog/appsettings.json b/src/Tests/Services/Application.FunctionalTests/Services/Catalog/appsettings.json deleted file mode 100644 index 708598b8a..000000000 --- a/src/Tests/Services/Application.FunctionalTests/Services/Catalog/appsettings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "ConnectionString": "Server=tcp:127.0.0.1,5433;Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word;Encrypt=False;TrustServerCertificate=true", - "ExternalCatalogBaseUrl": "http://localhost:5101", - "IdentityUrl": "http://localhost:5105", - "isTest": "true", - "EventBusConnection": "localhost", - "PicBaseUrl": "http://localhost:5101/api/v1/catalog/items/[0]/pic/", - "SubscriptionClientName": "Catalog" -} diff --git a/src/Tests/Services/Application.FunctionalTests/Services/IntegrationEventsScenarios.cs b/src/Tests/Services/Application.FunctionalTests/Services/IntegrationEventsScenarios.cs index 86466766f..f7b348631 100644 --- a/src/Tests/Services/Application.FunctionalTests/Services/IntegrationEventsScenarios.cs +++ b/src/Tests/Services/Application.FunctionalTests/Services/IntegrationEventsScenarios.cs @@ -1,4 +1,7 @@ namespace FunctionalTests.Services; + +using global::Basket.FunctionalTests.Base; +using global::Catalog.FunctionalTests; using Microsoft.eShopOnContainers.Services.Basket.API.Model; using Microsoft.eShopOnContainers.Services.Catalog.API.Model; using Microsoft.eShopOnContainers.Services.Catalog.API.ViewModel; @@ -12,7 +15,7 @@ public class IntegrationEventsScenarios string userId = "JohnId"; using var catalogServer = new CatalogScenariosBase().CreateServer(); - using var basketServer = new BasketScenariosBase().CreateServer(); + using var basketServer = new BasketScenarioBase().CreateServer(); var catalogClient = catalogServer.CreateClient(); var basketClient = basketServer.CreateClient(); @@ -22,7 +25,7 @@ public class IntegrationEventsScenarios // AND a user basket filled with products var basket = ComposeBasket(userId, originalCatalogProducts.Data.Take(3)); var res = await basketClient.PostAsync( - BasketScenariosBase.Post.CreateBasket, + BasketScenarioBase.Post.Basket, new StringContent(JsonSerializer.Serialize(basket), UTF8Encoding.UTF8, "application/json") ); @@ -60,7 +63,7 @@ public class IntegrationEventsScenarios while (continueLoop && counter < 20) { //get the basket and verify that the price of the modified product is updated - var basketGetResponse = await basketClient.GetAsync(BasketScenariosBase.Get.GetBasketByCustomer(userId)); + var basketGetResponse = await basketClient.GetAsync(BasketScenarioBase.Get.GetBasketByCustomer(userId)); var basketUpdated = JsonSerializer.Deserialize(await basketGetResponse.Content.ReadAsStringAsync(), new JsonSerializerOptions { PropertyNameCaseInsensitive = true @@ -84,7 +87,7 @@ public class IntegrationEventsScenarios private async Task> GetCatalogAsync(HttpClient catalogClient) { - var response = await catalogClient.GetAsync(CatalogScenariosBase.Get.Items); + var response = await catalogClient.GetAsync(CatalogScenariosBase.Get.Items(paginated: false)); var items = await response.Content.ReadAsStringAsync(); return JsonSerializer.Deserialize>(items, new JsonSerializerOptions { diff --git a/src/Tests/Services/Application.FunctionalTests/Services/Ordering/OrderingScenariosBase.cs b/src/Tests/Services/Application.FunctionalTests/Services/Ordering/OrderingScenariosBase.cs deleted file mode 100644 index 3d419eb83..000000000 --- a/src/Tests/Services/Application.FunctionalTests/Services/Ordering/OrderingScenariosBase.cs +++ /dev/null @@ -1,63 +0,0 @@ -namespace FunctionalTests.Services.Ordering; - -public class OrderingScenariosBase -{ - public TestServer CreateServer() - { - var path = Assembly.GetAssembly(typeof(OrderingScenariosBase)) - .Location; - - var hostBuilder = new WebHostBuilder() - .UseContentRoot(Path.GetDirectoryName(path)) - .ConfigureAppConfiguration(cb => - { - cb.AddJsonFile("Services/Ordering/appsettings.json", optional: false) - .AddEnvironmentVariables(); - }); - - var testServer = new TestServer(hostBuilder); - - testServer.Host - .MigrateDbContext((context, services) => - { - var env = services.GetService(); - var settings = services.GetService>(); - var logger = services.GetService>(); - - new OrderingContextSeed() - .SeedAsync(context, env, settings, logger) - .Wait(); - }) - .MigrateDbContext((_, __) => { }); - - return testServer; - } - - public static class Get - { - public static string Orders = "api/v1/orders"; - - public static string OrderBy(int id) - { - return $"api/v1/orders/{id}"; - } - } - - public static class Post - { - public static string AddNewOrder = "api/v1/orders/new"; - } - - public static class Put - { - public static string CancelOrder = "api/v1/orders/cancel"; - } - - public static class Delete - { - public static string OrderBy(int id) - { - return $"api/v1/orders/{id}"; - } - } -} diff --git a/src/Tests/Services/Application.FunctionalTests/Services/Ordering/appsettings.json b/src/Tests/Services/Application.FunctionalTests/Services/Ordering/appsettings.json deleted file mode 100644 index 18efe0185..000000000 --- a/src/Tests/Services/Application.FunctionalTests/Services/Ordering/appsettings.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;Encrypt=False;TrustServerCertificate=true", - "ExternalCatalogBaseUrl": "http://localhost:5101", - "IdentityUrl": "http://localhost:5105", - "isTest": "true", - "EventBusConnection": "localhost", - "CheckUpdateTime": "30000", - "GracePeriodTime": "1", - "SubscriptionClientName": "Ordering", - "IdentityUrlExternal": "http://localhost:5105" -} diff --git a/src/Tests/Services/Application.FunctionalTests/Services/Ordering/OrderingScenarios.cs b/src/Tests/Services/Application.FunctionalTests/Services/OrderingScenarios.cs similarity index 74% rename from src/Tests/Services/Application.FunctionalTests/Services/Ordering/OrderingScenarios.cs rename to src/Tests/Services/Application.FunctionalTests/Services/OrderingScenarios.cs index 46291b81a..e006a6998 100644 --- a/src/Tests/Services/Application.FunctionalTests/Services/Ordering/OrderingScenarios.cs +++ b/src/Tests/Services/Application.FunctionalTests/Services/OrderingScenarios.cs @@ -1,31 +1,46 @@ -namespace FunctionalTests.Services.Ordering; +using Basket.FunctionalTests.Base; +using Ordering.FunctionalTests; -public class OrderingScenarios : OrderingScenariosBase +namespace FunctionalTests.Services.Ordering; + +public class OrderingScenarios { [Fact] public async Task Cancel_basket_and_check_order_status_cancelled() { - using var orderServer = new OrderingScenariosBase().CreateServer(); - using var basketServer = new BasketScenariosBase().CreateServer(); + using var orderServer = new OrderingScenarioBase().CreateServer(); + using var basketServer = new BasketScenarioBase().CreateServer(); // Expected data var cityExpected = $"city-{Guid.NewGuid()}"; var orderStatusExpected = "cancelled"; - var basketClient = basketServer.CreateIdempotentClient(); - var orderClient = orderServer.CreateIdempotentClient(); + var basketClient = basketServer.CreateClient(); + var orderClient = orderServer.CreateClient(); // GIVEN a basket is created - var contentBasket = new StringContent(BuildBasket(), UTF8Encoding.UTF8, "application/json"); - await basketClient.PostAsync(BasketScenariosBase.Post.CreateBasket, contentBasket); + var contentBasket = new StringContent(BuildBasket(), UTF8Encoding.UTF8, "application/json") + { + Headers = { { "x-requestid", Guid.NewGuid().ToString() } } + }; + await basketClient.PostAsync(BasketScenarioBase.Post.Basket, contentBasket); // AND basket checkout is sent - await basketClient.PostAsync(BasketScenariosBase.Post.CheckoutOrder, new StringContent(BuildCheckout(cityExpected), UTF8Encoding.UTF8, "application/json")); + await basketClient.PostAsync( + BasketScenarioBase.Post.CheckoutOrder, + new StringContent(BuildCheckout(cityExpected), UTF8Encoding.UTF8, "application/json") + { + Headers = { { "x-requestid", Guid.NewGuid().ToString() } } + }); // WHEN Order is created in Ordering.api var newOrder = await TryGetNewOrderCreated(cityExpected, orderClient); + Assert.NotNull(newOrder); // AND Order is cancelled in Ordering.api - await orderClient.PutAsync(OrderingScenariosBase.Put.CancelOrder, new StringContent(BuildCancelOrder(newOrder.OrderNumber), UTF8Encoding.UTF8, "application/json")); + await orderClient.PutAsync(OrderingScenarioBase.Put.CancelOrder, new StringContent(BuildCancelOrder(newOrder.OrderNumber), UTF8Encoding.UTF8, "application/json") + { + Headers = { { "x-requestid", Guid.NewGuid().ToString() } } + }); // AND the requested order is retrieved var order = await TryGetOrder(newOrder.OrderNumber, orderClient); @@ -36,7 +51,7 @@ public class OrderingScenarios : OrderingScenariosBase async Task TryGetOrder(string orderNumber, HttpClient orderClient) { - var ordersGetResponse = await orderClient.GetStringAsync(OrderingScenariosBase.Get.Orders); + var ordersGetResponse = await orderClient.GetStringAsync(OrderingScenarioBase.Get.Orders); var orders = JsonSerializer.Deserialize>(ordersGetResponse, new JsonSerializerOptions { PropertyNameCaseInsensitive = true @@ -53,7 +68,7 @@ public class OrderingScenarios : OrderingScenariosBase while (counter < 20) { //get the orders and verify that the new order has been created - var ordersGetResponse = await orderClient.GetStringAsync(OrderingScenariosBase.Get.Orders); + var ordersGetResponse = await orderClient.GetStringAsync(OrderingScenarioBase.Get.Orders); var orders = JsonSerializer.Deserialize>(ordersGetResponse, new JsonSerializerOptions { PropertyNameCaseInsensitive = true @@ -68,7 +83,7 @@ public class OrderingScenarios : OrderingScenariosBase var lastOrder = orders.OrderByDescending(o => o.Date).First(); int.TryParse(lastOrder.OrderNumber, out int id); - var orderDetails = await orderClient.GetStringAsync(OrderingScenariosBase.Get.OrderBy(id)); + var orderDetails = await orderClient.GetStringAsync(OrderingScenarioBase.Get.OrderBy(id)); order = JsonSerializer.Deserialize(orderDetails, new JsonSerializerOptions { PropertyNameCaseInsensitive = true diff --git a/src/Web/WebMVC/AppSettings.cs b/src/Web/WebMVC/AppSettings.cs index 8879abf2e..b1a8b6851 100644 --- a/src/Web/WebMVC/AppSettings.cs +++ b/src/Web/WebMVC/AppSettings.cs @@ -3,6 +3,5 @@ public class AppSettings { public string PurchaseUrl { get; set; } - public string SignalrHubUrl { get; set; } public bool UseCustomizationData { get; set; } } diff --git a/src/Web/WebMVC/Controllers/AccountController.cs b/src/Web/WebMVC/Controllers/AccountController.cs index 78282f9a9..9f5a1dc44 100644 --- a/src/Web/WebMVC/Controllers/AccountController.cs +++ b/src/Web/WebMVC/Controllers/AccountController.cs @@ -11,17 +11,9 @@ public class AccountController : Controller } [Authorize(AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme)] - public async Task SignIn(string returnUrl) + public IActionResult SignIn(string returnUrl) { - var user = User as ClaimsPrincipal; - var token = await HttpContext.GetTokenAsync("access_token"); - - _logger.LogInformation("----- User {@User} authenticated into {AppName}", user, Program.AppName); - - if (token != null) - { - ViewData["access_token"] = token; - } + _logger.LogInformation("User {@User} authenticated", User.Identity.Name); // "Catalog" because UrlHelper doesn't support nameof() for controllers // https://github.com/aspnet/Mvc/issues/5853 diff --git a/src/Web/WebMVC/Controllers/TestController.cs b/src/Web/WebMVC/Controllers/TestController.cs deleted file mode 100644 index 680099cf4..000000000 --- a/src/Web/WebMVC/Controllers/TestController.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace WebMVC.Controllers; - -class TestPayload -{ - public int CatalogItemId { get; set; } - - public string BasketId { get; set; } - - public int Quantity { get; set; } -} - -[Authorize] -public class TestController : Controller -{ - private readonly IHttpClientFactory _client; - private readonly IIdentityParser _appUserParser; - - public TestController(IHttpClientFactory client, IIdentityParser identityParser) - { - _client = client; - _appUserParser = identityParser; - } - - public async Task Ocelot() - { - var url = "http://apigw/shopping/api/v1/basket/items"; - - var payload = new TestPayload() - { - CatalogItemId = 1, - Quantity = 1, - BasketId = _appUserParser.Parse(User).Id - }; - - var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json"); - - - var response = await _client.CreateClient(nameof(IBasketService)) - .PostAsync(url, content); - - if (response.IsSuccessStatusCode) - { - var str = await response.Content.ReadAsStringAsync(); - - return Ok(str); - } - else - { - return Ok(new { response.StatusCode, response.ReasonPhrase }); - } - } -} diff --git a/src/Web/WebMVC/Dockerfile b/src/Web/WebMVC/Dockerfile index c47b7737f..ee0adb5b2 100644 --- a/src/Web/WebMVC/Dockerfile +++ b/src/Web/WebMVC/Dockerfile @@ -32,6 +32,7 @@ COPY "Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj" COPY "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" COPY "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" COPY "Services/Payment/Payment.API/Payment.API.csproj" "Services/Payment/Payment.API/Payment.API.csproj" +COPY "Services/Services.Common/Services.Common.csproj" "Services/Services.Common/Services.Common.csproj" COPY "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" COPY "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" COPY "Web/WebhookClient/WebhookClient.csproj" "Web/WebhookClient/WebhookClient.csproj" diff --git a/src/Web/WebMVC/Extensions/Extensions.cs b/src/Web/WebMVC/Extensions/Extensions.cs new file mode 100644 index 000000000..4e9a87410 --- /dev/null +++ b/src/Web/WebMVC/Extensions/Extensions.cs @@ -0,0 +1,110 @@ +// Fix samesite issue when running eShop from docker-compose locally as by default http protocol is being used +// Refer to https://github.com/dotnet-architecture/eShopOnContainers/issues/1391 +using Yarp.ReverseProxy.Forwarder; + +internal static class Extensions +{ + public static void AddHealthChecks(this IServiceCollection services, IConfiguration configuration) + { + services.AddHealthChecks() + .AddUrlGroup(_ => new Uri(configuration["IdentityUrlHC"]), name: "identityapi-check", tags: new string[] { "identityapi" }); + } + + public static void AddApplicationServices(this IServiceCollection services, IConfiguration configuration) + { + services.Configure(configuration); + + if (configuration["DPConnectionString"] is string redisConnection) + { + services.AddDataProtection(opts => + { + opts.ApplicationDiscriminator = "eshop.webmvc"; + }) + .PersistKeysToStackExchangeRedis(ConnectionMultiplexer.Connect(redisConnection), "DataProtection-Keys"); + } + } + + // Adds all Http client services + public static void AddHttpClientServices(this IServiceCollection services) + { + // Register delegating handlers + services.AddTransient() + .AddTransient(); + + // Add http client services + services.AddHttpClient() + .SetHandlerLifetime(TimeSpan.FromMinutes(5)) //Sample. Default lifetime is 2 minutes + .AddHttpMessageHandler(); + + services.AddHttpClient(); + + services.AddHttpClient() + .AddHttpMessageHandler() + .AddHttpMessageHandler(); + + // Add custom application services + services.AddTransient, IdentityParser>(); + } + + public static void AddAuthenticationServices(this IServiceCollection services, IConfiguration configuration) + { + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); + + var identityUrl = configuration.GetRequiredValue("IdentityUrl"); + var callBackUrl = configuration.GetRequiredValue("CallBackUrl"); + var sessionCookieLifetime = configuration.GetValue("SessionCookieLifetimeMinutes", 60); + + // Add Authentication services + services.AddAuthentication(options => + { + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; + }) + .AddCookie(options => options.ExpireTimeSpan = TimeSpan.FromMinutes(sessionCookieLifetime)) + .AddOpenIdConnect(options => + { + options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.Authority = identityUrl; + options.SignedOutRedirectUri = callBackUrl; + options.ClientId = "mvc"; + options.ClientSecret = "secret"; + options.ResponseType = "code"; + options.SaveTokens = true; + options.GetClaimsFromUserInfoEndpoint = true; + options.RequireHttpsMetadata = false; + options.Scope.Add("openid"); + options.Scope.Add("profile"); + options.Scope.Add("orders"); + options.Scope.Add("basket"); + options.Scope.Add("webshoppingagg"); + options.Scope.Add("orders.signalrhub"); + options.Scope.Add("webhooks"); + }); + } + + public static IEndpointConventionBuilder MapForwardSignalR(this WebApplication app) + { + // Forward the SignalR traffic to the bff + var destination = app.Configuration.GetRequiredValue("PurchaseUrl"); + var authTransformer = new BffAuthTransformer(); + var requestConfig = new ForwarderRequestConfig(); + + return app.MapForwarder("/hub/notificationhub/{**any}", destination, requestConfig, authTransformer); + } + + private sealed class BffAuthTransformer : HttpTransformer + { + public override async ValueTask TransformRequestAsync(HttpContext httpContext, HttpRequestMessage proxyRequest, string destinationPrefix, CancellationToken cancellationToken) + { + // Set the access token as a bearer token for the outgoing request + var accessToken = await httpContext.GetTokenAsync("access_token"); + + if (accessToken is not null) + { + proxyRequest.Headers.Authorization = new("Bearer", accessToken); + } + + await base.TransformRequestAsync(httpContext, proxyRequest, destinationPrefix, cancellationToken); + } + } +} diff --git a/src/Web/WebMVC/Extensions/HttpClientExtensions.cs b/src/Web/WebMVC/Extensions/HttpClientExtensions.cs deleted file mode 100644 index d1b11c9f7..000000000 --- a/src/Web/WebMVC/Extensions/HttpClientExtensions.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace Microsoft.eShopOnContainers.WebMVC.Extensions; - -public static class HttpClientExtensions -{ - public static void SetBasicAuthentication(this HttpClient client, string userName, string password) => - client.DefaultRequestHeaders.Authorization = new BasicAuthenticationHeaderValue(userName, password); - - public static void SetToken(this HttpClient client, string scheme, string token) => - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(scheme, token); - - public static void SetBearerToken(this HttpClient client, string token) => - client.SetToken(JwtConstants.TokenType, token); -} - -public class BasicAuthenticationHeaderValue : AuthenticationHeaderValue -{ - public BasicAuthenticationHeaderValue(string userName, string password) - : base("Basic", EncodeCredential(userName, password)) - { } - - private static string EncodeCredential(string userName, string password) - { - Encoding encoding = Encoding.GetEncoding("iso-8859-1"); - string credential = String.Format("{0}:{1}", userName, password); - - return Convert.ToBase64String(encoding.GetBytes(credential)); - } -} diff --git a/src/Web/WebMVC/Extensions/SessionExtensions.cs b/src/Web/WebMVC/Extensions/SessionExtensions.cs deleted file mode 100644 index c7d87034d..000000000 --- a/src/Web/WebMVC/Extensions/SessionExtensions.cs +++ /dev/null @@ -1,16 +0,0 @@ -public static class SessionExtensions -{ - public static void SetObject(this ISession session, string key, object value) => - session.SetString(key, JsonSerializer.Serialize(value)); - - public static T GetObject(this ISession session, string key) - { - var value = session.GetString(key); - - return value == null ? default(T) : JsonSerializer.Deserialize(value, new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }); - } -} - diff --git a/src/Web/WebMVC/Infrastructure/HttpClientAuthorizationDelegatingHandler.cs b/src/Web/WebMVC/Infrastructure/HttpClientAuthorizationDelegatingHandler.cs deleted file mode 100644 index e9f21a4f0..000000000 --- a/src/Web/WebMVC/Infrastructure/HttpClientAuthorizationDelegatingHandler.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace WebMVC.Infrastructure; - -public class HttpClientAuthorizationDelegatingHandler - : DelegatingHandler -{ - private readonly IHttpContextAccessor _httpContextAccessor; - - public HttpClientAuthorizationDelegatingHandler(IHttpContextAccessor httpContextAccessor) - { - _httpContextAccessor = httpContextAccessor; - } - - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - var authorizationHeader = _httpContextAccessor.HttpContext - .Request.Headers["Authorization"]; - - if (!string.IsNullOrEmpty(authorizationHeader)) - { - request.Headers.Add("Authorization", new List() { authorizationHeader }); - } - - var token = await GetToken(); - - if (token != null) - { - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - } - - return await base.SendAsync(request, cancellationToken); - } - - async Task GetToken() - { - const string ACCESS_TOKEN = "access_token"; - - return await _httpContextAccessor.HttpContext - .GetTokenAsync(ACCESS_TOKEN); - } -} diff --git a/src/Web/WebMVC/Infrastructure/WebContextSeed.cs b/src/Web/WebMVC/Infrastructure/WebContextSeed.cs index cc03c1797..c20f39690 100644 --- a/src/Web/WebMVC/Infrastructure/WebContextSeed.cs +++ b/src/Web/WebMVC/Infrastructure/WebContextSeed.cs @@ -1,14 +1,12 @@ namespace WebMVC.Infrastructure; -using Serilog; public class WebContextSeed { public static void Seed(IApplicationBuilder applicationBuilder, IWebHostEnvironment env) { - var log = Log.Logger; - - var settings = (AppSettings)applicationBuilder + var settings = applicationBuilder .ApplicationServices.GetRequiredService>().Value; + var log = applicationBuilder.ApplicationServices.GetRequiredService>(); var useCustomizationData = settings.UseCustomizationData; var contentRootPath = env.ContentRootPath; @@ -29,7 +27,7 @@ public class WebContextSeed string overrideCssFile = Path.Combine(contentRootPath, "Setup", "override.css"); if (!File.Exists(overrideCssFile)) { - log.Error("Override css file '{FileName}' does not exists.", overrideCssFile); + log.LogError("Override css file '{FileName}' does not exists.", overrideCssFile); return; } @@ -38,7 +36,7 @@ public class WebContextSeed } catch (Exception ex) { - log.Error(ex, "EXCEPTION ERROR: {Message}", ex.Message); + log.LogError(ex, "Error getting preconfigured css"); } } @@ -49,7 +47,7 @@ public class WebContextSeed string imagesZipFile = Path.Combine(contentRootPath, "Setup", "images.zip"); if (!File.Exists(imagesZipFile)) { - log.Error("Zip file '{ZipFileName}' does not exists.", imagesZipFile); + log.LogError("Zip file '{ZipFileName}' does not exists.", imagesZipFile); return; } @@ -70,14 +68,14 @@ public class WebContextSeed } else { - log.Warning("Skipped file '{FileName}' in zipfile '{ZipFileName}'", entry.Name, imagesZipFile); + log.LogWarning("Skipped file '{FileName}' in zip file '{ZipFileName}'", entry.Name, imagesZipFile); } } } catch (Exception ex) { - log.Error(ex, "EXCEPTION ERROR: {Message}", ex.Message); + log.LogError(ex, "Error getting preconfigured images"); } } -} \ No newline at end of file +} diff --git a/src/Web/WebMVC/Program.cs b/src/Web/WebMVC/Program.cs index ae56982a6..34d407fec 100644 --- a/src/Web/WebMVC/Program.cs +++ b/src/Web/WebMVC/Program.cs @@ -1,38 +1,20 @@ var builder = WebApplication.CreateBuilder(args); -builder.Services.AddControllersWithViews(); - -AddApplicationInsights(builder); -AddHealthChecks(builder); -AddCustomMvc(builder); -AddHttpClientServices(builder); -AddCustomAuthentication(builder); -builder.WebHost.CaptureStartupErrors(false); -builder.Host.UseSerilog(CreateSerilogLogger(builder.Configuration)); +builder.AddServiceDefaults(); -var app = builder.Build(); +builder.Services.AddHttpForwarder(); +builder.Services.AddControllersWithViews(); -JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); -if (app.Environment.IsDevelopment()) -{ - app.UseDeveloperExceptionPage(); -} -else -{ - app.UseExceptionHandler("/Error"); -} +builder.Services.AddHealthChecks(builder.Configuration); +builder.Services.AddApplicationServices(builder.Configuration); +builder.Services.AddAuthenticationServices(builder.Configuration); +builder.Services.AddHttpClientServices(); -var pathBase = builder.Configuration["PATH_BASE"]; +var app = builder.Build(); -if (!string.IsNullOrEmpty(pathBase)) -{ - app.UsePathBase(pathBase); -} +app.UseServiceDefaults(); app.UseStaticFiles(); -app.UseSession(); - -WebContextSeed.Seed(app, app.Environment); // Fix samesite issue when running eShop from docker-compose locally as by default http protocol is being used // Refer to https://github.com/dotnet-architecture/eShopOnContainers/issues/1391 @@ -46,132 +28,8 @@ app.UseAuthorization(); app.MapControllerRoute("default", "{controller=Catalog}/{action=Index}/{id?}"); app.MapControllerRoute("defaultError", "{controller=Error}/{action=Error}"); app.MapControllers(); -app.MapHealthChecks("/liveness", new HealthCheckOptions -{ - Predicate = r => r.Name.Contains("self") -}); -app.MapHealthChecks("/hc", new HealthCheckOptions() -{ - Predicate = _ => true, - ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse -}); - -await app.RunAsync(); - -Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) -{ - var seqServerUrl = configuration["Serilog:SeqServerUrl"]; - var logstashUrl = configuration["Serilog:LogstashgUrl"]; - var cfg = new LoggerConfiguration() - .ReadFrom.Configuration(configuration) - .Enrich.WithProperty("ApplicationContext", AppName) - .Enrich.FromLogContext() - .WriteTo.Console(); - if (!string.IsNullOrWhiteSpace(seqServerUrl)) - { - cfg.WriteTo.Seq(seqServerUrl); - } - if (!string.IsNullOrWhiteSpace(logstashUrl)) - { - cfg.WriteTo.Http(logstashUrl, null); - } - return cfg.CreateLogger(); -} - -static void AddApplicationInsights(WebApplicationBuilder builder) -{ - builder.Services.AddApplicationInsightsTelemetry(builder.Configuration); - builder.Services.AddApplicationInsightsKubernetesEnricher(); -} - -static void AddHealthChecks(WebApplicationBuilder builder) -{ - builder.Services.AddHealthChecks() - .AddCheck("self", () => HealthCheckResult.Healthy()) - .AddUrlGroup(new Uri(builder.Configuration["IdentityUrlHC"]), name: "identityapi-check", tags: new string[] { "identityapi" }); -} - -static void AddCustomMvc(WebApplicationBuilder builder) -{ - builder.Services.AddOptions() - .Configure(builder.Configuration) - .AddSession() - .AddDistributedMemoryCache(); +app.MapForwardSignalR(); - if (builder.Configuration.GetValue("IsClusterEnv") == bool.TrueString) - { - builder.Services.AddDataProtection(opts => - { - opts.ApplicationDiscriminator = "eshop.webmvc"; - }) - .PersistKeysToStackExchangeRedis(ConnectionMultiplexer.Connect(builder.Configuration["DPConnectionString"]), "DataProtection-Keys"); - } -} - -// Adds all Http client services -static void AddHttpClientServices(WebApplicationBuilder builder) -{ - builder.Services.AddSingleton(); - - //register delegating handlers - builder.Services.AddTransient() - .AddTransient(); - - //set 5 min as the lifetime for each HttpMessageHandler int the pool - builder.Services.AddHttpClient("extendedhandlerlifetime").SetHandlerLifetime(TimeSpan.FromMinutes(5)); - - //add http client services - builder.Services.AddHttpClient() - .SetHandlerLifetime(TimeSpan.FromMinutes(5)) //Sample. Default lifetime is 2 minutes - .AddHttpMessageHandler(); - - builder.Services.AddHttpClient(); - - builder.Services.AddHttpClient() - .AddHttpMessageHandler() - .AddHttpMessageHandler(); - - - //add custom application services - builder.Services.AddTransient, IdentityParser>(); -} - -static void AddCustomAuthentication(WebApplicationBuilder builder) -{ - var identityUrl = builder.Configuration.GetValue("IdentityUrl"); - var callBackUrl = builder.Configuration.GetValue("CallBackUrl"); - var sessionCookieLifetime = builder.Configuration.GetValue("SessionCookieLifetimeMinutes", 60); - - // Add Authentication services - - builder.Services.AddAuthentication(options => - { - options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; - }) - .AddCookie(setup => setup.ExpireTimeSpan = TimeSpan.FromMinutes(sessionCookieLifetime)) - .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options => - { - options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; - options.Authority = identityUrl.ToString(); - options.SignedOutRedirectUri = callBackUrl.ToString(); - options.ClientId = "mvc"; - options.ClientSecret = "secret"; - options.ResponseType = "code"; - options.SaveTokens = true; - options.GetClaimsFromUserInfoEndpoint = true; - options.RequireHttpsMetadata = false; - options.Scope.Add("openid"); - options.Scope.Add("profile"); - options.Scope.Add("orders"); - options.Scope.Add("basket"); - options.Scope.Add("webshoppingagg"); - options.Scope.Add("orders.signalrhub"); - options.Scope.Add("webhooks"); - }); -} +WebContextSeed.Seed(app, app.Environment); -public partial class Program -{ - public static readonly string AppName = typeof(Program).Assembly.GetName().Name; -} \ No newline at end of file +await app.RunAsync(); diff --git a/src/Web/WebMVC/Properties/launchSettings.json b/src/Web/WebMVC/Properties/launchSettings.json index bf529db40..cc41e62fe 100644 --- a/src/Web/WebMVC/Properties/launchSettings.json +++ b/src/Web/WebMVC/Properties/launchSettings.json @@ -1,23 +1,9 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:5100", - "sslPort": 0 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, "Microsoft.eShopOnContainers.WebMVC": { "commandName": "Project", "launchBrowser": true, - "launchUrl": "http://localhost:5000", + "applicationUrl": "http://localhost:5331", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/src/Web/WebMVC/Services/BasketService.cs b/src/Web/WebMVC/Services/BasketService.cs index 4833b3408..952cdb187 100644 --- a/src/Web/WebMVC/Services/BasketService.cs +++ b/src/Web/WebMVC/Services/BasketService.cs @@ -53,7 +53,7 @@ public class BasketService : IBasketService var uri = API.Basket.CheckoutBasket(_basketByPassUrl); var basketContent = new StringContent(JsonSerializer.Serialize(basket), Encoding.UTF8, "application/json"); - _logger.LogInformation("Uri chechout {uri}", uri); + _logger.LogInformation("Uri checkout {uri}", uri); var response = await _apiClient.PostAsync(uri, basketContent); diff --git a/src/Web/WebMVC/Views/Shared/_Layout.cshtml b/src/Web/WebMVC/Views/Shared/_Layout.cshtml index 9a634b78a..6cb3f43fb 100644 --- a/src/Web/WebMVC/Views/Shared/_Layout.cshtml +++ b/src/Web/WebMVC/Views/Shared/_Layout.cshtml @@ -86,35 +86,23 @@ @RenderSection("scripts", required: false) - - @using Microsoft.AspNetCore.Authentication; - @using Microsoft.Extensions.Options - @inject IOptions settings -