using System; using System.Collections.Generic; using System.Globalization; using System.IdentityModel.Tokens.Jwt; using System.IO; using System.Net; using System.Net.Http; using System.Security.Claims; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using Aeroflot.Common.ApiWrappers; using Aeroflot.Common2.ApiWrapper.Aeroflot; using Aeroflot.Common2.ApiWrapper.Aeroflot.FallbackService; using Aeroflot.Common2.ApiWrapper.Aeroflot.Proxies; using Aeroflot.Common2.DataAccess.S3; using Aeroflot.Common2.Extensions; using Aeroflot.Common2.Observability.AspNetCore; using Aeroflot.Flights.BL; using Aeroflot.Flights.BL.Helpers; using Aeroflot.Flights.BL.Models; using Aeroflot.Flights.BL.Models.FlightFilter; using Aeroflot.Flights.DataSpace; using Aeroflot.Flights.Interfaces.Models; using Aeroflot.Flights.WebApi; using Aeroflot.Flights.WebApi.Models; using Aeroflot.Flights.WebApi.Models.Onlineboard.ApiV1; using Aeroflot.Flights.WebApi.Providers; //using Aeroflot.Flights.WebApi.SignalR; using Aeroflot.Security2; using Aeroflot.Security2.DataAccess; using Aeroflot.Security2.DataSpace; using Aeroflot.Security2.Entities; using Aeroflot.Security2.Entities.DataSpace; using Aeroflot.Security2.Services.EmailService; using Amazon.Runtime; using Amazon.S3; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; //using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SpaServices.AngularCli; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.OpenApi.Models; using Newtonsoft.Json.Serialization; using Prometheus; using Serilog; namespace Aeroflot.Flights.Web { public class Startup { private const string UserAgent = "Aeroflot.Flights / 1.0.001"; //"Aeroflot.Passbook / 1.7.013"; //private IHubContext hubContext; public Startup(IWebHostEnvironment env) { Environment = env; var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) .AddEnvironmentVariables(); Configuration = builder.Build(); } public IWebHostEnvironment Environment { get; } public IConfiguration Configuration { get; } public class AuthMessageHandler : DelegatingHandler { public AuthMessageHandler() { InnerHandler = new HttpClientHandler(); } protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { request.Headers.Add("User-Agent", UserAgent); return base.SendAsync(request, cancellationToken); } } public void ConfigureServices(IServiceCollection services) { var keysFolder = System.IO.Path.Combine(Environment.ContentRootPath, "App_Data"); services.AddDataProtection() .SetApplicationName("Flights") .PersistKeysToFileSystem(new DirectoryInfo(keysFolder)) .SetDefaultKeyLifetime(TimeSpan.FromDays(36500)) // 100 years .DisableAutomaticKeyGeneration(); //IdentityModelEventSource.ShowPII = true; services.TryAddSingleton(); services.AddSpaStaticFiles(config => { config.RootPath = "ClientApp/dist"; }); //var apiAssembly = typeof(WebApi.Controllers.FlightsController).Assembly; services.AddMvc() .AddNewtonsoftJson(opt => { opt.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); opt.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore; opt.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore; opt.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter()); opt.AllowInputFormatterExceptionMessages = true; }) .AddXmlSerializerFormatters() .AddXmlDataContractSerializerFormatters(); services.AddSession(); services.AddResponseCaching(); services.AddControllers(options => { options.CacheProfiles.Add( "DefaultBoard", new CacheProfile() { Duration = int.TryParse(Configuration["Options:Cache:DefaultBoard"], out int cacheBoardSeconds) ? cacheBoardSeconds : 60, VaryByQueryKeys = new[] { "*" }, }); options.CacheProfiles.Add( "DefaultDictionary", new CacheProfile() { Duration = int.TryParse(Configuration["Options:Cache:DefaultDictionary"], out int cacheDictionarySeconds) ? cacheDictionarySeconds : 3600, VaryByQueryKeys = new[] { "*" }, }); options.CacheProfiles.Add( "DefaultSchedule", new CacheProfile() { Duration = int.TryParse(Configuration["Options:Cache:DefaultSchedule"], out int cacheScheduleSeconds) ? cacheScheduleSeconds : 600, VaryByQueryKeys = new[] { "*" }, }); options.CacheProfiles.Add( "DefaultDetails", new CacheProfile() { Duration = int.TryParse(Configuration["Options:Cache:DefaultDetails"], out int cacheDetailsSeconds) ? cacheDetailsSeconds : 30, VaryByQueryKeys = new[] { "*" }, }); options.CacheProfiles.Add( "DefaultAirport", new CacheProfile() { Duration = int.TryParse(Configuration["Options:Cache:DefaultAirport"], out int cacheAirportSeconds) ? cacheAirportSeconds : 600, Location = ResponseCacheLocation.Any, VaryByQueryKeys = new[] { "*" }, }); options.CacheProfiles.Add( "Default1HourBasedOnQueryKeys", new CacheProfile() { Duration = 3600, VaryByQueryKeys = new[] { "*" }, }); options.CacheProfiles.Add( "DefaultRequests", new CacheProfile() { Duration = int.TryParse(Configuration["Options:Cache:DefaultRequest"], out int cacheRequestsSeconds) ? cacheRequestsSeconds : 600, VaryByQueryKeys = new[] { "*" }, }); }) .AddXmlSerializerFormatters() .AddXmlDataContractSerializerFormatters(); services.AddWebApi(); services.AddMemoryCache(); services.AddSwaggerGen(c => { //c.TagActionsBy(tagSelector => tagSelector.); c.CustomSchemaIds(type => type.FullName.ToString()); c.SwaggerDoc("v1", new OpenApiInfo { Title = "Aeroflot Flights", Version = "v1" }); c.AddSecurityDefinition( "Bearer", new OpenApiSecurityScheme { In = ParameterLocation.Header, Description = "Please enter JWT with Bearer into field", Name = "Authorization", Type = SecuritySchemeType.ApiKey, }); c.AddSecurityRequirement(new OpenApiSecurityRequirement() { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer", }, Scheme = "oauth2", Name = "Bearer", In = ParameterLocation.Header, }, new List() }, }); c.DescribeAllParametersInCamelCase(); }); services.Configure(Configuration.GetSection("Swagger")); services.AddEndpointsApiExplorer(); //ConfigureAuthService(services, Configuration); #region //ToDo: refactor this //var connectionString = Configuration.GetConnectionString("ConnectionString"); //services.AddDbContext( // builder => // builder.EnableSensitiveDataLogging().UseSqlServer(connectionString, options => options.EnableRetryOnFailure()), // ServiceLifetime.Transient); //services.AddDbContext( // builder => // builder.UseSqlServer(connectionString, options => options.EnableRetryOnFailure()), // ServiceLifetime.Transient); var scheduleAppUri = new Uri(Configuration["Api:ScheduleAppUri"]); var scheduleV2AppUri = new Uri(Configuration["Api:ScheduleV2AppUri"]); Func uriAccessor = key => { switch (key) { case Common.ApiWrappers.Schedule.Schedule.AppUri: return scheduleAppUri; case Common.ApiWrappers.Schedule.Schedule.AppV2Uri: return scheduleV2AppUri; default: throw new ArgumentOutOfRangeException(); } }; services.AddSingleton(uriAccessor); //services.AddSingleton(Options.Create(new FlightsApiWrapperSettings { FlightsUri = new Uri(Configuration["Dictionary:FlightsApiAppUri"]) })); #endregion services.Configure(Configuration.GetSection("Options:RefreshableApiProvider")); services.Configure(Configuration.GetSection("Options:ApiWrapperProxy")); services.Configure(Configuration.GetSection("Options:DatesProviderOptions")); services.Configure(Configuration.GetSection("Options:AeroflotApiWrapper")); services.Configure(Configuration.GetSection("Options:TransitionProvider")); services.Configure(Configuration.GetSection("Options:LegService")); services.Configure(Configuration.GetSection("Options:ConnectionsProvider")); services.Configure(Configuration.GetSection("Options:Validation")); services.Configure(Configuration.GetSection("Options:WhiteListCarriers")); Enum.TryParse(Configuration.GetSection("Options:AeroflotFallbackOptions:ServiceType").Value, true, out FallbackServiceType serviceType); var options = Configuration.GetAWSOptions("YandexAWS:AWS"); options.Credentials = new BasicAWSCredentials(Configuration.GetSection("YandexAWS:AWS:AccessKey").Value, Configuration.GetSection("YandexAWS:AWS:SecretKey").Value); services.AddDefaultAWSOptions(options); services.AddAWSService(); services.AddSingleton>(sp => ActivatorUtilities.CreateInstance>(sp, Configuration.GetSection("YandexAWS:Bucket").Value)); services.AddSingleton>(sp => ActivatorUtilities.CreateInstance>(sp, Configuration.GetSection("YandexAWS:Bucket").Value)); services.AddOptions().Bind(Configuration.GetSection("Options:FlightFilter")); services.AddOptions().Bind(Configuration.GetSection("HeadersOptions")); services.AddBlWeb(serviceType); //add dataspace services.AddFlightsClientGraphQL(Configuration["DataSpace:FlightsModelEndpoint"]); services.AddSecurity2ClientGraphQL(Configuration["DataSpace:FlightsModelEndpoint"]); //services.AddScoped(); // (_ => new IdentityRepository((IAsyncGenericRepository)_.GetService(typeof(AsyncGenericRepository)))); //services.AddScoped, PasswordHasher>(); //services.AddScoped(); //services.AddScoped(); //services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddAeroflotIdentityCore(o => { o.User.RequireUniqueEmail = true; o.User.AllowedUserNameCharacters = "абвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗКИЙКЛМНОПРСТУФЧЦЧШЩЪЫЬЭЮЯ abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; o.Lockout.MaxFailedAccessAttempts = 7; }) .AddDefaultTokenProviders(); services.AddDefaultPasswordValidators(); services.TryAddScoped>(); var configEws = Configuration.GetSection("EwsServiceOptions"); var ewsOptions = new EWSOptions() { AsmxUri = configEws.GetValue("EwsAsmxUri"), Password = configEws.GetValue("EwsPassword"), Username = configEws.GetValue("EwsUserName"), }; var configEmail = Configuration.GetSection("EmailOptions"); services.AddEWSEmailService(new EmailOptions() { AppName = configEmail.GetValue("AppName"), AppUrl = configEmail.GetValue("AppUrl"), InvitationUrl = configEmail.GetValue("InvitationUrl"), EWSOptions = ewsOptions, }); services.TryAddScoped, DefaultUserConfirmation>(); services.TryAddScoped>(); services.Configure(x => { x.ValueLengthLimit = int.MaxValue; x.MultipartBodyLengthLimit = int.MaxValue; }); services.AddAuthentication(options => { options.DefaultScheme = IdentityConstants.ApplicationScheme; options.DefaultSignInScheme = IdentityConstants.ExternalScheme; options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => { options.Authority = Configuration["Identity:AuthorityUri"]; options.Audience = Configuration["Identity:Audience"]; options.RequireHttpsMetadata = false; options.BackchannelHttpHandler = new AuthMessageHandler(); }) .AddIdentityCookies(o => { if (Environment.IsDevelopment()) { o.ApplicationCookie.Configure(options => { options.Cookie.HttpOnly = true; options.Cookie.SameSite = SameSiteMode.None; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; }); o.ExternalCookie.Configure(options => { options.Cookie.HttpOnly = true; options.Cookie.SameSite = SameSiteMode.None; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; }); } }); services.AddAuthorization(options => { options.DefaultPolicy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .Build(); options.AddPolicy("policyjwt", policy => { policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme) .RequireAuthenticatedUser(); }); options.AddPolicy("policycookie", policy => { policy.AddAuthenticationSchemes(IdentityConstants.ApplicationScheme) .RequireAuthenticatedUser(); }); }); services.AddHealthChecks(); services.AddCors(options => { options.AddPolicy( "Aeroflot", builder => { builder .WithOrigins("http://*.aeroflot.ru", "https://*.aeroflot.ru", "http://aeroflot.baccasoft.ru", "https://aeroflot.baccasoft.ru", "http://*.test.aeroflot.ru", "https://*.test.aeroflot.ru") .AllowAnyMethod() .AllowAnyHeader() .SetIsOriginAllowedToAllowWildcardSubdomains(); }); options.AddPolicy( "AllowAnyHeader", builder => { builder .AllowAnyOrigin() .AllowAnyHeader() .AllowAnyMethod(); }); }); //services.AddTransient(c => new FlightHubClient(hubContext)); //services.AddSignalR(); services.AddMvc() .AddRazorPagesOptions(options => { options.Conventions.AddPageRoute("/Robots", "/robots.txt"); options.Conventions.AddPageRoute("/Sitemap", "/sitemap.xml"); }); services.Configure(options => { options.ForwardLimit = 3; foreach (var ip in Configuration.GetSection("IpSettings").Get>()) { options.KnownNetworks.Add(new Microsoft.AspNetCore.HttpOverrides.IPNetwork(IPAddress.Parse(ip.Ip), ip.Mask)); } options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; }); services.AddOpenTelemetryAspNetCore(Configuration["OpenTelemetry:ServiceName"], Configuration["OpenTelemetry:ServiceNamespace"], Configuration["OpenTelemetry:OtlpExporterEndpoint"]); services.Configure(o => { o.SerializerOptions.Converters.Add(new JsonStringEnumConverter()); o.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; // DataSpace ids are 64 bit and need to be strings in Json // ints in Json has only about 53bit precision o.SerializerOptions.NumberHandling = JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowReadingFromString; o.SerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; }); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory logger) { app.UseForwardedHeaders(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseBrowserLink(); } app.UseHealthChecks("/healthy"); app.UseResponseCaching(); app.UseStaticFiles(); //if (!env.IsDevelopment()) //{ // app.UseSpaStaticFiles(); //} app.UseSpaStaticFiles(); app.Use(async (context, next) => { context.Request.EnableBuffering(); //var hubContext = context.RequestServices // .GetRequiredService>(); //this.hubContext = hubContext; await next.Invoke(); }); app.UseHttpsRedirection(); app.UseRouting(); app.UseCors("Aeroflot"); app.UseHttpMetrics(); app.UseAuthentication(); app.UseAuthorization(); app.UseSession(); app.UseSerilogRequestLogging(); app.UseEndpoints(endpoints => { endpoints.MapGet( "api/v{version}/{lang}/airports", ( [FromServices] WebApi.Controllers.Api.AirportController controller, HttpContext httpContext, [FromRoute] double version, [FromRoute] CaseInsensitive lang) => controller.GetAirports(httpContext, version, lang.Value, "json")).Produces>>(StatusCodes.Status200OK) .CacheOutput("DefaultAirport").WithTags("Airports").WithSummary("Get Airports"); endpoints.MapGet( "api/v{version}/{lang}/airports.{format}", ( [FromServices] WebApi.Controllers.Api.AirportController controller, HttpContext httpContext, [FromRoute] double version, [FromRoute] CaseInsensitive lang, [FromRoute] string format) => controller.GetAirports(httpContext, version, lang.Value, format)).Produces>>(StatusCodes.Status200OK) .CacheOutput("DefaultAirport").WithTags("Airports").WithSummary("Get Airports"); endpoints.MapGet( "api/v{version}/{lang}/airports/pairs", ( [FromServices] WebApi.Controllers.Api.AirportController controller, HttpContext httpContext, [FromRoute] double version, [FromRoute] CaseInsensitive lang) => controller.GetAirportPairs(httpContext, version, lang.Value)).Produces>(StatusCodes.Status200OK) .CacheOutput("DefaultAirport").WithTags("Airports").WithSummary("Get Airports"); endpoints.MapGet( "/api/AppSettings", ([FromServices] WebApi.Controllers.App.AppSettingsController controller) => controller.Get()).Produces(StatusCodes.Status200OK) .WithTags("AppSettings").WithSummary("Get App settings"); endpoints.MapPost( "/api/Meal/checkspecial", ([FromServices] WebApi.Controllers.App.MealController controller, [FromBody] MealApiRequestModel model) => controller.CheckMealAsync(model)).Produces(StatusCodes.Status200OK) .WithTags("Meal").WithSummary("Get meal information"); endpoints.MapGet( "api/v{version}/{language}/delays/statistics", ( [FromServices] WebApi.Controllers.Api.DelaysController controller, HttpContext httpContext, ClaimsPrincipal user, [FromRoute] double version, [FromRoute] string language) => controller.GetDelayStatistics(httpContext, user, version, language)).Produces(StatusCodes.Status200OK) .WithTags("Delays").WithSummary("Get delays statistics"); endpoints.MapGet( "api/Healthcheck", ( [FromServices] WebApi.Controllers.App.HealthcheckController controller, HttpContext httpContext, ClaimsPrincipal user) => controller.Get()).Produces(StatusCodes.Status200OK) .WithTags("Healthcheck").WithSummary("Health check"); endpoints.MapGet( "api/Dictionary/{version}/{dictionaryName}", ( [FromServices] WebApi.Controllers.App.DictionaryController controller, HttpContext httpContext, ClaimsPrincipal user, string dictionaryName, string version) => controller.GetData(dictionaryName, version)).Produces(StatusCodes.Status200OK) .CacheOutput("DefaultDictionary").WithTags("Dictionary").WithSummary("Get dictionaries"); endpoints.MapGet( "api/Flights/{version}/{lang}/board", ( [FromServices] WebApi.Controllers.App.FlightsController controller, HttpContext httpContext, ClaimsPrincipal user, [AsParameters] WebApi.Providers.SearchFlightParams searchParams, string version, string lang) => controller.GetOnlineBoard(searchParams, version, lang, httpContext)) .CacheOutput("DefaultBoard").WithTags("Flights").WithSummary("Get online board"); endpoints.MapGet( "api/Flights/{version}/{lang}/schedule", ( [FromServices] WebApi.Controllers.App.FlightsController controller, HttpContext httpContext, ClaimsPrincipal user, [AsParameters] WebApi.Providers.SearchFlightParams searchParams, string version, string lang) => controller.GetSchedule(searchParams, version, lang, httpContext)) .CacheOutput("DefaultSchedule").WithTags("Flights").WithSummary("Get schedule"); endpoints.MapGet( "api/Flights/{version}/{lang}/schedule_pdf", ( [FromServices] WebApi.Controllers.App.FlightsController controller, HttpContext httpContext, ClaimsPrincipal user, [AsParameters] WebApi.Providers.SearchFlightParams searchParams, string version, string lang) => controller.GetSchedulePdf(searchParams, version, lang, httpContext)) .CacheOutput("DefaultSchedule").WithTags("Flights").WithSummary("Get schedule PDF"); endpoints.MapGet( "api/Flights/{version}/{lang}/get_partner", ( [FromServices] WebApi.Controllers.App.FlightsController controller, HttpContext httpContext, ClaimsPrincipal user, [AsParameters] WebApi.Providers.SearchFlightParams searchParams, string version, string lang) => controller.GetPartner(searchParams, version, lang)) .WithTags("Flights").WithSummary("Get partner (obsolete)"); endpoints.MapGet( "api/Flights/{version}/{lang}/actual", ( [FromServices] WebApi.Controllers.App.FlightsController controller, HttpContext httpContext, ClaimsPrincipal user, [AsParameters] WebApi.Providers.SearchFlightParams searchParams, string version, string lang) => controller.GetActual(searchParams, version, lang)) .WithTags("Flights").WithSummary("Get actual (obsolete)"); endpoints.MapGet( "api/Flights/{version:regex(^(1|1.0|v1|v1.0)$)}/{lang}/detailed", ( [FromServices] WebApi.Controllers.App.FlightsController controller, HttpContext httpContext, ClaimsPrincipal user, [AsParameters] WebApi.Providers.SearchFlightParams searchParams, string version, string lang) => controller.GetDetailed(searchParams, version, lang)) .CacheOutput("DefaultDetails").WithTags("Flights").WithSummary("Get detailed (obsolete)"); endpoints.MapGet( "api/Flights/{version:regex(^(1|1.0|v1|v1.0)$)}/{lang}/{requestType}/details", ( [FromServices] WebApi.Controllers.App.FlightsController controller, HttpContext httpContext, ClaimsPrincipal user, [AsParameters] WebApi.Providers.SearchFlightParams searchParams, string version, string lang, string requestType) => controller.GetDetails(searchParams, version, lang, requestType)) .CacheOutput("DefaultDetails").WithTags("Flights").WithSummary("Get details (obsolete)"); endpoints.MapGet( "api/Flights/v{majorVersion:int}.{minorVersion:int:min(1)}/{lang}/{requestType}/details", ( [FromServices] WebApi.Controllers.App.FlightsController controller, HttpContext httpContext, ClaimsPrincipal user, //[FromQuery] string[] flights, -- does not work as it worked in controllers! //[FromQuery] DateTime[] dates, [FromQuery] string departure, [FromQuery] string arrival, [FromRoute] int majorVersion, [FromRoute] int minorVersion, [FromRoute] string lang, [FromRoute] string requestType) => { var flights = new List(); var dates = new List(); for (int i = 0; ; i++) { if (httpContext.Request.Query.ContainsKey("flights[" + i + "]") && httpContext.Request.Query.ContainsKey("dates[" + i + "]")) { if (DateTime.TryParseExact(httpContext.Request.Query["dates[" + i + "]"].ToString(), "yyyy-MM-ddTHH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt)) { flights.Add(httpContext.Request.Query["flights[" + i + "]"]); dates.Add(dt); } } else { break; } } if (httpContext.Request.Query.ContainsKey("flights") && httpContext.Request.Query.ContainsKey("dates")) { if (DateTime.TryParseExact(httpContext.Request.Query["dates"].ToString(), "yyyy-MM-ddTHH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt)) { flights.Add(httpContext.Request.Query["flights"]); dates.Add(dt); } } return controller.GetDetails(flights.ToArray(), dates.ToArray(), departure, arrival, majorVersion, minorVersion, lang, requestType); }) .CacheOutput("DefaultDetails").WithTags("Flights").WithSummary("Get details"); //endpoints.MapPost( // "api/Flights/updates", ( // [FromServices] WebApi.Controllers.App.FlightsController controller, // HttpContext httpContext, // ClaimsPrincipal user, // [FromBody] List < FlightUpdateModel > flightsUpdate) => // controller.PostUpdateFlight(flightsUpdate)) // .WithTags("Flights").WithSummary("Post flight updates"); endpoints.MapGet( "api/Flights/{version:regex(^(1|1.0|v1|v1.0)$)}/{lang}/destinations", ( [FromServices] WebApi.Controllers.App.FlightsController controller, HttpContext httpContext, [AsParameters] WebApi.Providers.SearchFlightParams searchParams, [FromRoute] string version, [FromRoute] string lang) => controller.GetDestinations(searchParams, lang)) .CacheOutput("DefaultSchedule").WithTags("Flights").WithSummary("Get destinations"); endpoints.MapGet( "api/Flights/{version:regex(^(1|1.0|v1|v1.0)$)}/{lang}/days/{date}/{daysNum}/{type}/{request}/{requestType}", ( [FromServices] WebApi.Controllers.App.FlightsController controller, HttpContext httpContext, ClaimsPrincipal user, [FromRoute] string version, [FromRoute] string lang, [FromRoute] DateTime date, [FromRoute] int daysNum, [FromRoute] string type, [FromRoute] string request, [FromRoute] string requestType) => controller.GetDays(version, lang, date, daysNum, type, request, requestType)) .CacheOutput("DefaultDetails").WithTags("Flights").WithSummary("Get flight days"); endpoints.MapGet( "api/{version:regex(^(v1|v1.0)$)}/{language}/flights", ( [FromServices] WebApi.Controllers.Api.V1.Onlineboard.FlightsController controller, HttpContext httpContext, [FromRoute] string language, [FromRoute] string version) => controller.SearchFlightsXml(httpContext, version, language)) .WithTags("Flights V1").WithSummary("Search onlineboard flights in xml"); endpoints.MapGet( "api/{version:regex(^(v1|v1.0)$)}/{language}/flights.{format}", ( [FromServices] WebApi.Controllers.Api.V1.Onlineboard.FlightsController controller, HttpContext httpContext, [FromRoute] string language, [FromRoute] string format, [FromRoute] string version) => controller.SearchFlights(httpContext, version, language, format)) .WithTags("Flights V1").WithSummary("Search onlineboard flights in requested format"); endpoints.MapGet( "api/v{version}/{language}/flight", ( [FromServices] WebApi.Controllers.Api.V1.Schedule.FlightController controller, HttpContext httpContext, [AsParameters] WebApi.Models.Schedule.ApiV1.SearchFlightParams searchParams, [FromRoute] string language, [FromRoute] double version) => controller.GetFlightXml(httpContext, version, language, searchParams)) .WithTags("Flights V1").WithSummary("Search schedule flights in xml"); endpoints.MapGet( "api/v{version}/{language}/flight.{format}", ( [FromServices] WebApi.Controllers.Api.V1.Schedule.FlightController controller, HttpContext httpContext, [AsParameters] WebApi.Models.Schedule.ApiV1.SearchFlightParams searchParams, [FromRoute] string language, [FromRoute] string format, [FromRoute] double version) => controller.GetFlight(httpContext, version, language, format, searchParams)) .WithTags("Flights V1").WithSummary("Search schedule flights in requested format"); endpoints.MapGet( "api/v{version}/{language}/aircraft", ( [FromServices] WebApi.Controllers.Api.FlightsController controller, HttpContext httpContext, [FromRoute] string language, [FromRoute] double version) => controller.GetAircraft(httpContext, version, language, "json")) .WithTags("Flights V2").WithSummary("Flight aircraft"); endpoints.MapGet( "api/v{version}/{language}/aircraft.{format}", ( [FromServices] WebApi.Controllers.Api.FlightsController controller, HttpContext httpContext, string format, [FromRoute] string language, [FromRoute] double version) => controller.GetAircraft(httpContext, version, language, format)) .WithTags("Flights V2").WithSummary("Flight aircraft"); endpoints.MapGet( "api/v{version}/{language}/dates", ( [FromServices] WebApi.Controllers.Api.FlightsController controller, HttpContext httpContext, [FromRoute] string language, [FromRoute] double version) => controller.GetDates(httpContext, version, language, "json")) .WithTags("Flights V2").WithSummary("Flight dates"); endpoints.MapGet( "api/v{version}/{language}/dates.{format}", ( [FromServices] WebApi.Controllers.Api.FlightsController controller, HttpContext httpContext, string format, [FromRoute] string language, [FromRoute] double version) => controller.GetDates(httpContext, version, language, format)) .WithTags("Flights V2").WithSummary("Flight dates"); endpoints.MapGet( "api/v{version:regex(^2)}/{lang}/flights", [AllowAnonymous] [Authorize(Policy = "policyjwt")] [Authorize(Policy = "policycookie")] ( [FromServices] WebApi.Controllers.Api.FlightsController controller, HttpContext httpContext, ClaimsPrincipal user, [FromRoute] string lang, [FromRoute] double version) => controller.GetFlights(httpContext, user, version, lang, "json")) .WithTags("Flights V2").WithSummary("Search flights in xml"); endpoints.MapGet( "api/v{version:regex(^2)}/{lang}/flights.{format}", [AllowAnonymous] [Authorize(Policy = "policyjwt")] [Authorize(Policy = "policycookie")] ( [FromServices] WebApi.Controllers.Api.FlightsController controller, HttpContext httpContext, ClaimsPrincipal user, [FromRoute] string lang, [FromRoute] string format, [FromRoute] double version) => controller.GetFlights(httpContext, user, version, lang, format)) .WithTags("Flights V2").WithSummary("Search flights in requested format"); endpoints.MapGet( "/api/Requests/{version}/GetPopular", ([FromServices] WebApi.Controllers.App.RequestsController controller, string version) => controller.GetPopular()).Produces>(StatusCodes.Status200OK) .CacheOutput("Requests").WithTags("Requests").WithSummary("Get populer requests"); endpoints.MapGet( "/api/Version", ([FromServices] WebApi.Controllers.App.VersionController controller) => controller.Get()).Produces(StatusCodes.Status200OK) .WithTags("Version").WithSummary("Get version"); endpoints.MapControllerRoute( name: "Flights", pattern: "api/{version}/{**url}", defaults: new { controller = "ExtFlights", action = "GetNotFound" }, constraints: new { version = @"v1(\.0)?", url = @".*/extflights.*" }); endpoints.MapControllerRoute( name: "Flights", pattern: "api/{version}/{**url}", defaults: new { controller = "Airport", action = "GetNotFound" }, constraints: new { url = @".*/airport.*", version = @"v1(\.(1|0))?" }); endpoints.MapControllerRoute( name: "Flights", pattern: "{**url}", defaults: new { controller = "Airport", action = "GetNotFound" }, constraints: new { url = @".*/airports.*" }); endpoints.MapControllerRoute( name: "Flights", pattern: "api/{version}/{**url}", defaults: new { controller = "Schedule", action = "GetNotFound" }, constraints: new { url = @".*/schedule.*", version = @"v1(\.0)?" }); endpoints.Map("/{country}-{lang}/404", context => { // this is because it asked to return 404 (nobody cares that we are on SPA, and it should be handled by SSR + ng Universal) context.Response.StatusCode = 404; return Task.CompletedTask; }); endpoints.Map("/{country}-{lang}/500", context => { // this is because it asked to return 500 (nobody cares that we are on SPA, and it should be handled by SSR + ng Universal) context.Response.StatusCode = 500; return Task.CompletedTask; }); endpoints.MapControllers(); endpoints.MapDefaultControllerRoute(); endpoints.MapRazorPages(); //endpoints.MapHub("/flights"); endpoints.MapMetrics(Configuration["Prometheus:Url"]); }); if (!String.IsNullOrEmpty(Configuration["Swagger:Password"])) { app.UseSwaggerAuthorized(); app.UseSwagger(); app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "Flights"); c.EnableTryItOutByDefault(); }); } app.UseSpa(spa => { spa.Options.SourcePath = "ClientApp"; spa.Options.StartupTimeout = new TimeSpan(0, 5, 0); if (env.IsDevelopment() && !Configuration.GetValue("DOTNET_RUNNING_IN_CONTAINER")) { spa.UseAngularCliServer(npmScript: "start"); } }); } private void ConfigureAuthService(IServiceCollection services, IConfiguration configuration) { var identityAuthority = configuration.GetSection("Identity").GetValue("Authority"); var identityClientId = configuration.GetSection("Identity").GetValue("ClientId"); var identityClientSecret = configuration.GetSection("Identity").GetValue("ClientSecret"); JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); services.AddAuthentication(options => { options.DefaultScheme = "Cookies"; options.DefaultChallengeScheme = "oidc"; }) .AddCookie() .AddOpenIdConnect("oidc", options => { options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.Authority = identityAuthority; options.ClientId = identityClientId; options.ClientSecret = identityClientSecret; options.ResponseType = OpenIdConnectResponseType.Code; options.CallbackPath = "/admin/signin-oidc"; options.SaveTokens = true; options.GetClaimsFromUserInfoEndpoint = true; options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters() { ValidateIssuer = true }; options.Scope.Add("profile"); options.Scope.Add("openid"); options.Scope.Add("offline_access"); //options.Events = new OpenIdConnectEvents //{ // OnAuthorizationCodeReceived = async context => // { // var code = context.ProtocolMessage.Code; // var identifier = context.Principal; // await Task.Yield(); // }, // OnMessageReceived = async ctxt => // { // string bodyContent = new StreamReader(ctxt.Request.Body).ReadToEnd(); // await Task.Yield(); // }, // OnTicketReceived = async ctxt => // { // await Task.Yield(); // }, // OnTokenValidated = async ctx => // { // var userID = ctx.Principal.FindFirstValue("sub"); // }, //OnRemoteFailure = ctx => { // ctx.HandleResponse(); // //ctx.Response.Redirect("~/Information"); // return Task.FromResult(0); // } //}; }); } public record CaseInsensitive(T Value) where T : struct, Enum { public static bool TryParse(string? value, IFormatProvider? provider, out CaseInsensitive? val) { if (Enum.TryParse(value, ignoreCase: true, out var enumVal)) { val = new CaseInsensitive(enumVal); return true; } val = null; return false; } } } }