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/Web/WebMVC/Extensions/Extensions.cs b/src/Web/WebMVC/Extensions/Extensions.cs index a530019c3..4e9a87410 100644 --- a/src/Web/WebMVC/Extensions/Extensions.cs +++ b/src/Web/WebMVC/Extensions/Extensions.cs @@ -10,7 +10,7 @@ internal static class Extensions .AddUrlGroup(_ => new Uri(configuration["IdentityUrlHC"]), name: "identityapi-check", tags: new string[] { "identityapi" }); } - public static void AddApplicationSevices(this IServiceCollection services, IConfiguration configuration) + public static void AddApplicationServices(this IServiceCollection services, IConfiguration configuration) { services.Configure(configuration); @@ -86,13 +86,13 @@ internal static class Extensions { // Forward the SignalR traffic to the bff var destination = app.Configuration.GetRequiredValue("PurchaseUrl"); - var authTransformer = new BffAuthTransfomer(); + var authTransformer = new BffAuthTransformer(); var requestConfig = new ForwarderRequestConfig(); return app.MapForwarder("/hub/notificationhub/{**any}", destination, requestConfig, authTransformer); } - private sealed class BffAuthTransfomer : HttpTransformer + private sealed class BffAuthTransformer : HttpTransformer { public override async ValueTask TransformRequestAsync(HttpContext httpContext, HttpRequestMessage proxyRequest, string destinationPrefix, CancellationToken cancellationToken) { diff --git a/src/Web/WebMVC/Program.cs b/src/Web/WebMVC/Program.cs index 71cdf91e4..34d407fec 100644 --- a/src/Web/WebMVC/Program.cs +++ b/src/Web/WebMVC/Program.cs @@ -6,7 +6,7 @@ builder.Services.AddHttpForwarder(); builder.Services.AddControllersWithViews(); builder.Services.AddHealthChecks(builder.Configuration); -builder.Services.AddApplicationSevices(builder.Configuration); +builder.Services.AddApplicationServices(builder.Configuration); builder.Services.AddAuthenticationServices(builder.Configuration); builder.Services.AddHttpClientServices(); diff --git a/src/Web/WebhookClient/Controllers/AccountController.cs b/src/Web/WebhookClient/Controllers/AccountController.cs index f67de6d61..ff522874a 100644 --- a/src/Web/WebhookClient/Controllers/AccountController.cs +++ b/src/Web/WebhookClient/Controllers/AccountController.cs @@ -5,8 +5,6 @@ public class AccountController : Controller { public async Task SignIn(string returnUrl) { - var user = User as ClaimsPrincipal; - var token = await HttpContext.GetTokenAsync("access_token"); if (token != null) diff --git a/src/Web/WebhookClient/Controllers/WebhooksReceivedController.cs b/src/Web/WebhookClient/Controllers/WebhooksReceivedController.cs index db7a2c6c0..1f37d4363 100644 --- a/src/Web/WebhookClient/Controllers/WebhooksReceivedController.cs +++ b/src/Web/WebhookClient/Controllers/WebhooksReceivedController.cs @@ -4,14 +4,13 @@ [Route("webhook-received")] public class WebhooksReceivedController : Controller { - - private readonly Settings _settings; + private readonly WebhookClientOptions _options; private readonly ILogger _logger; private readonly IHooksRepository _hooksRepository; - public WebhooksReceivedController(IOptions settings, ILogger logger, IHooksRepository hooksRepository) + public WebhooksReceivedController(IOptions options, ILogger logger, IHooksRepository hooksRepository) { - _settings = settings.Value; + _options = options.Value; _logger = logger; _hooksRepository = hooksRepository; } @@ -22,9 +21,9 @@ public class WebhooksReceivedController : Controller var header = Request.Headers[HeaderNames.WebHookCheckHeader]; var token = header.FirstOrDefault(); - _logger.LogInformation("Received hook with token {Token}. My token is {MyToken}. Token validation is set to {ValidateToken}", token, _settings.Token, _settings.ValidateToken); + _logger.LogInformation("Received hook with token {Token}. My token is {MyToken}. Token validation is set to {ValidateToken}", token, _options.Token, _options.ValidateToken); - if (!_settings.ValidateToken || _settings.Token == token) + if (!_options.ValidateToken || _options.Token == token) { _logger.LogInformation("Received hook is going to be processed"); var newHook = new WebHookReceived() diff --git a/src/Web/WebhookClient/Extensions/Extensions.cs b/src/Web/WebhookClient/Extensions/Extensions.cs new file mode 100644 index 000000000..c23684142 --- /dev/null +++ b/src/Web/WebhookClient/Extensions/Extensions.cs @@ -0,0 +1,49 @@ +namespace WebhookClient; + +internal static class Extensions +{ + public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration) + { + var identityUrl = configuration.GetValue("IdentityUrl"); + var callBackUrl = configuration.GetValue("CallBackUrl"); + + // Add Authentication services + + services.AddAuthentication(options => + { + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; + }) + .AddCookie(setup => setup.ExpireTimeSpan = TimeSpan.FromHours(2)) + .AddOpenIdConnect(options => + { + options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.Authority = identityUrl.ToString(); + options.SignedOutRedirectUri = callBackUrl.ToString(); + options.ClientId = "webhooksclient"; + options.ClientSecret = "secret"; + options.ResponseType = "code"; + options.SaveTokens = true; + options.GetClaimsFromUserInfoEndpoint = true; + options.RequireHttpsMetadata = false; + options.Scope.Add("openid"); + options.Scope.Add("webhooks"); + }); + + return services; + } + + public static IServiceCollection AddHttpClientServices(this IServiceCollection services, IConfiguration configuration) + { + services.AddSingleton(); + services.AddTransient(); + services.AddHttpClient("extendedhandlerlifetime").SetHandlerLifetime(Timeout.InfiniteTimeSpan); + + //add http client services + services.AddHttpClient("GrantClient") + .SetHandlerLifetime(TimeSpan.FromMinutes(5)) + .AddHttpMessageHandler(); + + return services; + } +} diff --git a/src/Web/WebhookClient/HttpClientAuthorizationDelegatingHandler.cs b/src/Web/WebhookClient/HttpClientAuthorizationDelegatingHandler.cs index 6ba7ddd60..89537ee0d 100644 --- a/src/Web/WebhookClient/HttpClientAuthorizationDelegatingHandler.cs +++ b/src/Web/WebhookClient/HttpClientAuthorizationDelegatingHandler.cs @@ -1,7 +1,6 @@ namespace WebhookClient; -public class HttpClientAuthorizationDelegatingHandler - : DelegatingHandler +public class HttpClientAuthorizationDelegatingHandler : DelegatingHandler { private readonly IHttpContextAccessor _httpContextAccessor; @@ -12,15 +11,14 @@ public class HttpClientAuthorizationDelegatingHandler protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { - var authorizationHeader = _httpContextAccessor.HttpContext - .Request.Headers["Authorization"]; + var authorizationHeader = _httpContextAccessor.HttpContext.Request.Headers["Authorization"]; if (!string.IsNullOrEmpty(authorizationHeader)) { request.Headers.Add("Authorization", new List() { authorizationHeader }); } - var token = await GetToken(); + var token = await _httpContextAccessor.HttpContext.GetTokenAsync("access_token"); if (token != null) { @@ -29,12 +27,4 @@ public class HttpClientAuthorizationDelegatingHandler 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/WebhookClient/Pages/RegisterWebhook.cshtml.cs b/src/Web/WebhookClient/Pages/RegisterWebhook.cshtml.cs index b564bf781..85d2e2543 100644 --- a/src/Web/WebhookClient/Pages/RegisterWebhook.cshtml.cs +++ b/src/Web/WebhookClient/Pages/RegisterWebhook.cshtml.cs @@ -7,7 +7,7 @@ namespace WebhookClient.Pages public class RegisterWebhookModel : PageModel { - private readonly Settings _settings; + private readonly WebhookClientOptions _options; private readonly IHttpClientFactory _httpClientFactory; [BindProperty] public string Token { get; set; } @@ -18,23 +18,23 @@ namespace WebhookClient.Pages public string ResponseMessage { get; set; } public string RequestBodyJson { get; set; } - public RegisterWebhookModel(IOptions settings, IHttpClientFactory httpClientFactory) + public RegisterWebhookModel(IOptions options, IHttpClientFactory httpClientFactory) { - _settings = settings.Value; + _options = options.Value; _httpClientFactory = httpClientFactory; } public void OnGet() { ResponseCode = (int)HttpStatusCode.OK; - Token = _settings.Token; + Token = _options.Token; } public async Task OnPost() { ResponseCode = (int)HttpStatusCode.OK; var protocol = Request.IsHttps ? "https" : "http"; - var selfurl = !string.IsNullOrEmpty(_settings.SelfUrl) ? _settings.SelfUrl : $"{protocol}://{Request.Host}/{Request.PathBase}"; + var selfurl = !string.IsNullOrEmpty(_options.SelfUrl) ? _options.SelfUrl : $"{protocol}://{Request.Host}/{Request.PathBase}"; if (!selfurl.EndsWith("/")) { selfurl = selfurl + "/"; @@ -50,7 +50,7 @@ namespace WebhookClient.Pages Url = url, Token = Token }; - var response = await client.PostAsync(_settings.WebhooksUrl + "/api/v1/webhooks", payload, new JsonMediaTypeFormatter()); + var response = await client.PostAsync(_options.WebhooksUrl + "/api/v1/webhooks", payload, new JsonMediaTypeFormatter()); if (response.IsSuccessStatusCode) { @@ -68,4 +68,4 @@ namespace WebhookClient.Pages return Page(); } } -} \ No newline at end of file +} diff --git a/src/Web/WebhookClient/Program.cs b/src/Web/WebhookClient/Program.cs index db38493ae..977fa4243 100644 --- a/src/Web/WebhookClient/Program.cs +++ b/src/Web/WebhookClient/Program.cs @@ -1,6 +1,71 @@ -CreateWebHostBuilder(args).Build().Run(); +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddSession(opt => + { + opt.Cookie.Name = ".eShopWebhooks.Session"; + }) + .Configure(builder.Configuration) + .AddHttpClientServices(builder.Configuration) + .AddCustomAuthentication(builder.Configuration) + .AddTransient() + .AddSingleton() + .AddMvc(); +builder.Services.AddControllers(); +var app = builder.Build(); -IWebHostBuilder CreateWebHostBuilder(string[] args) => - WebHost.CreateDefaultBuilder(args) - .UseStartup(); +var pathBase = app.Configuration["PATH_BASE"]; +if (!string.IsNullOrEmpty(pathBase)) +{ + app.UsePathBase(pathBase); +} + +if (!app.Environment.IsDevelopment()) +{ + app.UseExceptionHandler("/Error"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. +} + +app.Map("/check", capp => +{ + capp.Run(async (context) => + { + if ("OPTIONS".Equals(context.Request.Method, StringComparison.InvariantCultureIgnoreCase)) + { + var validateToken = bool.TrueString.Equals(builder.Configuration["ValidateToken"], StringComparison.InvariantCultureIgnoreCase); + var header = context.Request.Headers[HeaderNames.WebHookCheckHeader]; + var value = header.FirstOrDefault(); + var tokenToValidate = builder.Configuration["Token"]; + if (!validateToken || value == tokenToValidate) + { + if (!string.IsNullOrWhiteSpace(tokenToValidate)) + { + context.Response.Headers.Add(HeaderNames.WebHookCheckHeader, tokenToValidate); + } + context.Response.StatusCode = (int)HttpStatusCode.OK; + } + else + { + await context.Response.WriteAsync("Invalid token"); + context.Response.StatusCode = (int)HttpStatusCode.BadRequest; + } + } + else + { + context.Response.StatusCode = (int)HttpStatusCode.BadRequest; + } + }); +}); + +// 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 +app.UseCookiePolicy(new CookiePolicyOptions { MinimumSameSitePolicy = SameSiteMode.Lax }); + +app.UseStaticFiles(); +app.UseSession(); +app.UseRouting(); +app.UseAuthentication(); +app.UseAuthorization(); +app.MapDefaultControllerRoute(); +app.MapRazorPages(); + +await app.RunAsync(); diff --git a/src/Web/WebhookClient/Services/WebhooksClient.cs b/src/Web/WebhookClient/Services/WebhooksClient.cs index 87f950a34..55b0d9ab8 100644 --- a/src/Web/WebhookClient/Services/WebhooksClient.cs +++ b/src/Web/WebhookClient/Services/WebhooksClient.cs @@ -2,18 +2,17 @@ public class WebhooksClient : IWebhooksClient { - private readonly IHttpClientFactory _httpClientFactory; - private readonly Settings _settings; - public WebhooksClient(IHttpClientFactory httpClientFactory, IOptions settings) + private readonly WebhookClientOptions _options; + public WebhooksClient(IHttpClientFactory httpClientFactory, IOptions options) { _httpClientFactory = httpClientFactory; - _settings = settings.Value; + _options = options.Value; } public async Task> LoadWebhooks() { var client = _httpClientFactory.CreateClient("GrantClient"); - var response = await client.GetAsync(_settings.WebhooksUrl + "/api/v1/webhooks"); + var response = await client.GetAsync(_options.WebhooksUrl + "/api/v1/webhooks"); var json = await response.Content.ReadAsStringAsync(); var subscriptions = JsonSerializer.Deserialize>(json, new JsonSerializerOptions { diff --git a/src/Web/WebhookClient/Startup.cs b/src/Web/WebhookClient/Startup.cs deleted file mode 100644 index 61d91030f..000000000 --- a/src/Web/WebhookClient/Startup.cs +++ /dev/null @@ -1,144 +0,0 @@ -namespace WebhookClient; - -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.AddSession(opt => - { - opt.Cookie.Name = ".eShopWebhooks.Session"; - }) - .AddConfiguration(Configuration) - .AddHttpClientServices(Configuration) - .AddCustomAuthentication(Configuration) - .AddTransient() - .AddSingleton() - .AddMvc(); - - services.AddControllers(); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - - var pathBase = Configuration["PATH_BASE"]; - if (!string.IsNullOrEmpty(pathBase)) - { - app.UsePathBase(pathBase); - } - - if (!env.IsDevelopment()) - { - app.UseExceptionHandler("/Error"); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - } - - app.Map("/check", capp => - { - capp.Run(async (context) => - { - if ("OPTIONS".Equals(context.Request.Method, StringComparison.InvariantCultureIgnoreCase)) - { - var validateToken = bool.TrueString.Equals(Configuration["ValidateToken"], StringComparison.InvariantCultureIgnoreCase); - var header = context.Request.Headers[HeaderNames.WebHookCheckHeader]; - var value = header.FirstOrDefault(); - var tokenToValidate = Configuration["Token"]; - if (!validateToken || value == tokenToValidate) - { - if (!string.IsNullOrWhiteSpace(tokenToValidate)) - { - context.Response.Headers.Add(HeaderNames.WebHookCheckHeader, tokenToValidate); - } - context.Response.StatusCode = (int)HttpStatusCode.OK; - } - else - { - await context.Response.WriteAsync("Invalid token"); - context.Response.StatusCode = (int)HttpStatusCode.BadRequest; - } - } - else - { - context.Response.StatusCode = (int)HttpStatusCode.BadRequest; - } - }); - }); - - // 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 - app.UseCookiePolicy(new CookiePolicyOptions { MinimumSameSitePolicy = SameSiteMode.Lax }); - - app.UseStaticFiles(); - app.UseSession(); - app.UseRouting(); - app.UseAuthentication(); - app.UseAuthorization(); - app.UseEndpoints(endpoints => - { - endpoints.MapDefaultControllerRoute(); - endpoints.MapRazorPages(); - }); - } -} - -static class ServiceExtensions -{ - public static IServiceCollection AddConfiguration(this IServiceCollection services, IConfiguration configuration) - { - services.Configure(configuration); - return services; - } - public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration) - { - var identityUrl = configuration.GetValue("IdentityUrl"); - var callBackUrl = configuration.GetValue("CallBackUrl"); - - // Add Authentication services - - services.AddAuthentication(options => - { - options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; - }) - .AddCookie(setup => setup.ExpireTimeSpan = TimeSpan.FromHours(2)) - .AddOpenIdConnect(options => - { - options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; - options.Authority = identityUrl.ToString(); - options.SignedOutRedirectUri = callBackUrl.ToString(); - options.ClientId = "webhooksclient"; - options.ClientSecret = "secret"; - options.ResponseType = "code"; - options.SaveTokens = true; - options.GetClaimsFromUserInfoEndpoint = true; - options.RequireHttpsMetadata = false; - options.Scope.Add("openid"); - options.Scope.Add("webhooks"); - }); - - return services; - } - - public static IServiceCollection AddHttpClientServices(this IServiceCollection services, IConfiguration configuration) - { - services.AddSingleton(); - services.AddTransient(); - services.AddHttpClient("extendedhandlerlifetime").SetHandlerLifetime(Timeout.InfiniteTimeSpan); - - //add http client services - services.AddHttpClient("GrantClient") - .SetHandlerLifetime(TimeSpan.FromMinutes(5)) - .AddHttpMessageHandler(); - - return services; - } -} diff --git a/src/Web/WebhookClient/Settings.cs b/src/Web/WebhookClient/WebhookClientOptions.cs similarity index 89% rename from src/Web/WebhookClient/Settings.cs rename to src/Web/WebhookClient/WebhookClientOptions.cs index 14b67dadf..e423bcf61 100644 --- a/src/Web/WebhookClient/Settings.cs +++ b/src/Web/WebhookClient/WebhookClientOptions.cs @@ -1,6 +1,6 @@ namespace WebhookClient; -public class Settings +public class WebhookClientOptions { public string Token { get; set; } public string IdentityUrl { get; set; } diff --git a/src/docker-compose.override.yml b/src/docker-compose.override.yml index f60cde8b7..a08b080e5 100644 --- a/src/docker-compose.override.yml +++ b/src/docker-compose.override.yml @@ -305,7 +305,7 @@ services: environment: - ASPNETCORE_URLS=http://0.0.0.0:80 - Token=6168DB8D-DC58-4094-AF24-483278923590 # Webhooks are registered with this token (any value is valid) but the client won't check it - - Identity__Url=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105 + - IdentityUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105 - CallBackUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5114 - WebhooksUrl=http://webhooks-api - SelfUrl=http://webhooks-client/