mirror of
https://github.com/hanzoai/jube-fork.git
synced 2026-10-30 18:33:33 +00:00
* Pushed Case Workflow Display HTML rendering logic down into a controller for improved security and consistent tokenisation of the Jube token format against context. Included a migration to update the default demo entry to use proper tokenisation. Updated the repository to support fetching CaseWorkflowDisplay with appropriate security. * Implemented improved tokenisation of the EntityAnalysisModelInstanceEntryPayload model via a common extension method on EntityAnalysisModelInstanceEntryPayload, used consistently across the system for serialisation and storage. * Included a migration to update the tokenisation format for full CaseWorkflowDisplay support. * Fixed an issue where CaseWorkflowFilter was incorrectly filtering on CaseWorkflowStatus.Guid instead of CaseWorkflowStatus.Name, causing the GUID to be returned rather than the display name on the template/demo model. (The remainder was fixed in GitHub issue #6.) Included a migration with a corrective SQL statement. * Fixed an issue where tokens were not being stored alongside CaseWorkflowFilter definition configuration. It now behaves identically to CaseSearch. Note: migrating missing tokens is not feasible as it requires JS-side parsing via JQueryBuilder. * Reduced the size of the CaseWorkflowForm invocation to include only the required fields given the new POST body context being rendered server side. Improved API serialisation by using a dictionary for form payload keys and values. * Consolidated JSON serialisation settings into DI and updated usages throughout. * Updated EntityAnalysisModelInstanceEntryPayload write serialisers to handle deserialisation, which is required to support tokenisation of new Case Automation POST bodies. * Created an extension method on the EntityAnalysisModelInstanceEntryPayload to centralise tokenisation of strings across Activation and Case Automation on a consistent basis. Simplified Case Notification and Case HTTP dispatch by removing tokenisation logic from those classes. * Removed a redundant field from the CaseWorkflowFormEntry POCO (database left unchanged for now). * Fixed a bug in Case session SQL rebuilding where the rebuild date was not being correctly checked for null or default value, which caused a failure in the last migration. * Made minor modifications to the Create Case process and method signature to skip serialisation when the payload object required for tokenisation is already available on the thread (e.g. during synchronous reprocessing). * Updated Case Workflow Macro processing to fetch the archive payload for the case from the database, improving security of that invocation endpoint. * Removed what appeared to be a duplicate EntityAnalysisModelInstanceEntryPayload model introduced during refactoring. * Fixed a but in Case Worfkflow Display Role assigment owing to volatile Guid in its update.
676 lines
30 KiB
C#
Executable File
676 lines
30 KiB
C#
Executable File
/* Copyright (C) 2022-present Jube Holdings Limited.
|
|
*
|
|
* This file is part of Jube™ software.
|
|
*
|
|
* Jube™ is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License
|
|
* as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
|
* Jube™ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
|
|
|
|
* You should have received a copy of the GNU Affero General Public License along with Jube™. If not,
|
|
* see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
namespace Jube.App
|
|
{
|
|
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.IdentityModel.Tokens.Jwt;
|
|
using System.Net;
|
|
using System.Security.Claims;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Cache;
|
|
using Cache.Redis.Callback;
|
|
using Code;
|
|
using Code.Jube.WebApp.Code;
|
|
using Code.signalr;
|
|
using Code.WatcherDispatch;
|
|
using DynamicEnvironment;
|
|
using Engine;
|
|
using Engine.Helpers;
|
|
using FluentMigrator.Runner;
|
|
using log4net;
|
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
using Microsoft.AspNetCore.Authentication.Negotiate;
|
|
using Microsoft.AspNetCore.Builder;
|
|
using Microsoft.AspNetCore.Hosting;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Identity;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Hosting;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
using Microsoft.OpenApi.Models;
|
|
using Middlewares.Extensions;
|
|
using Migrations.Baseline;
|
|
using Newtonsoft.Json.Serialization;
|
|
using Npgsql;
|
|
using RabbitMQ.Client;
|
|
using TaskCancellation;
|
|
using TaskCancellation.Interfaces;
|
|
|
|
public class Startup
|
|
{
|
|
public Startup(IConfiguration configuration)
|
|
{
|
|
Configuration = configuration;
|
|
}
|
|
|
|
private IConfiguration Configuration { get; }
|
|
|
|
public void ConfigureServices(IServiceCollection services)
|
|
{
|
|
var cancellationTokenProvider = AddSingletonForCancellationToken(services);
|
|
var taskCoordinator = AddSingletonForTaskCoordinator(services, cancellationTokenProvider);
|
|
var contractResolver = AddSingletonForJsonSerializationHelper(services);
|
|
|
|
var dynamicEnvironment = AddSingletonForDynamicEnvironmentAndLogging(services, out var log);
|
|
ConfigureThreadPool(dynamicEnvironment, log);
|
|
ValidateConnectionToPostgres(dynamicEnvironment.AppSettings("ConnectionString"), log);
|
|
|
|
var callbacks = AddSingletonForCallbacks(services);
|
|
|
|
var cacheService = AddSingletonForCacheService(services, callbacks, Int32.Parse(dynamicEnvironment.AppSettings("CallbackTimeout") ?? "10000"),
|
|
taskCoordinator, dynamicEnvironment, log);
|
|
|
|
var rabbitMqConnection = AddSingletonForRabbitMqConnection(services, dynamicEnvironment, log);
|
|
|
|
AddSingletonForEngine(services, dynamicEnvironment, log, rabbitMqConnection, cacheService, contractResolver, taskCoordinator);
|
|
AddSingletonForIdentity(services);
|
|
ConfigureAuthentication(services, dynamicEnvironment);
|
|
AddGenericServicesRequired(services);
|
|
AddSwagger(services);
|
|
AddSingletonRelayToBeInstantiatedInConfigureServices(services, dynamicEnvironment);
|
|
WriteWelcomeMessageToConsole();
|
|
}
|
|
|
|
private static TaskCoordinator AddSingletonForTaskCoordinator(IServiceCollection services, ICancellationTokenProvider cancellationTokenProvider)
|
|
{
|
|
var taskCoordinator = new TaskCoordinator(cancellationTokenProvider);
|
|
services.AddSingleton(taskCoordinator);
|
|
return taskCoordinator;
|
|
}
|
|
|
|
private static ICancellationTokenProvider AddSingletonForCancellationToken(IServiceCollection services)
|
|
{
|
|
ICancellationTokenProvider cancellationTokenProvider = new CancellationTokenProvider();
|
|
services.AddSingleton(cancellationTokenProvider);
|
|
return cancellationTokenProvider;
|
|
}
|
|
|
|
private static void WriteWelcomeMessageToConsole()
|
|
{
|
|
|
|
Console.WriteLine(@"Copyright (C) 2022-present Jube Holdings Limited.");
|
|
Console.WriteLine(@"");
|
|
Console.WriteLine(@"This software is Jube. Welcome.");
|
|
Console.WriteLine(@"");
|
|
Console.Write(
|
|
@"Jube™ is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.");
|
|
Console.WriteLine(
|
|
@"Jube™ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.");
|
|
Console.WriteLine(@"");
|
|
Console.WriteLine(
|
|
@"You should have received a copy of the GNU Affero General Public License along with Jube™. If not, see <https://www.gnu.org/licenses/>.");
|
|
|
|
Console.WriteLine(@"");
|
|
Console.WriteLine(
|
|
@"If you are seeing this message it means that database migrations have completed and the database is fully configured with required Tables, Indexes and Constraints.");
|
|
Console.WriteLine(@"");
|
|
Console.WriteLine(
|
|
@"Comprehensive documentation is available via https://github.com/jube-home/aml-transaction-monitoring.");
|
|
Console.WriteLine(@"");
|
|
Console.WriteLine(
|
|
@"Use a web browser (e.g. Chrome) to navigate to the user interface via default endpoint https://<ASPNETCORE_URLS Environment Variable>/ (for example https://127.0.0.1:5001/ given ASPNETCORE_URLS=https://127.0.0.1:5001/). The default user name \ password is 'Administrator' \ 'Administrator' but will need to be changed on first use. Availability of the user interface may be a few moments after this messages as the Kestrel web server starts and endpoint routing is established.");
|
|
Console.WriteLine(@"");
|
|
Console.WriteLine(
|
|
@"The default endpoint for posting example transaction payload is https://<ASPNETCORE_URLS Environment Variable>/api/invoke/EntityAnalysisModel/90c425fd-101a-420b-91d1-cb7a24a969cc/.Example JSON payload is available in the documentation via at https://jube-home.github.io/aml-transaction-monitoring/Configuration/Models/Models/.");
|
|
Console.WriteLine();
|
|
}
|
|
|
|
private static void AddSingletonRelayToBeInstantiatedInConfigureServices(IServiceCollection services, DynamicEnvironment dynamicEnvironment)
|
|
{
|
|
if (!dynamicEnvironment.AppSettings("StreamingActivationWatcher").Equals("True", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return;
|
|
}
|
|
|
|
services.AddSingleton<Relay>();
|
|
}
|
|
|
|
private static void AddGenericServicesRequired(IServiceCollection services)
|
|
{
|
|
|
|
services.AddAuthorization();
|
|
services.AddRazorPages();
|
|
services.AddHttpContextAccessor();
|
|
services.AddControllers().AddNewtonsoftJson(options =>
|
|
{
|
|
options.SerializerSettings.ContractResolver = new DefaultContractResolver
|
|
{
|
|
NamingStrategy = new CamelCaseNamingStrategy()
|
|
};
|
|
});
|
|
services.AddMvc();
|
|
services.AddSignalR();
|
|
services.AddEndpointsApiExplorer();
|
|
}
|
|
|
|
private static void AddSingletonForIdentity(IServiceCollection services)
|
|
{
|
|
|
|
services.AddTransient<IUserStore<ApplicationUser>, UserStore>();
|
|
services.AddTransient<IRoleStore<ApplicationRole>, RoleStore>();
|
|
services.AddIdentity<ApplicationUser, ApplicationRole>().AddDefaultTokenProviders();
|
|
}
|
|
|
|
private static void AddSwagger(IServiceCollection services)
|
|
{
|
|
|
|
services.AddSwaggerGen(c =>
|
|
{
|
|
c.SwaggerDoc("v1", new OpenApiInfo
|
|
{
|
|
Title = "Jube.App.Api",
|
|
Version = "v1"
|
|
});
|
|
c.CustomSchemaIds(type => type.FullName);
|
|
c.OperationFilter<AuthorizationHeaderParameterOperationFilter>();
|
|
});
|
|
}
|
|
|
|
private static void ConfigureAuthentication(IServiceCollection services, DynamicEnvironment dynamicEnvironment)
|
|
{
|
|
var jwtValidAudience = dynamicEnvironment.AppSettings("JWTValidAudience");
|
|
var jwtValidIssuer = dynamicEnvironment.AppSettings("JWTValidIssuer");
|
|
var jwtKey = dynamicEnvironment.AppSettings("JWTKey");
|
|
|
|
if (dynamicEnvironment.AppSettings("NegotiateAuthentication")
|
|
.Equals("True", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
|
|
.AddNegotiate();
|
|
|
|
services.AddAuthorization(options => { options.FallbackPolicy = options.DefaultPolicy; });
|
|
}
|
|
else
|
|
{
|
|
services.AddAuthentication(options =>
|
|
{
|
|
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
|
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
|
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
|
|
}).AddJwtBearer(options =>
|
|
{
|
|
options.SaveToken = true;
|
|
options.AutomaticRefreshInterval = TimeSpan.FromMinutes(5);
|
|
options.RequireHttpsMetadata = false;
|
|
options.TokenValidationParameters = new TokenValidationParameters
|
|
{
|
|
ClockSkew = TimeSpan.Zero,
|
|
NameClaimType = ClaimTypes.Name,
|
|
ValidateIssuer = true,
|
|
ValidateAudience = true,
|
|
ValidAudience = jwtValidAudience,
|
|
ValidIssuer = jwtValidIssuer,
|
|
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey))
|
|
};
|
|
options.Events = new JwtBearerEvents
|
|
{
|
|
OnTokenValidated = context =>
|
|
{
|
|
if (DateTime.UtcNow.AddMinutes(10) < context.SecurityToken.ValidTo)
|
|
{
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
var token = Jwt.CreateToken(context.Principal?.Identity?.Name,
|
|
jwtKey,
|
|
jwtValidIssuer,
|
|
jwtValidAudience
|
|
);
|
|
|
|
var cookieOptions = new CookieOptions
|
|
{
|
|
Expires = DateTime.Now.AddMinutes(15),
|
|
HttpOnly = true
|
|
};
|
|
|
|
var tokenString = new JwtSecurityTokenHandler().WriteToken(token);
|
|
|
|
context.Response.Headers.Append("authentication",
|
|
tokenString);
|
|
|
|
context.Response.Cookies.Append("authentication", tokenString
|
|
, cookieOptions);
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
};
|
|
});
|
|
}
|
|
}
|
|
|
|
private static void AddSingletonForEngine(IServiceCollection services
|
|
, DynamicEnvironment dynamicEnvironment, ILog log, IConnection rabbitMqConnection,
|
|
CacheService cacheService, JsonSerializationHelper jsonSerializationHelper, ITaskCoordinator taskCoordinator)
|
|
{
|
|
if (!dynamicEnvironment.AppSettings("EnableEngine").Equals("True", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var engine = new Engine(dynamicEnvironment, log, rabbitMqConnection, cacheService, jsonSerializationHelper, taskCoordinator);
|
|
|
|
services.AddSingleton(engine);
|
|
}
|
|
|
|
private static IConnection AddSingletonForRabbitMqConnection(IServiceCollection services, DynamicEnvironment dynamicEnvironment, ILog log)
|
|
{
|
|
|
|
IConnection rabbitMqConnection = null;
|
|
if (dynamicEnvironment.AppSettings("AMQP").Equals("True", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
rabbitMqConnection = ConnectToRabbitMqChannel(services, log, dynamicEnvironment.AppSettings("AMQPUri"), Int32.Parse(dynamicEnvironment.AppSettings("AMQPHeartbeatSeconds") ?? "30"));
|
|
}
|
|
else
|
|
{
|
|
if (log.IsInfoEnabled)
|
|
{
|
|
log.Info(
|
|
"Start: No connection to AMQP is being made. AMQP will be bypassed throughout the application.");
|
|
}
|
|
}
|
|
return rabbitMqConnection;
|
|
}
|
|
|
|
private static ConcurrentDictionary<Guid, TaskCompletionSource<Callback>> AddSingletonForCallbacks(IServiceCollection services)
|
|
{
|
|
var callbacks = new ConcurrentDictionary<Guid, TaskCompletionSource<Callback>>();
|
|
services.AddSingleton(callbacks);
|
|
|
|
return callbacks;
|
|
}
|
|
|
|
|
|
private static CacheService AddSingletonForCacheService(IServiceCollection services,
|
|
ConcurrentDictionary<Guid, TaskCompletionSource<Callback>> callbacks, int callbackTimeout,
|
|
TaskCoordinator taskCoordinator, DynamicEnvironment dynamicEnvironment, ILog log)
|
|
{
|
|
var cacheService =
|
|
ConnectToRedis(dynamicEnvironment.AppSettings("RedisConnectionString"),
|
|
dynamicEnvironment.AppSettings("ConnectionString"),
|
|
callbacks,
|
|
callbackTimeout,
|
|
dynamicEnvironment.AppSettings("LocalCache").Equals("True", StringComparison.OrdinalIgnoreCase),
|
|
dynamicEnvironment.AppSettings("LocalCacheFill").Equals("True", StringComparison.OrdinalIgnoreCase),
|
|
Int64.Parse(dynamicEnvironment.AppSettings("LocalCacheBytes")),
|
|
dynamicEnvironment.AppSettings("RedisMessagePackCompression").Equals("True", StringComparison.OrdinalIgnoreCase),
|
|
dynamicEnvironment.AppSettings("RedisStorePayloadCountsAndBytes").Equals("True", StringComparison.OrdinalIgnoreCase),
|
|
dynamicEnvironment.AppSettings("RedisPublishSubscribeEvents").Equals("True", StringComparison.OrdinalIgnoreCase),
|
|
log);
|
|
|
|
if (dynamicEnvironment.AppSettings("EnableMigration").Equals("True", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
RunFluentMigrator(dynamicEnvironment, cacheService, log);
|
|
}
|
|
|
|
cacheService.InstantiateRepositoriesTask = taskCoordinator.RunAsync("InstantiateRepositoriesAsync", _ => cacheService.InstantiateRepositoriesAsync(taskCoordinator));
|
|
|
|
services.AddSingleton(cacheService);
|
|
|
|
return cacheService;
|
|
}
|
|
|
|
private static DynamicEnvironment AddSingletonForDynamicEnvironmentAndLogging(IServiceCollection services, out ILog log)
|
|
{
|
|
|
|
var dynamicEnvironment = new DynamicEnvironment();
|
|
log = dynamicEnvironment.Log;
|
|
|
|
services.AddSingleton(log);
|
|
services.AddSingleton(dynamicEnvironment);
|
|
return dynamicEnvironment;
|
|
}
|
|
|
|
private static JsonSerializationHelper AddSingletonForJsonSerializationHelper(IServiceCollection services)
|
|
{
|
|
|
|
var jsonSerializationHelper = new JsonSerializationHelper();
|
|
services.AddSingleton(jsonSerializationHelper);
|
|
return jsonSerializationHelper;
|
|
}
|
|
|
|
private static void ValidateConnectionToPostgres(string connectionString, ILog log)
|
|
{
|
|
const int retryConnectionToPostgres = 10;
|
|
for (var i = 0; i < retryConnectionToPostgres; i++)
|
|
{
|
|
try
|
|
{
|
|
if (log.IsInfoEnabled)
|
|
{
|
|
log.Info("Is attempting a connection validation for Postgres.");
|
|
}
|
|
|
|
var connection = new NpgsqlConnection(connectionString);
|
|
var command = new NpgsqlCommand("select true");
|
|
connection.Open();
|
|
command.Connection = connection;
|
|
command.ExecuteNonQuery();
|
|
connection.Close();
|
|
|
|
if (log.IsInfoEnabled)
|
|
{
|
|
log.Info("Postgres connection validated.");
|
|
}
|
|
|
|
return;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (log.IsInfoEnabled)
|
|
{
|
|
log.Info($"Could not connect to Postgres after {i} attempts for {ex.Message}.");
|
|
}
|
|
|
|
#pragma warning disable VSTHRD002
|
|
Task.Delay(6000).Wait();
|
|
#pragma warning restore VSTHRD002
|
|
}
|
|
}
|
|
|
|
throw new Exception($"Could not connect to Postgres after {retryConnectionToPostgres}.");
|
|
}
|
|
|
|
private static IConnection ConnectToRabbitMqChannel(IServiceCollection services, ILog log,
|
|
string amqpUrl, int heartbeat)
|
|
{
|
|
const int retryRabbitMqConnection = 10;
|
|
for (var i = 0; i < retryRabbitMqConnection; i++)
|
|
{
|
|
try
|
|
{
|
|
if (log.IsInfoEnabled)
|
|
{
|
|
log.Info("Start: Is going to make a connection to AMQP Uri " +
|
|
amqpUrl + "");
|
|
}
|
|
|
|
var uri = new Uri(amqpUrl);
|
|
var rabbitMqConnectionFactory = new ConnectionFactory
|
|
{
|
|
Uri = uri,
|
|
RequestedHeartbeat = TimeSpan.FromSeconds(heartbeat)
|
|
};
|
|
var rabbitMqConnection = rabbitMqConnectionFactory.CreateConnection();
|
|
services.AddSingleton(rabbitMqConnection);
|
|
|
|
if (log.IsInfoEnabled)
|
|
{
|
|
log.Info("Start: Has made a connection to AMQP Uri " + amqpUrl + "");
|
|
}
|
|
|
|
services.AddSingleton(rabbitMqConnection);
|
|
|
|
return rabbitMqConnection;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (log.IsInfoEnabled)
|
|
{
|
|
log.Info($"Start: Error making a connection to AMQP Uri after {i} attempts " +
|
|
amqpUrl + " with error " + ex);
|
|
}
|
|
|
|
#pragma warning disable VSTHRD002
|
|
Task.Delay(3000).Wait();
|
|
#pragma warning restore VSTHRD002
|
|
}
|
|
}
|
|
|
|
throw new Exception($"Could not connect to RabbitMQ after {retryRabbitMqConnection} attempts.");
|
|
}
|
|
|
|
private static CacheService ConnectToRedis(string redisConnectionString,
|
|
string postgresConnectionString,
|
|
ConcurrentDictionary<Guid, TaskCompletionSource<Callback>> callbacks,
|
|
int callbackTimeout,
|
|
bool localCache, bool localCacheFill,
|
|
long localCacheBytes,
|
|
bool messagePackCompression,
|
|
bool storePayloadCountsAndBytes,
|
|
bool publishSubscribe, ILog log)
|
|
{
|
|
const int retryRedisConnectionRetry = 10;
|
|
for (var i = 0; i < retryRedisConnectionRetry; i++)
|
|
{
|
|
try
|
|
{
|
|
if (log.IsInfoEnabled)
|
|
{
|
|
log.Info("Start: Is going to make a connection to Redis Endpoints string showing " +
|
|
"endpoints and port seperated by :, then combined seperated by comma " +
|
|
"for example localhost:1234,localhost4321. Value for parsing is " +
|
|
redisConnectionString + "");
|
|
}
|
|
|
|
var cacheService = new CacheService(redisConnectionString,
|
|
postgresConnectionString, callbacks,
|
|
callbackTimeout,
|
|
localCache, localCacheFill,
|
|
localCacheBytes, messagePackCompression, storePayloadCountsAndBytes, publishSubscribe, log);
|
|
|
|
if (log.IsInfoEnabled)
|
|
{
|
|
log.Info("Connected to Redis. Returning connection for startup.");
|
|
}
|
|
|
|
return cacheService;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (log.IsInfoEnabled)
|
|
{
|
|
log.Info($"Can't make a connection to Redis after {i} attempt(s) for {ex.Message}.");
|
|
}
|
|
|
|
#pragma warning disable VSTHRD002
|
|
Task.Delay(1500).Wait();
|
|
#pragma warning restore VSTHRD002
|
|
}
|
|
}
|
|
|
|
throw new Exception($"Could not connect to Redis after {retryRedisConnectionRetry} attempts.");
|
|
}
|
|
|
|
#pragma warning disable AsyncFixer03
|
|
#pragma warning disable VSTHRD100
|
|
public async void Configure(IApplicationBuilder app, IWebHostEnvironment env,
|
|
#pragma warning restore VSTHRD100
|
|
#pragma warning restore AsyncFixer03
|
|
DynamicEnvironment dynamicEnvironment, ILog log)
|
|
{
|
|
try
|
|
{
|
|
if (env.IsDevelopment())
|
|
{
|
|
app.UseDeveloperExceptionPage();
|
|
}
|
|
|
|
app.UseRouting();
|
|
|
|
var lifetime = app.ApplicationServices.GetRequiredService<IHostApplicationLifetime>();
|
|
app.UseWhen(context => context.Request.Path.StartsWithSegments("/api") &&
|
|
lifetime.ApplicationStopping.IsCancellationRequested,
|
|
branch => branch.UseConditionalConnectionCloseWhenStopping());
|
|
|
|
app.UseWhen(context => context.Request.Path.StartsWithSegments("/Account/Login"), appBuilder =>
|
|
{
|
|
appBuilder.Use(async (context, next) =>
|
|
{
|
|
context.Response.Headers.Append("Content-Security-Policy", "frame-ancestors 'none'");
|
|
await next.Invoke();
|
|
});
|
|
});
|
|
|
|
app.UseWhen(
|
|
httpContext =>
|
|
!httpContext.Request.Path.StartsWithSegments("/api/invoke", StringComparison.OrdinalIgnoreCase),
|
|
appBuilder => appBuilder.UseHttpsRedirection()
|
|
);
|
|
|
|
app.UseWhen(
|
|
httpContext =>
|
|
!httpContext.Request.Path.StartsWithSegments("/api/invoke", StringComparison.OrdinalIgnoreCase),
|
|
appBuilder => appBuilder.UseExceptionHandler("/Error")
|
|
);
|
|
|
|
app.UseWhen(
|
|
httpContext =>
|
|
!httpContext.Request.Path.StartsWithSegments("/api/invoke", StringComparison.OrdinalIgnoreCase),
|
|
appBuilder =>
|
|
appBuilder
|
|
.UseHsts()// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
|
);
|
|
|
|
app.UseWhen(
|
|
httpContext =>
|
|
!httpContext.Request.Path.StartsWithSegments("/api/invoke", StringComparison.OrdinalIgnoreCase),
|
|
appBuilder => appBuilder.UseStatusCodePages(context =>
|
|
|
|
{
|
|
var request = context.HttpContext.Request;
|
|
var response = context.HttpContext.Response;
|
|
|
|
if (response.StatusCode != (int)HttpStatusCode.Unauthorized)
|
|
{
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
if (!request.Path.StartsWithSegments("/api"))
|
|
{
|
|
response.Redirect("/Account/Login");
|
|
}
|
|
|
|
return Task.CompletedTask;
|
|
})
|
|
);
|
|
|
|
app.UseWhen(
|
|
httpContext =>
|
|
!httpContext.Request.Path.StartsWithSegments("/api/invoke", StringComparison.OrdinalIgnoreCase),
|
|
appBuilder => appBuilder.UseTransposeJwtFromCookieToHeaderMiddleware()
|
|
);
|
|
|
|
app.UseWhen(
|
|
httpContext =>
|
|
!httpContext.Request.Path.StartsWithSegments("/api/invoke", StringComparison.OrdinalIgnoreCase),
|
|
appBuilder => appBuilder.RequestTrackingMiddleware()
|
|
);
|
|
|
|
app.UseWhen(
|
|
httpContext =>
|
|
!httpContext.Request.Path.StartsWithSegments("/api/invoke", StringComparison.OrdinalIgnoreCase),
|
|
appBuilder => appBuilder.UseStaticFiles()
|
|
);
|
|
|
|
app.UseWhen(
|
|
httpContext =>
|
|
!httpContext.Request.Path.StartsWithSegments("/api/invoke", StringComparison.OrdinalIgnoreCase),
|
|
appBuilder => appBuilder.UseSwagger()
|
|
);
|
|
|
|
app.UseWhen(
|
|
httpContext =>
|
|
!httpContext.Request.Path.StartsWithSegments("/api/invoke", StringComparison.OrdinalIgnoreCase),
|
|
appBuilder => appBuilder.UseSwaggerUI()
|
|
);
|
|
|
|
app.UseWhen(
|
|
httpContext =>
|
|
!httpContext.Request.Path.StartsWithSegments("/api/invoke", StringComparison.OrdinalIgnoreCase),
|
|
appBuilder => appBuilder.UseAuthentication()
|
|
);
|
|
|
|
app.UseWhen(
|
|
httpContext =>
|
|
!httpContext.Request.Path.StartsWithSegments("/api/invoke", StringComparison.OrdinalIgnoreCase),
|
|
appBuilder => appBuilder.UseAuthorization()
|
|
);
|
|
|
|
app.UseEndpoints(endpoints =>
|
|
{
|
|
endpoints.MapRazorPages();
|
|
endpoints.MapControllers();
|
|
endpoints.MapHub<WatcherHub>("/watcherHub");
|
|
});
|
|
|
|
await app.StartRelayAsync().ConfigureAwait(false);
|
|
await app.StartEngineAsync().ConfigureAwait(false);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
log.Error($"Error in App Configure as {ex}");
|
|
}
|
|
}
|
|
|
|
private static void RunFluentMigrator(DynamicEnvironment dynamicEnvironment,
|
|
CacheService cacheService, ILog log)
|
|
{
|
|
var serviceCollection = new ServiceCollection().AddFluentMigratorCore()
|
|
.AddSingleton(dynamicEnvironment)
|
|
.AddSingleton(cacheService)
|
|
.AddSingleton(log)
|
|
.ConfigureRunner(rb => rb
|
|
.AddPostgres11_0()
|
|
.WithGlobalConnectionString(dynamicEnvironment.AppSettings("ConnectionString"))
|
|
.ScanIn(typeof(AddActivationWatcherTableIndex).Assembly).For.Migrations())
|
|
.BuildServiceProvider(false);
|
|
|
|
using var scope = serviceCollection.CreateScope();
|
|
var runner = serviceCollection.GetRequiredService<IMigrationRunner>();
|
|
runner.MigrateUp();
|
|
}
|
|
|
|
private void ConfigureThreadPool(DynamicEnvironment dynamicEnvironment, ILog log)
|
|
{
|
|
if (dynamicEnvironment.AppSettings("ThreadPoolManualControl")
|
|
.Equals("True", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
ThreadPool.SetMinThreads(Int32.Parse(dynamicEnvironment.AppSettings("MinThreadPoolThreads")),
|
|
Int32.Parse(dynamicEnvironment.AppSettings("MinThreadPoolThreads")));
|
|
|
|
if (log.IsDebugEnabled)
|
|
{
|
|
log.Debug(
|
|
$"Start: Set the min threads to {dynamicEnvironment.AppSettings("MinThreadPoolThreads")} from the configuration file.");
|
|
}
|
|
|
|
ThreadPool.SetMaxThreads(Int32.Parse(dynamicEnvironment.AppSettings("MaxThreadPoolThreads")),
|
|
Int32.Parse(dynamicEnvironment.AppSettings("MaxThreadPoolThreads")));
|
|
|
|
if (log.IsDebugEnabled)
|
|
{
|
|
log.Debug(
|
|
$"Start: Set the max threads to {Int32.Parse(dynamicEnvironment.AppSettings("MaxThreadPoolThreads"))} from the configuration file.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (log.IsDebugEnabled)
|
|
{
|
|
log.Debug("Start: No manual thread pool parameters have been set will configure based on CPU count and certain other estimates.");
|
|
}
|
|
|
|
var logicalCores = Environment.ProcessorCount;
|
|
var workerThreads = logicalCores * 2;
|
|
var ioThreads = logicalCores * 4;
|
|
ThreadPool.SetMinThreads(workerThreads, ioThreads);
|
|
}
|
|
}
|
|
}
|
|
}
|