Beispiel #1
0
        /// <summary>
        /// Creates a pre-configured <see cref="Process"/> pointing to dmm-tools.exe
        /// </summary>
        /// <param name="output">A <see cref="StringBuilder"/> used to accept stdout</param>
        /// <param name="errorOutput">A <see cref="StringBuilder"/> used to accept stderr</param>
        /// <returns>A <see cref="Task"/> resulting in a pre-configured <see cref="Process"/> pointing to dmm-tools.exe</returns>
        Process CreateDMMToolsProcess(StringBuilder output, StringBuilder errorOutput)
        {
            var P = new Process();

            P.StartInfo.RedirectStandardOutput = true;
            P.StartInfo.RedirectStandardError  = true;
            P.StartInfo.UseShellExecute        = false;
            P.StartInfo.WorkingDirectory       = ioManager.ResolvePath(".");
            P.OutputDataReceived += new DataReceivedEventHandler(
                delegate(object sender, DataReceivedEventArgs e)
            {
                output.Append(Environment.NewLine);
                output.Append(e.Data);
            }
                );
            P.ErrorDataReceived += new DataReceivedEventHandler(
                delegate(object sender, DataReceivedEventArgs e)
            {
                errorOutput.Append(Environment.NewLine);
                errorOutput.Append(e.Data);
            }
                );

            try
            {
                P.StartInfo.FileName = ioManager.ConcatPath(ioManager.GetDirectoryName(Assembly.GetExecutingAssembly().Location), DMMToolsPath);
            }
            catch
            {
                P.Dispose();
                throw;
            }

            return(P);
        }
Beispiel #2
0
        async Task <string> ValidateNonExistantSqliteDBName(string databaseName, CancellationToken cancellationToken)
        {
            var resolvedPath = ioManager.ResolvePath(databaseName);

            try
            {
                var  directoryName    = ioManager.GetDirectoryName(resolvedPath);
                bool directoryExisted = await ioManager.DirectoryExists(directoryName, cancellationToken).ConfigureAwait(false);

                await ioManager.CreateDirectory(directoryName, cancellationToken).ConfigureAwait(false);

                try
                {
                    await ioManager.WriteAllBytes(resolvedPath, Array.Empty <byte>(), cancellationToken).ConfigureAwait(false);
                }
                catch
                {
                    if (!directoryExisted)
                    {
                        await ioManager.DeleteDirectory(directoryName, cancellationToken).ConfigureAwait(false);
                    }
                    throw;
                }
            }
            catch (IOException)
            {
                return(null);
            }

            if (!Path.IsPathRooted(databaseName))
            {
                await console.WriteAsync("Note, this relative path (currently) resolves to the following:", true, cancellationToken).ConfigureAwait(false);

                await console.WriteAsync(resolvedPath, true, cancellationToken).ConfigureAwait(false);

                bool writeResolved = await PromptYesNo(
                    "Would you like to save the relative path in the configuration? If not, the full path will be saved. (y/n): ",
                    cancellationToken)
                                     .ConfigureAwait(false);

                if (writeResolved)
                {
                    databaseName = resolvedPath;
                }
            }

            await ioManager.DeleteFile(databaseName, cancellationToken).ConfigureAwait(false);

            return(databaseName);
        }
        /// <summary>
        /// Configure dependency injected services
        /// </summary>
        /// <param name="services">The <see cref="IServiceCollection"/> to configure</param>
        public void ConfigureServices(IServiceCollection services)
        {
            if (services == null)
            {
                throw new ArgumentNullException(nameof(services));
            }

            // needful
            services.AddSingleton <IApplication>(this);

            // configure configuration
            services.UseStandardConfig <UpdatesConfiguration>(configuration);
            services.UseStandardConfig <DatabaseConfiguration>(configuration);
            services.UseStandardConfig <GeneralConfiguration>(configuration);
            services.UseStandardConfig <FileLoggingConfiguration>(configuration);
            services.UseStandardConfig <ControlPanelConfiguration>(configuration);

            // enable options which give us config reloading
            services.AddOptions();

            // this is needful for the setup wizard
            services.AddLogging();

            // other stuff needed for for setup wizard and configuration
            services.AddSingleton <IConsole, IO.Console>();
            services.AddSingleton <IDatabaseConnectionFactory, DatabaseConnectionFactory>();
            services.AddSingleton <ISetupWizard, SetupWizard>();
            services.AddSingleton <IPlatformIdentifier, PlatformIdentifier>();
            services.AddSingleton <IAsyncDelayer, AsyncDelayer>();

            GeneralConfiguration      generalConfiguration;
            DatabaseConfiguration     databaseConfiguration;
            FileLoggingConfiguration  fileLoggingConfiguration;
            ControlPanelConfiguration controlPanelConfiguration;
            IPlatformIdentifier       platformIdentifier;

            // temporarily build the service provider in it's current state
            // do it here so we can run the setup wizard if necessary
            // also allows us to get some options and other services we need for continued configuration
            using (var provider = services.BuildServiceProvider())
            {
                // run the wizard if necessary
                var setupWizard         = provider.GetRequiredService <ISetupWizard>();
                var applicationLifetime = provider.GetRequiredService <Microsoft.AspNetCore.Hosting.IApplicationLifetime>();
                var setupWizardRan      = setupWizard.CheckRunWizard(applicationLifetime.ApplicationStopping).GetAwaiter().GetResult();

                // load the configuration options we need
                var generalOptions = provider.GetRequiredService <IOptions <GeneralConfiguration> >();
                generalConfiguration = generalOptions.Value;

                // unless this is set, in which case, we leave
                if (setupWizardRan && generalConfiguration.SetupWizardMode == SetupWizardMode.Only)
                {
                    throw new OperationCanceledException("Exiting due to SetupWizardMode configuration!");                     // we don't inject a logger in the constuctor to log this because it's not yet configured
                }
                var dbOptions = provider.GetRequiredService <IOptions <DatabaseConfiguration> >();
                databaseConfiguration = dbOptions.Value;

                var loggingOptions = provider.GetRequiredService <IOptions <FileLoggingConfiguration> >();
                fileLoggingConfiguration = loggingOptions.Value;

                var controlPanelOptions = provider.GetRequiredService <IOptions <ControlPanelConfiguration> >();
                controlPanelConfiguration = controlPanelOptions.Value;

                platformIdentifier = provider.GetRequiredService <IPlatformIdentifier>();
            }

            // setup file logging via serilog
            if (!fileLoggingConfiguration.Disable)
            {
                services.AddLogging(builder =>
                {
                    // common app data is C:/ProgramData on windows, else /usr/shar
                    var logPath = !String.IsNullOrEmpty(fileLoggingConfiguration.Directory) ? fileLoggingConfiguration.Directory : ioManager.ConcatPath(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), VersionPrefix, "Logs");

                    logPath = ioManager.ConcatPath(logPath, "tgs-{Date}.log");

                    LogEventLevel?ConvertLogLevel(LogLevel logLevel)
                    {
                        switch (logLevel)
                        {
                        case LogLevel.Critical:
                            return(LogEventLevel.Fatal);

                        case LogLevel.Debug:
                            return(LogEventLevel.Debug);

                        case LogLevel.Error:
                            return(LogEventLevel.Error);

                        case LogLevel.Information:
                            return(LogEventLevel.Information);

                        case LogLevel.Trace:
                            return(LogEventLevel.Verbose);

                        case LogLevel.Warning:
                            return(LogEventLevel.Warning);

                        case LogLevel.None:
                            return(null);

                        default:
                            throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid log level {0}", logLevel));
                        }
                    }

                    var logEventLevel       = ConvertLogLevel(fileLoggingConfiguration.LogLevel);
                    var microsoftEventLevel = ConvertLogLevel(fileLoggingConfiguration.MicrosoftLogLevel);

                    var formatter = new MessageTemplateTextFormatter("{Timestamp:o} {RequestId,13} [{Level:u3}] {SourceContext:l}: {Message} ({EventId:x8}){NewLine}{Exception}", null);

                    var configuration = new LoggerConfiguration()
                                        .Enrich.FromLogContext()
                                        .WriteTo.Async(w => w.RollingFile(formatter, logPath, shared: true, flushToDiskInterval: TimeSpan.FromSeconds(2)));

                    if (logEventLevel.HasValue)
                    {
                        configuration.MinimumLevel.Is(logEventLevel.Value);
                    }

                    if (microsoftEventLevel.HasValue)
                    {
                        configuration.MinimumLevel.Override("Microsoft", microsoftEventLevel.Value);
                    }

                    builder.AddSerilog(configuration.CreateLogger(), true);
                });
            }

            // configure bearer token validation
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(jwtBearerOptions =>
            {
                // this line isn't actually run until the first request is made
                // at that point tokenFactory will be populated
                jwtBearerOptions.TokenValidationParameters = tokenFactory.ValidationParameters;
                jwtBearerOptions.Events = new JwtBearerEvents
                {
                    // Application is our composition root so this monstrosity of a line is okay
                    OnTokenValidated = ctx => ctx.HttpContext.RequestServices.GetRequiredService <IClaimsInjector>().InjectClaimsIntoContext(ctx, ctx.HttpContext.RequestAborted)
                };
            });

            // f*****g prevents converting 'sub' to M$ bs
            // can't be done in the above lambda, that's too late
            JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

            // add mvc, configure the json serializer settings
            services
            .AddMvc(options =>
            {
                var dataAnnotationValidator = options.ModelValidatorProviders.Single(validator => validator.GetType().Name == "DataAnnotationsModelValidatorProvider");
                options.ModelValidatorProviders.Remove(dataAnnotationValidator);
            })
            .SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
            .AddJsonOptions(options =>
            {
                options.AllowInputFormatterExceptionMessages      = true;
                options.SerializerSettings.NullValueHandling      = NullValueHandling.Ignore;
                options.SerializerSettings.CheckAdditionalContent = true;
                options.SerializerSettings.MissingMemberHandling  = MissingMemberHandling.Error;
                options.SerializerSettings.ReferenceLoopHandling  = ReferenceLoopHandling.Ignore;
                options.SerializerSettings.Converters             = new[] { new VersionConverter() };
            });

            if (hostingEnvironment.IsDevelopment())
            {
                string GetDocumentationFilePath(string assemblyLocation) => ioManager.ConcatPath(ioManager.GetDirectoryName(assemblyLocation), String.Concat(ioManager.GetFileNameWithoutExtension(assemblyLocation), ".xml"));

                var assemblyDocumentationPath = GetDocumentationFilePath(assemblyInformationProvider.Path);
                var apiDocumentationPath      = GetDocumentationFilePath(typeof(ApiHeaders).Assembly.Location);
                services.AddSwaggerGen(genOptions => SwaggerConfiguration.Configure(genOptions, assemblyDocumentationPath, apiDocumentationPath));
            }

            // enable browser detection
            services.AddDetectionCore().AddBrowser();

            // CORS conditionally enabled later
            services.AddCors();

            void AddTypedContext <TContext>() where TContext : DatabaseContext <TContext>
            {
                services.AddDbContext <TContext>(builder =>
                {
                    if (hostingEnvironment.IsDevelopment())
                    {
                        builder.EnableSensitiveDataLogging();
                    }
                });
                services.AddScoped <IDatabaseContext>(x => x.GetRequiredService <TContext>());
            }

            // add the correct database context type
            var dbType = databaseConfiguration.DatabaseType;

            switch (dbType)
            {
            case DatabaseType.MySql:
            case DatabaseType.MariaDB:
                AddTypedContext <MySqlDatabaseContext>();
                break;

            case DatabaseType.SqlServer:
                AddTypedContext <SqlServerDatabaseContext>();
                break;

            default:
                throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid {0}: {1}!", nameof(DatabaseType), dbType));
            }

            // configure other database services
            services.AddSingleton <IDatabaseContextFactory, DatabaseContextFactory>();
            services.AddSingleton <IDatabaseSeeder, DatabaseSeeder>();

            // configure security services
            services.AddScoped <IAuthenticationContextFactory, AuthenticationContextFactory>();
            services.AddScoped <IClaimsInjector, ClaimsInjector>();
            services.AddSingleton <IIdentityCache, IdentityCache>();
            services.AddSingleton <ICryptographySuite, CryptographySuite>();
            services.AddSingleton <ITokenFactory, TokenFactory>();
            services.AddSingleton <IPasswordHasher <Models.User>, PasswordHasher <Models.User> >();

            // configure platform specific services
            if (platformIdentifier.IsWindows)
            {
                if (generalConfiguration.UseBasicWatchdogOnWindows)
                {
                    services.AddSingleton <IWatchdogFactory, WatchdogFactory>();
                }
                else
                {
                    services.AddSingleton <IWatchdogFactory, WindowsWatchdogFactory>();
                }

                services.AddSingleton <ISystemIdentityFactory, WindowsSystemIdentityFactory>();
                services.AddSingleton <ISymlinkFactory, WindowsSymlinkFactory>();
                services.AddSingleton <IByondInstaller, WindowsByondInstaller>();
                services.AddSingleton <IPostWriteHandler, WindowsPostWriteHandler>();

                services.AddSingleton <WindowsNetworkPromptReaper>();
                services.AddSingleton <INetworkPromptReaper>(x => x.GetRequiredService <WindowsNetworkPromptReaper>());
                services.AddSingleton <IHostedService>(x => x.GetRequiredService <WindowsNetworkPromptReaper>());
            }
            else
            {
                services.AddSingleton <IWatchdogFactory, WatchdogFactory>();
                services.AddSingleton <ISystemIdentityFactory, PosixSystemIdentityFactory>();
                services.AddSingleton <ISymlinkFactory, PosixSymlinkFactory>();
                services.AddSingleton <IByondInstaller, PosixByondInstaller>();
                services.AddSingleton <IPostWriteHandler, PosixPostWriteHandler>();
                services.AddSingleton <INetworkPromptReaper, PosixNetworkPromptReaper>();
            }

            // configure misc services
            services.AddSingleton <ISynchronousIOManager, SynchronousIOManager>();
            services.AddSingleton <IGitHubClientFactory, GitHubClientFactory>();
            services.AddSingleton <IProcessExecutor, ProcessExecutor>();
            services.AddSingleton <IByondTopicSender>(new ByondTopicSender
            {
                ReceiveTimeout = generalConfiguration.ByondTopicTimeout,
                SendTimeout    = generalConfiguration.ByondTopicTimeout
            });

            // configure component services
            services.AddSingleton <ICredentialsProvider, CredentialsProvider>();
            services.AddSingleton <IProviderFactory, ProviderFactory>();
            services.AddSingleton <IChatFactory, ChatFactory>();
            services.AddSingleton <IInstanceFactory, InstanceFactory>();

            // configure root services
            services.AddSingleton <InstanceManager>();
            services.AddSingleton <IInstanceManager>(x => x.GetRequiredService <InstanceManager>());
            services.AddSingleton <IHostedService>(x => x.GetRequiredService <InstanceManager>());

            services.AddSingleton <IJobManager, JobManager>();
        }