using System; using System.Globalization; using System.IO; using System.Reflection; using System.Web.Optimization; using Microsoft.AspNet.Authentication; using Microsoft.AspNet.Authorization; using Microsoft.AspNet.Builder; using Microsoft.AspNet.Diagnostics; using Microsoft.AspNet.Hosting; using Microsoft.AspNet.Localization; using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.Filters; using Microsoft.AspNet.Mvc.Razor; using Microsoft.Data.Entity; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.OptionsModel; using Microsoft.Extensions.PlatformAbstractions; using Microsoft.Net.Http.Headers; using Newtonsoft.Json; namespace Yavsc { using System.Collections.Generic; using System.Linq; using System.Net; using System.Security.Claims; using Formatters; using Google.Apis.Util.Store; using Microsoft.AspNet.Http; using Microsoft.AspNet.Identity; using Microsoft.AspNet.SignalR; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; using Models; using Services; using Yavsc.Abstract.FileSystem; using Yavsc.AuthorizationHandlers; using Yavsc.Helpers; using Yavsc.Models.Messaging; using static System.Environment; public partial class Startup { public static string AvatarsDirName { private set; get; } public static string GitDirName { private set; get; } public static string Authority { get; private set; } public static string Temp { get; set; } public static SiteSettings SiteSetup { get; private set; } public static GoogleServiceAccount GServiceAccount { get; private set; } public static string HostingFullName { get; set; } public static PayPalSettings PayPalSettings { get; private set; } private static ILogger _logger; static ILiveProcessor _liveProcessor; /// /// generating reset password and confirmation tokens /// public IUserTokenProvider UserTokenProvider { get; set; } public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv) { AppDomain.CurrentDomain.UnhandledException += OnUnHandledException; var devtag = env.IsDevelopment() ? "D" : ""; var prodtag = env.IsProduction() ? "P" : ""; var stagetag = env.IsStaging() ? "S" : ""; HostingFullName = $"{appEnv.RuntimeFramework.FullName} [{env.EnvironmentName}:{prodtag}{devtag}{stagetag}]"; // Set up configuration sources. var builder = new ConfigurationBuilder() .AddJsonFile("appsettings.json") .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true); if (env.IsDevelopment()) { // For more details on using the user secret store see http://go.microsoft.com/fwlink/?LinkID=532709 builder.AddUserSecrets(); BundleTable.EnableOptimizations = false; } BundleConfig.RegisterBundles(BundleTable.Bundles); builder.AddEnvironmentVariables(); Configuration = builder.Build(); var auth = Configuration["Site:Authority"]; var cxstr = Configuration["ConnectionStrings:Default"]; ConnectionString = cxstr; AppDomain.CurrentDomain.SetData(Constants.YavscConnectionStringEnvName, ConnectionString); var googleClientFile = Configuration["Authentication:Google:GoogleWebClientJson"]; var googleServiceAccountJsonFile = Configuration["Authentication:Google:GoogleServiceAccountJson"]; if (googleClientFile != null) GoogleWebClientConfiguration = new ConfigurationBuilder().AddJsonFile(googleClientFile).Build(); if (googleServiceAccountJsonFile != null) { var safile = new FileInfo(googleServiceAccountJsonFile); GServiceAccount = JsonConvert.DeserializeObject(safile.OpenText().ReadToEnd()); } } // never hit ... private void OnUnHandledException(object sender, UnhandledExceptionEventArgs e) { _logger.LogError(sender.ToString()); _logger.LogError(JsonConvert.SerializeObject(e.ExceptionObject)); } public static string ConnectionString { get; set; } public static GoogleAuthSettings GoogleSettings { get; set; } public IConfigurationRoot Configuration { get; set; } public static IConfigurationRoot GoogleWebClientConfiguration { get; set; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { // Database connection services.AddOptions(); var siteSettings = Configuration.GetSection("Site"); services.Configure(siteSettings); var smtpSettings = Configuration.GetSection("Smtp"); services.Configure(smtpSettings); var googleSettings = Configuration.GetSection("Authentication").GetSection("Google"); services.Configure(googleSettings); var cinfoSettings = Configuration.GetSection("Authentication").GetSection("Societeinfo"); services.Configure(cinfoSettings); var oauthFacebookSettings = Configuration.GetSection("Authentication").GetSection("Facebook"); services.Configure(oauthFacebookSettings); var paypalSettings = Configuration.GetSection("Authentication").GetSection("PayPal"); services.Configure(paypalSettings); services.Add(ServiceDescriptor.Singleton(typeof(IOptions), typeof(OptionsManager))); services.Add(ServiceDescriptor.Singleton(typeof(IOptions), typeof(OptionsManager))); services.Add(ServiceDescriptor.Singleton(typeof(IOptions), typeof(OptionsManager))); services.Add(ServiceDescriptor.Singleton(typeof(IOptions), typeof(OptionsManager))); services.Add(ServiceDescriptor.Singleton(typeof(IOptions), typeof(OptionsManager))); services.Add(ServiceDescriptor.Singleton(typeof(IDiskUsageTracker), typeof(DiskUsageTracker))); services.Configure(options => { var supportedCultures = new[] { new CultureInfo("en"), new CultureInfo("fr"), new CultureInfo("pt") }; var supportedUICultures = new[] { new CultureInfo("fr"), new CultureInfo("en"), new CultureInfo("pt") }; // You must explicitly state which cultures your application supports. // These are the cultures the app supports for formatting numbers, dates, etc. options.SupportedCultures = supportedCultures; // These are the cultures the app supports for UI strings, i.e. we have localized resources for. options.SupportedUICultures = supportedUICultures; options.RequestCultureProviders = new List { new QueryStringRequestCultureProvider { Options = options }, new CookieRequestCultureProvider { Options = options, CookieName="ASPNET_CULTURE" }, new AcceptLanguageHeaderRequestCultureProvider { Options = options } }; }); // DataProtection ConfigureProtectionServices(services); // Add framework services. services.AddEntityFramework() .AddNpgsql() .AddDbContext(); ConfigureOAuthServices(services); services.AddCors( options => { options.AddPolicy("CorsPolicy", builder => { builder.WithOrigins("*"); }); } ); // Add memory cache services services.AddCaching(); // Add session related services. services.AddSession(); // Add the system clock service services.AddSingleton(); services.AddAuthorization(options => { options.AddPolicy("AdministratorOnly", policy => { policy.RequireClaim("http://schemas.microsoft.com/ws/2008/06/identity/claims/role", Constants.AdminGroupName); }); options.AddPolicy("FrontOffice", policy => policy.RequireRole(Constants.FrontOfficeGroupName)); options.AddPolicy("Bearer", new AuthorizationPolicyBuilder() .AddAuthenticationSchemes("yavsc") .RequireAuthenticatedUser().Build()); // options.AddPolicy("EmployeeId", policy => policy.RequireClaim("EmployeeId", "123", "456")); // options.AddPolicy("BuildingEntry", policy => policy.Requirements.Add(new OfficeEntryRequirement())); options.AddPolicy("Authenticated", policy => policy.RequireAuthenticatedUser()); }); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddMvc(config => { var policy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .Build(); config.Filters.Add(new AuthorizeFilter(policy)); config.Filters.Add(new ProducesAttribute("application/json")); // config.ModelBinders.Insert(0,new MyDateTimeModelBinder()); // config.ModelBinders.Insert(0,new MyDecimalModelBinder()); config.OutputFormatters.Add(new PdfFormatter()); }).AddFormatterMappings( config => config.SetMediaTypeMappingForFormat("text/pdf", new MediaTypeHeaderValue("text/pdf")) ).AddFormatterMappings( config => config.SetMediaTypeMappingForFormat("text/x-tex", new MediaTypeHeaderValue("text/x-tex")) ) .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix, options => { options.ResourcesPath = "Resources"; }).AddDataAnnotationsLocalization(); // services.AddScoped(); // Inject ticket formatting services.AddTransient(typeof(ISecureDataFormat<>), typeof(SecureDataFormat<>)); services.AddTransient, Microsoft.AspNet.Authentication.SecureDataFormat>(); services.AddTransient, TicketDataFormat>(); // Add application services. services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient((sp) => new FileDataStore("googledatastore", false)); services.AddTransient(); // TODO for SMS: services.AddTransient(); services.AddLocalization(options => { options.ResourcesPath = "Resources"; }); } static ApplicationDbContext _dbContext; public static IServiceProvider Services { get; private set; } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure( IApplicationBuilder app, IHostingEnvironment env, ApplicationDbContext dbContext, IOptions siteSettings, IOptions localizationOptions, IAuthorizationService authorizationService, IOptions payPalSettings, IOptions googleSettings, IStringLocalizer localizer, UserManager usermanager, ILiveProcessor liveProcessor, ILoggerFactory loggerFactory) { Services = app.ApplicationServices; _dbContext = dbContext; _usermanager = usermanager; _liveProcessor = liveProcessor; GoogleSettings = googleSettings.Value; ResourcesHelpers.GlobalLocalizer = localizer; SiteSetup = siteSettings.Value; Authority = siteSettings.Value.Authority; var blogsDir = siteSettings.Value.Blog; if (blogsDir == null) throw new Exception("blogsDir is not set."); var billsDir = siteSettings.Value.Bills; if (billsDir == null) throw new Exception("billsDir is not set."); AbstractFileSystemHelpers.UserFilesDirName = new DirectoryInfo(blogsDir).FullName; AbstractFileSystemHelpers.UserBillsDirName = new DirectoryInfo(billsDir).FullName; Temp = siteSettings.Value.TempDir; PayPalSettings = payPalSettings.Value; // TODO implement an installation & upgrade procedure // Create required directories foreach (string dir in new string[] { AbstractFileSystemHelpers.UserFilesDirName, AbstractFileSystemHelpers.UserBillsDirName, SiteSetup.TempDir }) { if (dir == null) throw new Exception(nameof(dir)); DirectoryInfo di = new DirectoryInfo(dir); if (!di.Exists) di.Create(); } loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddDebug(); _logger = loggerFactory.CreateLogger(); app.UseStatusCodePagesWithReExecute("/Home/Status/{0}"); if (env.IsDevelopment()) { var logenvvar = Environment.GetEnvironmentVariable("ASPNET_LOG_LEVEL"); if (logenvvar != null) switch (logenvvar) { case "info": loggerFactory.MinimumLevel = LogLevel.Information; break; case "warn": loggerFactory.MinimumLevel = LogLevel.Warning; break; case "err": loggerFactory.MinimumLevel = LogLevel.Error; break; case "debug": default: loggerFactory.MinimumLevel = LogLevel.Debug; break; } app.UseDeveloperExceptionPage(); app.UseRuntimeInfoPage(); var epo = new ErrorPageOptions { SourceCodeLineCount = 20 }; app.UseDeveloperExceptionPage(epo); app.UseDatabaseErrorPage( x => { x.EnableAll(); x.ShowExceptionDetails = true; } ); app.UseWelcomePage("/welcome"); } else { // For more details on creating database during deployment see http://go.microsoft.com/fwlink/?LinkID=615859 app.UseExceptionHandler("/Home/Error"); try { using (var serviceScope = app.ApplicationServices.GetRequiredService() .CreateScope()) { serviceScope.ServiceProvider.GetService() .Database.Migrate(); } } catch (TargetInvocationException ex) { if (ex.InnerException is InvalidOperationException) // nothing to do ? { // TODO (or not) Hit the developper } else throw ex; } } // before fixing the security protocol, let beleive our lib it's done with it. var cxmgr = PayPal.Manager.ConnectionManager.Instance; // then, fix it. ServicePointManager.SecurityProtocol = (SecurityProtocolType)0xC00; // Tls12, required by PayPal app.UseIISPlatformHandler(options => { options.AuthenticationDescriptions.Clear(); options.AutomaticAuthentication = false; }); app.UseSession(); ConfigureOAuthApp(app); ConfigureFileServerApp(app, SiteSetup, env, authorizationService); app.UseRequestLocalization(localizationOptions.Value, (RequestCulture)new RequestCulture((string)"en-US")); ConfigureWorkflow(); // Empty this odd chat user list from db /* foreach (var p in dbContext.ChatConnection) { dbContext.Entry(p).State = EntityState.Deleted; } dbContext.SaveChanges(); FIXME */ ConfigureWebSocketsApp(app); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); _logger.LogInformation("LocalApplicationData: " + Environment.GetFolderPath(SpecialFolder.LocalApplicationData, SpecialFolderOption.DoNotVerify)); app.Use(async (context, next) => { const string livePath = "live"; var liveCasting = context.Request.Path.StartsWithSegments(Constants.LivePath); if (liveCasting) { // ensure this request is for a websocket if (context.WebSockets.IsWebSocketRequest) { if (!context.User.Identity.IsAuthenticated) context.Response.StatusCode = 403; else { var liveId = long.Parse(context.Request.Path.Value.Substring(Constants.LivePath.Length + 1)); var userId = context.User.GetUserId(); var user = await _dbContext.Users.FirstAsync(u => u.Id == userId); var uname = user.UserName; var flow = _dbContext.LiveFlow.Include(f => f.Owner).SingleOrDefault(f => (f.OwnerId == userId && f.Id == liveId)); if (flow == null) { _logger.LogWarning("Aborting. Flow info was not found."); context.Response.StatusCode = 400; return; } var hubContext = GlobalHost.ConnectionManager.GetHubContext(); hubContext.Clients.All.addPublicStream(new PublicStreamInfo { id = flow.Id, sender = flow.Owner.UserName, title = flow.Title, url = flow.GetFileUrl(), mediaType = flow.MediaType }, $"{flow.Owner.UserName} is starting a stream!"); string destDir = context.User.InitPostToFileSystem(livePath); _logger.LogInformation($"Saving flow to {destDir}"); string fileName = flow.GetFileName(); _logger.LogInformation("flow : " + flow.Title + " for " + uname); FileInfo destFileInfo = new FileInfo(Path.Combine(destDir, fileName)); // this should end :-) while (destFileInfo.Exists) { flow.SequenceNumber++; fileName = flow.GetFileName(); destFileInfo = new FileInfo(Path.Combine(destDir, fileName)); } await _liveProcessor.AcceptStream(context, user, destDir, fileName); } } else { context.Response.StatusCode = 400; } } else { await next(); } }); CheckApp(env, loggerFactory); } // Entry point for the application. public static void Main(string[] args) => Microsoft.AspNet.Hosting.WebApplication.Run(args); } } //