Port WebhookClient to WebApplicationBuilder
This commit is contained in:
parent
dbef61fdaf
commit
6f4ae509f1
@ -1,9 +0,0 @@
|
||||
namespace Microsoft.eShopOnContainers.Services.Identity.API
|
||||
{
|
||||
public class AppSettings
|
||||
{
|
||||
public string MvcClient { get; set; }
|
||||
|
||||
public bool UseCustomizationData { get; set; }
|
||||
}
|
||||
}
|
@ -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<AppSettings>(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)
|
||||
{
|
||||
|
@ -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();
|
||||
|
||||
|
@ -5,8 +5,6 @@ public class AccountController : Controller
|
||||
{
|
||||
public async Task<IActionResult> SignIn(string returnUrl)
|
||||
{
|
||||
var user = User as ClaimsPrincipal;
|
||||
|
||||
var token = await HttpContext.GetTokenAsync("access_token");
|
||||
|
||||
if (token != null)
|
||||
|
@ -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> settings, ILogger<WebhooksReceivedController> logger, IHooksRepository hooksRepository)
|
||||
public WebhooksReceivedController(IOptions<WebhookClientOptions> options, ILogger<WebhooksReceivedController> 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()
|
||||
|
49
src/Web/WebhookClient/Extensions/Extensions.cs
Normal file
49
src/Web/WebhookClient/Extensions/Extensions.cs
Normal file
@ -0,0 +1,49 @@
|
||||
namespace WebhookClient;
|
||||
|
||||
internal static class Extensions
|
||||
{
|
||||
public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
var identityUrl = configuration.GetValue<string>("IdentityUrl");
|
||||
var callBackUrl = configuration.GetValue<string>("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<IHttpContextAccessor, HttpContextAccessor>();
|
||||
services.AddTransient<HttpClientAuthorizationDelegatingHandler>();
|
||||
services.AddHttpClient("extendedhandlerlifetime").SetHandlerLifetime(Timeout.InfiniteTimeSpan);
|
||||
|
||||
//add http client services
|
||||
services.AddHttpClient("GrantClient")
|
||||
.SetHandlerLifetime(TimeSpan.FromMinutes(5))
|
||||
.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
@ -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<HttpResponseMessage> 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<string>() { 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<string> GetToken()
|
||||
{
|
||||
const string ACCESS_TOKEN = "access_token";
|
||||
|
||||
return await _httpContextAccessor.HttpContext
|
||||
.GetTokenAsync(ACCESS_TOKEN);
|
||||
}
|
||||
}
|
||||
|
@ -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> settings, IHttpClientFactory httpClientFactory)
|
||||
public RegisterWebhookModel(IOptions<WebhookClientOptions> 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<IActionResult> 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<WebhookSubscriptionRequest>(_settings.WebhooksUrl + "/api/v1/webhooks", payload, new JsonMediaTypeFormatter());
|
||||
var response = await client.PostAsync<WebhookSubscriptionRequest>(_options.WebhooksUrl + "/api/v1/webhooks", payload, new JsonMediaTypeFormatter());
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
@ -68,4 +68,4 @@ namespace WebhookClient.Pages
|
||||
return Page();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,71 @@
|
||||
CreateWebHostBuilder(args).Build().Run();
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Services.AddSession(opt =>
|
||||
{
|
||||
opt.Cookie.Name = ".eShopWebhooks.Session";
|
||||
})
|
||||
.Configure<WebhookClientOptions>(builder.Configuration)
|
||||
.AddHttpClientServices(builder.Configuration)
|
||||
.AddCustomAuthentication(builder.Configuration)
|
||||
.AddTransient<IWebhooksClient, WebhooksClient>()
|
||||
.AddSingleton<IHooksRepository, InMemoryHooksRepository>()
|
||||
.AddMvc();
|
||||
builder.Services.AddControllers();
|
||||
var app = builder.Build();
|
||||
|
||||
IWebHostBuilder CreateWebHostBuilder(string[] args) =>
|
||||
WebHost.CreateDefaultBuilder(args)
|
||||
.UseStartup<Startup>();
|
||||
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();
|
||||
|
@ -2,18 +2,17 @@
|
||||
|
||||
public class WebhooksClient : IWebhooksClient
|
||||
{
|
||||
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly Settings _settings;
|
||||
public WebhooksClient(IHttpClientFactory httpClientFactory, IOptions<Settings> settings)
|
||||
private readonly WebhookClientOptions _options;
|
||||
public WebhooksClient(IHttpClientFactory httpClientFactory, IOptions<WebhookClientOptions> options)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_settings = settings.Value;
|
||||
_options = options.Value;
|
||||
}
|
||||
public async Task<IEnumerable<WebhookResponse>> 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<IEnumerable<WebhookResponse>>(json, new JsonSerializerOptions
|
||||
{
|
||||
|
@ -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<IWebhooksClient, WebhooksClient>()
|
||||
.AddSingleton<IHooksRepository, InMemoryHooksRepository>()
|
||||
.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<Settings>(configuration);
|
||||
return services;
|
||||
}
|
||||
public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
var identityUrl = configuration.GetValue<string>("IdentityUrl");
|
||||
var callBackUrl = configuration.GetValue<string>("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<IHttpContextAccessor, HttpContextAccessor>();
|
||||
services.AddTransient<HttpClientAuthorizationDelegatingHandler>();
|
||||
services.AddHttpClient("extendedhandlerlifetime").SetHandlerLifetime(Timeout.InfiniteTimeSpan);
|
||||
|
||||
//add http client services
|
||||
services.AddHttpClient("GrantClient")
|
||||
.SetHandlerLifetime(TimeSpan.FromMinutes(5))
|
||||
.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
namespace WebhookClient;
|
||||
|
||||
public class Settings
|
||||
public class WebhookClientOptions
|
||||
{
|
||||
public string Token { get; set; }
|
||||
public string IdentityUrl { get; set; }
|
@ -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/
|
||||
|
Loading…
x
Reference in New Issue
Block a user