tgstation-server 6.12.0
The /tg/station 13 server suite
Loading...
Searching...
No Matches
Application.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Frozen;
3using System.Collections.Generic;
4using System.Globalization;
5using System.Threading.Tasks;
6
7using Cyberboss.AspNetCore.AsyncInitializer;
8
9using Elastic.CommonSchema.Serilog;
10
11using HotChocolate.AspNetCore;
12using HotChocolate.Subscriptions;
13using HotChocolate.Types;
14
15using Microsoft.AspNetCore.Authentication;
16using Microsoft.AspNetCore.Authentication.JwtBearer;
17using Microsoft.AspNetCore.Builder;
18using Microsoft.AspNetCore.Cors.Infrastructure;
19using Microsoft.AspNetCore.Hosting;
20using Microsoft.AspNetCore.Http;
21using Microsoft.AspNetCore.Http.Connections;
22using Microsoft.AspNetCore.Identity;
23using Microsoft.AspNetCore.Mvc.Infrastructure;
24using Microsoft.AspNetCore.SignalR;
25using Microsoft.Extensions.Configuration;
26using Microsoft.Extensions.DependencyInjection;
27using Microsoft.Extensions.Hosting;
28using Microsoft.Extensions.Logging;
29using Microsoft.Extensions.Options;
30
31using Newtonsoft.Json;
32
33using Serilog;
34using Serilog.Events;
35using Serilog.Formatting.Display;
36using Serilog.Sinks.Elasticsearch;
37
75
77{
81#pragma warning disable CA1506
82 public sealed class Application : SetupApplication
83 {
87 readonly IWebHostEnvironment hostingEnvironment;
88
93
99 {
100 var assemblyInformationProvider = new AssemblyInformationProvider();
101 var ioManager = new DefaultIOManager();
102 return new ServerFactory(
103 assemblyInformationProvider,
104 ioManager);
105 }
106
113 static void AddWatchdog<TSystemWatchdogFactory>(IServiceCollection services, IPostSetupServices postSetupServices)
114 where TSystemWatchdogFactory : class, IWatchdogFactory
115 {
116 if (postSetupServices.GeneralConfiguration.UseBasicWatchdog)
117 services.AddSingleton<IWatchdogFactory, WatchdogFactory>();
118 else
119 services.AddSingleton<IWatchdogFactory, TSystemWatchdogFactory>();
120 }
121
128 IConfiguration configuration,
129 IWebHostEnvironment hostingEnvironment)
130 : base(configuration)
131 {
132 this.hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment));
133 }
134
142 public void ConfigureServices(
143 IServiceCollection services,
144 IAssemblyInformationProvider assemblyInformationProvider,
145 IIOManager ioManager,
146 IPostSetupServices postSetupServices)
147 {
148 ConfigureServices(services, assemblyInformationProvider, ioManager);
149
150 ArgumentNullException.ThrowIfNull(postSetupServices);
151
152 // configure configuration
153 services.UseStandardConfig<UpdatesConfiguration>(Configuration);
154 services.UseStandardConfig<ControlPanelConfiguration>(Configuration);
155 services.UseStandardConfig<SwarmConfiguration>(Configuration);
156 services.UseStandardConfig<SessionConfiguration>(Configuration);
157 services.UseStandardConfig<TelemetryConfiguration>(Configuration);
158
159 // enable options which give us config reloading
160 services.AddOptions();
161
162 // Set the timeout for IHostedService.StopAsync
163 services.Configure<HostOptions>(
164 opts => opts.ShutdownTimeout = TimeSpan.FromMinutes(postSetupServices.GeneralConfiguration.RestartTimeoutMinutes));
165
166 static LogEventLevel? ConvertSeriLogLevel(LogLevel logLevel) =>
167 logLevel switch
168 {
169 LogLevel.Critical => LogEventLevel.Fatal,
170 LogLevel.Debug => LogEventLevel.Debug,
171 LogLevel.Error => LogEventLevel.Error,
172 LogLevel.Information => LogEventLevel.Information,
173 LogLevel.Trace => LogEventLevel.Verbose,
174 LogLevel.Warning => LogEventLevel.Warning,
175 LogLevel.None => null,
176 _ => throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid log level {0}", logLevel)),
177 };
178
179 var microsoftEventLevel = ConvertSeriLogLevel(postSetupServices.FileLoggingConfiguration.MicrosoftLogLevel);
180 var elasticsearchConfiguration = postSetupServices.ElasticsearchConfiguration;
181 services.SetupLogging(
182 config =>
183 {
184 if (microsoftEventLevel.HasValue)
185 {
186 config.MinimumLevel.Override("Microsoft", microsoftEventLevel.Value);
187 config.MinimumLevel.Override("System.Net.Http.HttpClient", microsoftEventLevel.Value);
188 }
189 },
190 sinkConfig =>
191 {
192 if (postSetupServices.FileLoggingConfiguration.Disable)
193 return;
194
195 var logPath = postSetupServices.FileLoggingConfiguration.GetFullLogDirectory(
196 ioManager,
197 assemblyInformationProvider,
198 postSetupServices.PlatformIdentifier);
199
200 var logEventLevel = ConvertSeriLogLevel(postSetupServices.FileLoggingConfiguration.LogLevel);
201
202 var formatter = new MessageTemplateTextFormatter(
203 "{Timestamp:o} "
205 + "): [{Level:u3}] {SourceContext:l}: {Message} ({EventId:x8}){NewLine}{Exception}",
206 null);
207
208 logPath = ioManager.ConcatPath(logPath, "tgs-.log");
209 var rollingFileConfig = sinkConfig.File(
210 formatter,
211 logPath,
212 logEventLevel ?? LogEventLevel.Verbose,
213 50 * 1024 * 1024, // 50MB max size
214 flushToDiskInterval: TimeSpan.FromSeconds(2),
215 rollingInterval: RollingInterval.Day,
216 rollOnFileSizeLimit: true);
217 },
218 elasticsearchConfiguration.Enable
219 ? new ElasticsearchSinkOptions(elasticsearchConfiguration.Host ?? throw new InvalidOperationException($"Missing {ElasticsearchConfiguration.Section}:{nameof(elasticsearchConfiguration.Host)}!"))
220 {
221 // Yes I know this means they cannot use a self signed cert unless they also have authentication, but lets be real here
222 // No one is going to be doing one of those but not the other
223 ModifyConnectionSettings = connectionConfigration => (!String.IsNullOrWhiteSpace(elasticsearchConfiguration.Username) && !String.IsNullOrWhiteSpace(elasticsearchConfiguration.Password))
224 ? connectionConfigration
225 .BasicAuthentication(
226 elasticsearchConfiguration.Username,
227 elasticsearchConfiguration.Password)
228 .ServerCertificateValidationCallback((o, certificate, chain, errors) => true)
229 : null,
230 CustomFormatter = new EcsTextFormatter(),
231 AutoRegisterTemplate = true,
232 AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv7,
233 IndexFormat = "tgs-logs",
234 }
235 : null,
236 postSetupServices.InternalConfiguration,
237 postSetupServices.FileLoggingConfiguration);
238
239 // configure authentication pipeline
241
242 // add mvc, configure the json serializer settings
243 var jsonVersionConverterList = new List<JsonConverter>
244 {
245 new VersionConverter(),
246 };
247
248 void ConfigureNewtonsoftJsonSerializerSettingsForApi(JsonSerializerSettings settings)
249 {
250 settings.NullValueHandling = NullValueHandling.Ignore;
251 settings.CheckAdditionalContent = true;
252 settings.MissingMemberHandling = MissingMemberHandling.Error;
253 settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
254 settings.Converters = jsonVersionConverterList;
255 }
256
257 services
258 .AddMvc(options =>
259 {
260 options.ReturnHttpNotAcceptable = true;
261 options.RespectBrowserAcceptHeader = true;
262 })
263 .AddNewtonsoftJson(options =>
264 {
265 options.AllowInputFormatterExceptionMessages = true;
266 ConfigureNewtonsoftJsonSerializerSettingsForApi(options.SerializerSettings);
267 });
268
269 services.AddSignalR(
270 options =>
271 {
272 options.AddFilter<AuthorizationContextHubFilter>();
273 })
274 .AddNewtonsoftJsonProtocol(options =>
275 {
276 ConfigureNewtonsoftJsonSerializerSettingsForApi(options.PayloadSerializerSettings);
277 });
278
279 services.AddHub<JobsHub, IJobsHub>();
280
281 if (postSetupServices.GeneralConfiguration.HostApiDocumentation)
282 {
283 string GetDocumentationFilePath(string assemblyLocation) => ioManager.ConcatPath(ioManager.GetDirectoryName(assemblyLocation), String.Concat(ioManager.GetFileNameWithoutExtension(assemblyLocation), ".xml"));
284 var assemblyDocumentationPath = GetDocumentationFilePath(GetType().Assembly.Location);
285 var apiDocumentationPath = GetDocumentationFilePath(typeof(ApiHeaders).Assembly.Location);
286 services.AddSwaggerGen(genOptions => SwaggerConfiguration.Configure(genOptions, assemblyDocumentationPath, apiDocumentationPath));
287 services.AddSwaggerGenNewtonsoftSupport();
288 }
289
290 // CORS conditionally enabled later
291 services.AddCors();
292
293 // Enable managed HTTP clients
294 services.AddHttpClient();
296
297 // configure graphql
298 services
299 .AddScoped<GraphQL.Subscriptions.ITopicEventReceiver, ShutdownAwareTopicEventReceiver>()
300 .AddGraphQLServer()
301 .AddAuthorization()
302 .ModifyOptions(options =>
303 {
304 options.EnsureAllNodesCanBeResolved = true;
305 options.EnableFlagEnums = true;
306 })
307#if DEBUG
308 .ModifyCostOptions(options =>
309 {
310 options.EnforceCostLimits = false;
311 })
312#endif
313 .AddMutationConventions()
314 .AddInMemorySubscriptions(
315 new SubscriptionOptions
316 {
317 TopicBufferCapacity = 1024, // mainly so high for tests, not possible to DoS the server without authentication and some other access to generate messages
318 })
319 .AddGlobalObjectIdentification()
320 .AddQueryFieldToMutationPayloads()
321 .ModifyOptions(options =>
322 {
323 options.EnableDefer = true;
324 })
325 .ModifyPagingOptions(pagingOptions =>
326 {
327 pagingOptions.IncludeTotalCount = true;
328 pagingOptions.RequirePagingBoundaries = false;
329 pagingOptions.DefaultPageSize = ApiController.DefaultPageSize;
330 pagingOptions.MaxPageSize = ApiController.MaximumPageSize;
331 })
332 .AddFiltering()
333 .AddSorting()
334 .AddHostTypes()
335 .AddErrorFilter<ErrorMessageFilter>()
336 .AddType<StandaloneNode>()
337 .AddType<LocalGateway>()
338 .AddType<RemoteGateway>()
339 .AddType<GraphQL.Types.UserName>()
340 .AddType<UnsignedIntType>()
341 .BindRuntimeType<Version, SemverType>()
342 .TryAddTypeInterceptor<RightsTypeInterceptor>()
343 .AddQueryType<Query>()
344 .AddMutationType<Mutation>()
345 .AddSubscriptionType<Subscription>();
346
347 void AddTypedContext<TContext>()
348 where TContext : DatabaseContext
349 {
350 var configureAction = DatabaseContext.GetConfigureAction<TContext>();
351
352 services.AddDbContextPool<TContext>((serviceProvider, builder) =>
353 {
354 if (hostingEnvironment.IsDevelopment())
355 builder.EnableSensitiveDataLogging();
356
357 var databaseConfigOptions = serviceProvider.GetRequiredService<IOptions<DatabaseConfiguration>>();
358 var databaseConfig = databaseConfigOptions.Value ?? throw new InvalidOperationException("DatabaseConfiguration missing!");
359 configureAction(builder, databaseConfig);
360 });
361 services.AddScoped<IDatabaseContext>(x => x.GetRequiredService<TContext>());
362 }
363
364 // add the correct database context type
365 var dbType = postSetupServices.DatabaseConfiguration.DatabaseType;
366 switch (dbType)
367 {
368 case DatabaseType.MySql:
369 case DatabaseType.MariaDB:
370 AddTypedContext<MySqlDatabaseContext>();
371 break;
372 case DatabaseType.SqlServer:
373 AddTypedContext<SqlServerDatabaseContext>();
374 break;
375 case DatabaseType.Sqlite:
376 AddTypedContext<SqliteDatabaseContext>();
377 break;
378 case DatabaseType.PostgresSql:
379 AddTypedContext<PostgresSqlDatabaseContext>();
380 break;
381 default:
382 throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid {0}: {1}!", nameof(DatabaseType), dbType));
383 }
384
385 // configure other database services
386 services.AddSingleton<IDatabaseContextFactory, DatabaseContextFactory>();
387 services.AddSingleton<IDatabaseSeeder, DatabaseSeeder>();
388
389 // configure other security services
390 services.AddSingleton<IOAuthProviders, OAuthProviders>();
391 services.AddSingleton<IIdentityCache, IdentityCache>();
392 services.AddSingleton<ICryptographySuite, CryptographySuite>();
393 services.AddSingleton<ITokenFactory, TokenFactory>();
395 services.AddSingleton<IPasswordHasher<Models.User>, PasswordHasher<Models.User>>();
396
397 // configure platform specific services
398 if (postSetupServices.PlatformIdentifier.IsWindows)
399 {
400 AddWatchdog<WindowsWatchdogFactory>(services, postSetupServices);
403 services.AddSingleton<ByondInstallerBase, WindowsByondInstaller>();
404 services.AddSingleton<OpenDreamInstaller, WindowsOpenDreamInstaller>();
405 services.AddSingleton<IPostWriteHandler, WindowsPostWriteHandler>();
406 services.AddSingleton<IProcessFeatures, WindowsProcessFeatures>();
407
408 services.AddSingleton<WindowsNetworkPromptReaper>();
409 services.AddSingleton<INetworkPromptReaper>(x => x.GetRequiredService<WindowsNetworkPromptReaper>());
410 services.AddSingleton<IHostedService>(x => x.GetRequiredService<WindowsNetworkPromptReaper>());
411 }
412 else
413 {
414 AddWatchdog<PosixWatchdogFactory>(services, postSetupServices);
415 services.AddSingleton<ISystemIdentityFactory, PosixSystemIdentityFactory>();
416 services.AddSingleton<IFilesystemLinkFactory, PosixFilesystemLinkFactory>();
417 services.AddSingleton<ByondInstallerBase, PosixByondInstaller>();
418 services.AddSingleton<OpenDreamInstaller>();
419 services.AddSingleton<IPostWriteHandler, PosixPostWriteHandler>();
420
421 services.AddSingleton<IProcessFeatures, PosixProcessFeatures>();
422 services.AddHostedService<PosixProcessFeatures>();
423
424 // PosixProcessFeatures also needs a IProcessExecutor for gcore
425 services.AddSingleton(x => new Lazy<IProcessExecutor>(() => x.GetRequiredService<IProcessExecutor>(), true));
426 services.AddSingleton<INetworkPromptReaper, PosixNetworkPromptReaper>();
427
428 services.AddHostedService<PosixSignalHandler>();
429 }
430
431 // only global repo manager should be for the OD repo
432 // god help me if we need more
433 var openDreamRepositoryDirectory = ioManager.ConcatPath(
434 ioManager.GetPathInLocalDirectory(assemblyInformationProvider),
435 "OpenDreamRepository");
436 services.AddSingleton(
437 services => services
438 .GetRequiredService<IRepositoryManagerFactory>()
439 .CreateRepositoryManager(
441 services.GetRequiredService<IIOManager>(),
442 openDreamRepositoryDirectory),
443 new NoopEventConsumer()));
444
445 services.AddSingleton(
446 serviceProvider => new Dictionary<EngineType, IEngineInstaller>
447 {
448 { EngineType.Byond, serviceProvider.GetRequiredService<ByondInstallerBase>() },
449 { EngineType.OpenDream, serviceProvider.GetRequiredService<OpenDreamInstaller>() },
450 }
451 .ToFrozenDictionary());
452 services.AddSingleton<IEngineInstaller, DelegatingEngineInstaller>();
453
454 if (postSetupServices.InternalConfiguration.UsingSystemD)
455 services.AddHostedService<SystemDManager>();
456
457 // configure file transfer services
458 services.AddSingleton<FileTransferService>();
459 services.AddSingleton<IFileTransferStreamHandler>(x => x.GetRequiredService<FileTransferService>());
460 services.AddSingleton<IFileTransferTicketProvider>(x => x.GetRequiredService<FileTransferService>());
462
463 // configure swarm service
464 services.AddSingleton<SwarmService>();
465 services.AddSingleton<ISwarmService>(x => x.GetRequiredService<SwarmService>());
466 services.AddSingleton<ISwarmOperations>(x => x.GetRequiredService<SwarmService>());
467 services.AddSingleton<ISwarmServiceController>(x => x.GetRequiredService<SwarmService>());
468
469 // configure component services
470 services.AddSingleton<IPortAllocator, PortAllocator>();
471 services.AddSingleton<IInstanceFactory, InstanceFactory>();
472 services.AddSingleton<IGitRemoteFeaturesFactory, GitRemoteFeaturesFactory>();
473 services.AddSingleton<ILibGit2RepositoryFactory, LibGit2RepositoryFactory>();
474 services.AddSingleton<ILibGit2Commands, LibGit2Commands>();
475 services.AddSingleton<IRepositoryManagerFactory, RepostoryManagerFactory>();
477 services.AddChatProviderFactory();
478 services.AddSingleton<IChatManagerFactory, ChatManagerFactory>();
479 services.AddSingleton<IServerUpdater, ServerUpdater>();
480 services.AddSingleton<IServerUpdateInitiator, ServerUpdateInitiator>();
481 services.AddSingleton<IDotnetDumpService, DotnetDumpService>();
482
483 // configure authorities
484 services.AddScoped(typeof(IRestAuthorityInvoker<>), typeof(RestAuthorityInvoker<>));
485 services.AddScoped(typeof(IGraphQLAuthorityInvoker<>), typeof(GraphQLAuthorityInvoker<>));
486 services.AddScoped<ILoginAuthority, LoginAuthority>();
487 services.AddScoped<IUserAuthority, UserAuthority>();
488 services.AddScoped<IUserGroupAuthority, UserGroupAuthority>();
489 services.AddScoped<IPermissionSetAuthority, PermissionSetAuthority>();
491
492 // configure misc services
493 services.AddSingleton<IProcessExecutor, ProcessExecutor>();
494 services.AddSingleton<ISynchronousIOManager, SynchronousIOManager>();
495 services.AddSingleton<IServerPortProvider, ServerPortProivder>();
496 services.AddSingleton<ITopicClientFactory, TopicClientFactory>();
497 services.AddHostedService<CommandPipeManager>();
498 services.AddHostedService<VersionReportingService>();
499
500 services.AddFileDownloader();
501 services.AddGitHub();
502
503 // configure root services
504 services.AddSingleton<JobService>();
505 services.AddSingleton<IJobService>(provider => provider.GetRequiredService<JobService>());
506 services.AddSingleton<IJobsHubUpdater>(provider => provider.GetRequiredService<JobService>());
507 services.AddSingleton<IJobManager>(x => x.GetRequiredService<IJobService>());
508 services.AddSingleton<JobsHubGroupMapper>();
509 services.AddSingleton<IPermissionsUpdateNotifyee>(provider => provider.GetRequiredService<JobsHubGroupMapper>());
510 services.AddSingleton<IHostedService>(x => x.GetRequiredService<JobsHubGroupMapper>()); // bit of a hack, but we need this to load immediated
511
512 services.AddSingleton<InstanceManager>();
513 services.AddSingleton<IBridgeDispatcher>(x => x.GetRequiredService<InstanceManager>());
514 services.AddSingleton<IInstanceManager>(x => x.GetRequiredService<InstanceManager>());
515 }
516
530 public void Configure(
531 IApplicationBuilder applicationBuilder,
532 IServerControl serverControl,
534 IServerPortProvider serverPortProvider,
535 IAssemblyInformationProvider assemblyInformationProvider,
536 IOptions<ControlPanelConfiguration> controlPanelConfigurationOptions,
537 IOptions<GeneralConfiguration> generalConfigurationOptions,
538 IOptions<SwarmConfiguration> swarmConfigurationOptions,
539 IOptions<InternalConfiguration> internalConfigurationOptions,
540 ILogger<Application> logger)
541 {
542 ArgumentNullException.ThrowIfNull(applicationBuilder);
543 ArgumentNullException.ThrowIfNull(serverControl);
544
545 this.tokenFactory = tokenFactory ?? throw new ArgumentNullException(nameof(tokenFactory));
546
547 ArgumentNullException.ThrowIfNull(serverPortProvider);
548 ArgumentNullException.ThrowIfNull(assemblyInformationProvider);
549
550 var controlPanelConfiguration = controlPanelConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(controlPanelConfigurationOptions));
551 var generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
552 var swarmConfiguration = swarmConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(swarmConfigurationOptions));
553 var internalConfiguration = internalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(internalConfigurationOptions));
554
555 ArgumentNullException.ThrowIfNull(logger);
556
557 logger.LogDebug("Content Root: {contentRoot}", hostingEnvironment.ContentRootPath);
558 logger.LogTrace("Web Root: {webRoot}", hostingEnvironment.WebRootPath);
559
560 // setup the HTTP request pipeline
561 // Add additional logging context to the request
562 applicationBuilder.UseAdditionalRequestLoggingContext(swarmConfiguration);
563
564 // Wrap exceptions in a 500 (ErrorMessage) response
565 applicationBuilder.UseServerErrorHandling();
566
567 // Add the X-Powered-By response header
568 applicationBuilder.UseServerBranding(assemblyInformationProvider);
569
570 // Add the X-Accel-Buffering response header
571 applicationBuilder.UseDisabledNginxProxyBuffering();
572
573 // suppress OperationCancelledExceptions, they are just aborted HTTP requests
574 applicationBuilder.UseCancelledRequestSuppression();
575
576 // 503 requests made while the application is starting
577 applicationBuilder.UseAsyncInitialization<IInstanceManager>(
578 (instanceManager, cancellationToken) => instanceManager.Ready.WaitAsync(cancellationToken));
579
580 if (generalConfiguration.HostApiDocumentation)
581 {
582 var siteDocPath = Routes.ApiRoot + $"doc/{SwaggerConfiguration.DocumentName}.json";
583 if (!String.IsNullOrWhiteSpace(controlPanelConfiguration.PublicPath))
584 siteDocPath = controlPanelConfiguration.PublicPath.TrimEnd('/') + siteDocPath;
585
586 applicationBuilder.UseSwagger(options =>
587 {
588 options.RouteTemplate = Routes.ApiRoot + "doc/{documentName}.{json|yaml}";
589 });
590 applicationBuilder.UseSwaggerUI(options =>
591 {
593 options.SwaggerEndpoint(siteDocPath, "TGS API");
594 });
595 logger.LogTrace("Swagger API generation enabled");
596 }
597
598 // spa loading if necessary
599 if (controlPanelConfiguration.Enable)
600 {
601 logger.LogInformation("Web control panel enabled.");
602 applicationBuilder.UseFileServer(new FileServerOptions
603 {
605 EnableDefaultFiles = true,
606 EnableDirectoryBrowsing = false,
607 RedirectToAppendTrailingSlash = false,
608 });
609 }
610 else
611#if NO_WEBPANEL
612 logger.LogDebug("Web control panel was not included in TGS build!");
613#else
614 logger.LogTrace("Web control panel disabled!");
615#endif
616
617 // Enable endpoint routing
618 applicationBuilder.UseRouting();
619
620 // Set up CORS based on configuration if necessary
621 Action<CorsPolicyBuilder>? corsBuilder = null;
622 if (controlPanelConfiguration.AllowAnyOrigin)
623 {
624 logger.LogTrace("Access-Control-Allow-Origin: *");
625 corsBuilder = builder => builder.SetIsOriginAllowed(_ => true);
626 }
627 else if (controlPanelConfiguration.AllowedOrigins?.Count > 0)
628 {
629 logger.LogTrace("Access-Control-Allow-Origin: {allowedOrigins}", String.Join(',', controlPanelConfiguration.AllowedOrigins));
630 corsBuilder = builder => builder.WithOrigins([.. controlPanelConfiguration.AllowedOrigins]);
631 }
632
633 var originalBuilder = corsBuilder;
634 corsBuilder = builder =>
635 {
636 builder
637 .AllowAnyHeader()
638 .AllowAnyMethod()
639 .AllowCredentials()
640 .SetPreflightMaxAge(TimeSpan.FromDays(1));
641 originalBuilder?.Invoke(builder);
642 };
643 applicationBuilder.UseCors(corsBuilder);
644
645 // validate the API version
646 applicationBuilder.UseApiCompatibility();
647
648 // authenticate JWT tokens using our security pipeline if present, returns 401 if bad
649 applicationBuilder.UseAuthentication();
650
651 // enable authorization on endpoints
652 applicationBuilder.UseAuthorization();
653
654 // suppress and log database exceptions
655 applicationBuilder.UseDbConflictHandling();
656
657 // setup endpoints
658 applicationBuilder.UseEndpoints(endpoints =>
659 {
660 // access to the signalR jobs hub
661 endpoints.MapHub<JobsHub>(
663 options =>
664 {
665 options.Transports = HttpTransportType.ServerSentEvents;
666 options.CloseOnAuthenticationExpiration = true;
667 })
668 .RequireAuthorization()
669 .RequireCors(corsBuilder);
670
671 // majority of handling is done in the controllers
672 endpoints.MapControllers();
673
674 if (internalConfiguration.EnableGraphQL)
675 {
676 logger.LogWarning("Enabling GraphQL. This API is experimental and breaking changes may occur at any time!");
677 var gqlOptions = new GraphQLServerOptions
678 {
679 EnableBatching = true,
680 };
681
682 gqlOptions.Tool.Enable = generalConfiguration.HostApiDocumentation;
683
684 endpoints
685 .MapGraphQL(Routes.GraphQL)
686 .WithOptions(gqlOptions);
687 }
688 });
689
690 // 404 anything that gets this far
691 // End of request pipeline setup
692 logger.LogTrace("Configuration version: {configVersion}", GeneralConfiguration.CurrentConfigVersion);
693 logger.LogTrace("DMAPI Interop version: {interopVersion}", DMApiConstants.InteropVersion);
694 if (controlPanelConfiguration.Enable)
695 logger.LogTrace("Webpanel version: {webCPVersion}", MasterVersionsAttribute.Instance.RawWebpanelVersion);
696
697 logger.LogDebug("Starting hosting on port {httpApiPort}...", serverPortProvider.HttpApiPort);
698 }
699
701 protected override void ConfigureHostedService(IServiceCollection services)
702 => services.AddSingleton<IHostedService>(x => x.GetRequiredService<InstanceManager>());
703
708 void ConfigureAuthenticationPipeline(IServiceCollection services)
709 {
710 services.AddHttpContextAccessor();
711 services.AddScoped<IApiHeadersProvider, ApiHeadersProvider>();
712 services.AddScoped<AuthenticationContextFactory>();
713 services.AddScoped<ITokenValidator>(provider => provider.GetRequiredService<AuthenticationContextFactory>());
714
715 // what if you
716 // wanted to just do this:
717 // return provider.GetRequiredService<AuthenticationContextFactory>().CurrentAuthenticationContext
718 // But M$ said
719 // https://stackoverflow.com/questions/56792917/scoped-services-in-asp-net-core-with-signalr-hubs
720 services.AddScoped(provider => (provider
721 .GetRequiredService<IHttpContextAccessor>()
722 .HttpContext ?? throw new InvalidOperationException($"Unable to resolve {nameof(IAuthenticationContext)} due to no HttpContext being available!"))
723 .RequestServices
724 .GetRequiredService<AuthenticationContextFactory>()
725 .CurrentAuthenticationContext);
727
728 services
729 .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
730 .AddJwtBearer(jwtBearerOptions =>
731 {
732 // this line isn't actually run until the first request is made
733 // at that point tokenFactory will be populated
734 jwtBearerOptions.TokenValidationParameters = tokenFactory?.ValidationParameters ?? throw new InvalidOperationException("tokenFactory not initialized!");
735 jwtBearerOptions.MapInboundClaims = false;
736 jwtBearerOptions.Events = new JwtBearerEvents
737 {
738 OnMessageReceived = context =>
739 {
740 if (String.IsNullOrWhiteSpace(context.Token))
741 {
742 var accessToken = context.Request.Query["access_token"];
743 var path = context.HttpContext.Request.Path;
744
745 if (!String.IsNullOrWhiteSpace(accessToken) &&
746 path.StartsWithSegments(Routes.HubsRoot, StringComparison.OrdinalIgnoreCase))
747 {
748 context.Token = accessToken;
749 }
750 }
751
752 return Task.CompletedTask;
753 },
754 OnTokenValidated = context => context
755 .HttpContext
756 .RequestServices
757 .GetRequiredService<ITokenValidator>()
758 .ValidateToken(
759 context,
760 context
761 .HttpContext
762 .RequestAborted),
763 };
764 });
765 }
766 }
767}
Represents the header that must be present for every server request.
Definition ApiHeaders.cs:25
Routes to a server actions.
Definition Routes.cs:9
const string GraphQL
The GraphQL route.
Definition Routes.cs:18
const string HubsRoot
The root route of all hubs.
Definition Routes.cs:23
const string JobsHub
The root route of all hubs.
Definition Routes.cs:118
Base implementation of IEngineInstaller for EngineType.Byond.
Implementation of IEngineInstaller that forwards calls to different IEngineInstaller based on their a...
Implementation of IEngineInstaller for EngineType.OpenDream.
Implementation of OpenDreamInstaller for Windows systems.
Constants used for communication with the DMAPI.
static readonly Version InteropVersion
The DMAPI InteropVersion being used.
DatabaseType DatabaseType
The Configuration.DatabaseType to create.
LogLevel MicrosoftLogLevel
The minimum Microsoft.Extensions.Logging.LogLevel to display in logs for Microsoft library sources.
string GetFullLogDirectory(IIOManager ioManager, IAssemblyInformationProvider assemblyInformationProvider, IPlatformIdentifier platformIdentifier)
Gets the evaluated log Directory.
LogLevel LogLevel
The minimum Microsoft.Extensions.Logging.LogLevel to display in logs.
static readonly Version CurrentConfigVersion
The current ConfigVersion.
bool HostApiDocumentation
If the swagger documentation and UI should be made avaiable.
uint RestartTimeoutMinutes
The timeout minutes for restarting the server.
bool UsingSystemD
If the server is running under SystemD.
Configuration options for the game sessions.
Configuration for the server swarm system.
Configuration for the automatic update system.
Base Controller for API functions.
const ushort MaximumPageSize
Maximum size of Paginated<TModel> results.
const ushort DefaultPageSize
Default size of Paginated<TModel> results.
const string ControlPanelRoute
Route to the ControlPanelController.
Sets up dependency injection.
override void ConfigureHostedService(IServiceCollection services)
Configures the IHostedService.
static void AddWatchdog< TSystemWatchdogFactory >(IServiceCollection services, IPostSetupServices postSetupServices)
Adds the IWatchdogFactory implementation.
void ConfigureServices(IServiceCollection services, IAssemblyInformationProvider assemblyInformationProvider, IIOManager ioManager, IPostSetupServices postSetupServices)
Configure the Application's services .
static IServerFactory CreateDefaultServerFactory()
Create the default IServerFactory.
void ConfigureAuthenticationPipeline(IServiceCollection services)
Configure the services for the authentication pipeline.
ITokenFactory? tokenFactory
The ITokenFactory for the Application.
Application(IConfiguration configuration, IWebHostEnvironment hostingEnvironment)
Initializes a new instance of the Application class.
void Configure(IApplicationBuilder applicationBuilder, IServerControl serverControl, ITokenFactory tokenFactory, IServerPortProvider serverPortProvider, IAssemblyInformationProvider assemblyInformationProvider, IOptions< ControlPanelConfiguration > controlPanelConfigurationOptions, IOptions< GeneralConfiguration > generalConfigurationOptions, IOptions< SwarmConfiguration > swarmConfigurationOptions, IOptions< InternalConfiguration > internalConfigurationOptions, ILogger< Application > logger)
Configure the Application.
readonly IWebHostEnvironment hostingEnvironment
The IWebHostEnvironment for the Application.
Reads from the command pipe opened by the host watchdog.
Handles TGS version reporting, if enabled.
Backend abstract implementation of IDatabaseContext.
IErrorFilter for transforming ErrorMessageResponse-like Exception.
GraphQL query global::System.Type.
Definition Query.cs:12
A ScalarType<TRuntimeType, TLiteral> for semantic Versions.
Definition SemverType.cs:13
Root type for GraphQL subscriptions.
IGateway for the SwarmNode this query is executing on.
IIOManager that resolves paths to Environment.CurrentDirectory.
IPostWriteHandler for POSIX systems.
An IIOManager that resolve relative paths from another IIOManager to a subdirectory of that.
IPostWriteHandler for Windows systems.
Handles mapping groups for the JobsHub.
A SignalR Hub for pushing job updates.
Definition JobsHub.cs:16
Attribute for bringing in the master versions list from MSBuild that aren't embedded into assemblies ...
string RawWebpanelVersion
The Version string of the control panel version built.
static MasterVersionsAttribute Instance
Return the Assembly's instance of the MasterVersionsAttribute.
A IClaimsTransformation that maps Claims using an IAuthenticationContext.
An IHubFilter that denies method calls and connections if the IAuthenticationContext is not valid for...
ISystemIdentityFactory for windows systems. Uses long running tasks due to potential networked domain...
Implementation of IServerFactory.
DI root for configuring a SetupWizard.
IConfiguration Configuration
The IConfiguration for the SetupApplication.
Helps keep servers connected to the same database in sync by coordinating updates.
Implements the SystemD notify service protocol.
Implementation of the file transfer service.
Helpers for manipulating the Serilog.Context.LogContext.
static string Template
Common template used for adding our custom log context to serilog.
Implements various filters for Swashbuckle.
const string DocumentationSiteRouteExtension
The path to the hosted documentation site.
static void Configure(SwaggerGenOptions swaggerGenOptions, string assemblyDocumentationPath, string apiDocumentationPath)
Configure the swagger settings.
JsonConverter and IYamlTypeConverter for serializing global::System.Versions in semver format.
SignalR client methods for receiving JobResponses.
Definition IJobsHub.cs:12
IAuthority for administrative server operations.
IAuthority for authenticating with the server.
Invokes TAuthority methods and generates IActionResult responses.
For downloading and installing game engines for a given system.
Task Ready
Task that completes when the IInstanceManager finishes initializing.
For low level interactions with a LibGit2Sharp.IRepository.
Represents a service that may take an updated Host assembly and run it, stopping the current assembly...
Provides access to the server's HttpApiPort.
ushort HttpApiPort
The port the server listens on.
Factory for scoping usage of IDatabaseContexts. Meant for use by Components.
For initially setting up a database.
Interface for using filesystems.
Definition IIOManager.cs:13
string ConcatPath(params string[] paths)
Combines an array of strings into a path.
string GetDirectoryName(string path)
Gets the directory portion of a given path .
string GetFileNameWithoutExtension(string path)
Gets the file name portion of a path with.
Handles changing file modes/permissions after writing.
For accessing the disk in a synchronous manner.
Manages the runtime of Jobs.
The service that manages everything to do with jobs.
Definition IJobService.cs:9
Allows manually triggering jobs hub updates.
Contains various cryptographic functions.
Receives notifications about permissions updates.
TokenValidationParameters ValidationParameters
The TokenValidationParameters for the ITokenFactory.
Set of objects needed to configure an Core.Application.
GeneralConfiguration GeneralConfiguration
The Configuration.GeneralConfiguration.
ElasticsearchConfiguration ElasticsearchConfiguration
The Configuration.ElasticsearchConfiguration.
FileLoggingConfiguration FileLoggingConfiguration
The Configuration.FileLoggingConfiguration.
DatabaseConfiguration DatabaseConfiguration
The Configuration.DatabaseConfiguration.
InternalConfiguration InternalConfiguration
The Configuration.InternalConfiguration.
IPlatformIdentifier PlatformIdentifier
The IPlatformIdentifier.
Swarm service operations for the Controllers.SwarmController.
Start and stop controllers for a swarm service.
Used for swarm operations. Functions may be no-op based on configuration.
Service for managing the dotnet-dump installation.
On Windows, DreamDaemon will show an unskippable prompt when using /world/proc/OpenPort()....
bool IsWindows
If the current platform is a Windows platform.
Abstraction for suspending and resuming processes.
Reads and writes to Streams associated with FileTicketResponses.
Service for temporarily storing files to be downloaded or uploaded.
Gets unassigned ports for use by TGS.
EngineType
The type of engine the codebase is using.
Definition EngineType.cs:7
DatabaseType
Type of database to user.