#pragma warning disable CA1506 public async Task <IActionResult> Home(CancellationToken cancellationToken) { if (controlPanelConfiguration.Enable) { Response.Headers.Add( HeaderNames.Vary, new StringValues( new[] { HeaderNames.UserAgent, ApiHeaders.ApiVersionHeader })); } // we only allow authorization header issues if (ApiHeaders == null) { // if we are using a browser and the control panel, redirect to the app page if (controlPanelConfiguration.Enable && browserResolver.Browser.Type != BrowserType.Generic) { Logger.LogDebug("Unauthorized browser request (User-Agent: \"{0}\"), redirecting to control panel...", browserResolver.UserAgent); return(Redirect(Core.Application.ControlPanelRoute)); } try { var headers = new ApiHeaders(Request.GetTypedHeaders(), true); if (!headers.Compatible()) { return(StatusCode( HttpStatusCode.UpgradeRequired, new ErrorMessage(ErrorCode.ApiMismatch))); } } catch (HeadersException) { return(HeadersIssue(true)); } } return(Json(new ServerInformation { Version = assemblyInformationProvider.Version, ApiVersion = ApiHeaders.Version, DMApiVersion = DMApiConstants.Version, MinimumPasswordLength = generalConfiguration.MinimumPasswordLength, InstanceLimit = generalConfiguration.InstanceLimit, UserLimit = generalConfiguration.UserLimit, UserGroupLimit = generalConfiguration.UserGroupLimit, ValidInstancePaths = generalConfiguration.ValidInstancePaths, WindowsHost = platformIdentifier.IsWindows, SwarmServers = swarmService.GetSwarmServers(), OAuthProviderInfos = await oAuthProviders.ProviderInfos(cancellationToken).ConfigureAwait(false), UpdateInProgress = serverControl.UpdateInProgress, })); }
#pragma warning disable CA1506 public async Task <IActionResult> Home(CancellationToken cancellationToken) { if (controlPanelConfiguration.Enable) { Response.Headers.Add( HeaderNames.Vary, new StringValues( new[] { ApiHeaders.ApiVersionHeader })); } if (ApiHeaders == null) { if (controlPanelConfiguration.Enable && !Request.Headers.TryGetValue(ApiHeaders.ApiVersionHeader, out _)) { Logger.LogDebug("No API headers on request, redirecting to control panel..."); return(Redirect(ControlPanelController.ControlPanelRoute)); } try { // we only allow authorization header issues var headers = new ApiHeaders(Request.GetTypedHeaders(), true); if (!headers.Compatible()) { return(StatusCode( HttpStatusCode.UpgradeRequired, new ErrorMessageResponse(ErrorCode.ApiMismatch))); } } catch (HeadersException) { return(HeadersIssue(true)); } } return(Json(new ServerInformationResponse { Version = assemblyInformationProvider.Version, ApiVersion = ApiHeaders.Version, DMApiVersion = DMApiConstants.InteropVersion, MinimumPasswordLength = generalConfiguration.MinimumPasswordLength, InstanceLimit = generalConfiguration.InstanceLimit, UserLimit = generalConfiguration.UserLimit, UserGroupLimit = generalConfiguration.UserGroupLimit, ValidInstancePaths = generalConfiguration.ValidInstancePaths, WindowsHost = platformIdentifier.IsWindows, SwarmServers = swarmService.GetSwarmServers(), OAuthProviderInfos = await oAuthProviders.ProviderInfos(cancellationToken).ConfigureAwait(false), UpdateInProgress = serverControl.UpdateInProgress, })); }
/// <inheritdoc /> #pragma warning disable CA1506 // TODO: Decomplexify public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { if (context == null) { throw new ArgumentNullException(nameof(context)); } // ALL valid token and login requests that match a route go through this function // 404 is returned before if (AuthenticationContext != null && AuthenticationContext.User == null) { // valid token, expired password await Unauthorized().ExecuteResultAsync(context).ConfigureAwait(false); return; } // validate the headers try { ApiHeaders = new ApiHeaders(Request.GetTypedHeaders()); if (!ApiHeaders.Compatible()) { await StatusCode( HttpStatusCode.UpgradeRequired, new ErrorMessageResponse(ErrorCode.ApiMismatch)) .ExecuteResultAsync(context) .ConfigureAwait(false); return; } var errorCase = await ValidateRequest(context.HttpContext.RequestAborted).ConfigureAwait(false); if (errorCase != null) { await errorCase.ExecuteResultAsync(context).ConfigureAwait(false); return; } } catch (HeadersException) { if (requireHeaders) { await HeadersIssue(false) .ExecuteResultAsync(context) .ConfigureAwait(false); return; } } if (ModelState?.IsValid == false) { var errorMessages = ModelState .SelectMany(x => x.Value.Errors) .Select(x => x.ErrorMessage) // We use RequiredAttributes purely for preventing properties from becoming nullable in the databases // We validate missing required fields in controllers // Unfortunately, we can't remove the whole validator for that as it checks other things like StringLength // This is the best way to deal with it unfortunately .Where(x => !x.EndsWith(" field is required.", StringComparison.Ordinal)); if (errorMessages.Any()) { await BadRequest( new ErrorMessageResponse(ErrorCode.ModelValidationFailure) { AdditionalData = String.Join(Environment.NewLine, errorMessages) }) .ExecuteResultAsync(context).ConfigureAwait(false); return; } ModelState.Clear(); } using (ApiHeaders?.InstanceId != null ? LogContext.PushProperty("Instance", ApiHeaders.InstanceId) : null) using (AuthenticationContext != null ? LogContext.PushProperty("User", AuthenticationContext.User.Id) : null) using (LogContext.PushProperty("Request", $"{Request.Method} {Request.Path}")) { if (ApiHeaders != null) { Logger.LogDebug( "Starting API request: Version: {0}. {1}: {2}", ApiHeaders.ApiVersion.Semver(), HeaderNames.UserAgent, ApiHeaders.RawUserAgent); } else if (Request.Headers.TryGetValue(HeaderNames.UserAgent, out var userAgents)) { Logger.LogDebug( "Starting unauthorized API request. {0}: {1}", HeaderNames.UserAgent, userAgents); } else { Logger.LogDebug( "Starting unauthorized API request. No {0}!", HeaderNames.UserAgent); } await base.OnActionExecutionAsync(context, next).ConfigureAwait(false); } }
/// <inheritdoc /> #pragma warning disable CA1506 // TODO: Decomplexify public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { // ALL valid token and login requests that match a route go through this function // 404 is returned before if (AuthenticationContext != null && AuthenticationContext.User == null) { // valid token, expired password await Unauthorized().ExecuteResultAsync(context).ConfigureAwait(false); return; } // validate the headers try { ApiHeaders = new ApiHeaders(Request.GetTypedHeaders()); if (!ApiHeaders.Compatible()) { await StatusCode((int)HttpStatusCode.UpgradeRequired, new ErrorMessage { Message = "Provided API version is incompatible with server version!" }).ExecuteResultAsync(context).ConfigureAwait(false); return; } if (requireInstance) { if (!ApiHeaders.InstanceId.HasValue) { await BadRequest(new ErrorMessage { Message = "Missing Instance header!" }).ExecuteResultAsync(context).ConfigureAwait(false); return; } if (AuthenticationContext.InstanceUser == null) { // accessing an instance they don't have access to or one that's disabled await Forbid().ExecuteResultAsync(context).ConfigureAwait(false); return; } } } catch (InvalidOperationException e) { if (requireHeaders) { await BadRequest(new ErrorMessage { Message = e.Message }).ExecuteResultAsync(context).ConfigureAwait(false); return; } } if (ModelState?.IsValid == false) { var errorMessages = ModelState.SelectMany(x => x.Value.Errors).Select(x => x.ErrorMessage).ToList(); // HACK // do some fuckery to remove RequiredAttribute errors for (var I = 0; I < errorMessages.Count; ++I) { var message = errorMessages[I]; if (message.StartsWith("The ", StringComparison.Ordinal) && message.EndsWith(" field is required.", StringComparison.Ordinal)) { errorMessages.RemoveAt(I); --I; } } if (errorMessages.Count > 0) { await BadRequest(new ErrorMessage { Message = String.Join(Environment.NewLine, errorMessages) }).ExecuteResultAsync(context).ConfigureAwait(false); return; } } if (ApiHeaders != null) { Logger.LogDebug("Request made by User ID {0}. Api version: {1}. User-Agent: {2}. Type: {3}. Route {4}{5} to Instance {6}", AuthenticationContext?.User.Id.ToString(CultureInfo.InvariantCulture), ApiHeaders.ApiVersion, ApiHeaders.RawUserAgent, Request.Method, Request.Path, Request.QueryString, ApiHeaders.InstanceId); } try { await base.OnActionExecutionAsync(context, next).ConfigureAwait(false); } catch (OperationCanceledException e) { Logger.LogDebug("Request cancelled! Exception: {0}", e); throw; } }
/// <inheritdoc /> #pragma warning disable CA1506 // TODO: Decomplexify public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { // ALL valid token and login requests that match a route go through this function // 404 is returned before if (AuthenticationContext != null && AuthenticationContext.User == null) { // valid token, expired password await Unauthorized().ExecuteResultAsync(context).ConfigureAwait(false); return; } // validate the headers try { ApiHeaders = new ApiHeaders(Request.GetTypedHeaders()); if (!ApiHeaders.Compatible()) { await StatusCode( (int)HttpStatusCode.UpgradeRequired, new ErrorMessage(ErrorCode.ApiMismatch)) .ExecuteResultAsync(context) .ConfigureAwait(false); return; } if (requireInstance) { if (!ApiHeaders.InstanceId.HasValue) { await BadRequest(new ErrorMessage(ErrorCode.InstanceHeaderRequired)).ExecuteResultAsync(context).ConfigureAwait(false); return; } if (AuthenticationContext.InstanceUser == null) { // accessing an instance they don't have access to or one that's disabled await Forbid().ExecuteResultAsync(context).ConfigureAwait(false); return; } } } catch (InvalidOperationException e) { if (requireHeaders) { await BadRequest( new ErrorMessage(ErrorCode.BadHeaders) { AdditionalData = e.Message }) .ExecuteResultAsync(context) .ConfigureAwait(false); return; } } if (ModelState?.IsValid == false) { var errorMessages = ModelState .SelectMany(x => x.Value.Errors) .Select(x => x.ErrorMessage) // We use RequiredAttributes purely for preventing properties from becoming nullable in the databases // We validate missing required fields in controllers // Unfortunately, we can't remove the whole validator for that as it checks other things like StringLength // This is the best way to deal with it unfortunately .Where(x => !x.EndsWith(" field is required.", StringComparison.Ordinal)); if (errorMessages.Any()) { await BadRequest( new ErrorMessage(ErrorCode.ModelValidationFailure) { AdditionalData = String.Join(Environment.NewLine, errorMessages) }) .ExecuteResultAsync(context).ConfigureAwait(false); return; } ModelState.Clear(); } if (ApiHeaders != null) { Logger.LogDebug( "Request details: User ID {0}. Api version: {1}. User-Agent: {2}. Type: {3}. Route {4}{5} to Instance {6}", AuthenticationContext?.User.Id.Value.ToString(CultureInfo.InvariantCulture), ApiHeaders.ApiVersion.Semver(), ApiHeaders.RawUserAgent, Request.Method, Request.Path, Request.QueryString, ApiHeaders.InstanceId); } try { await base.OnActionExecutionAsync(context, next).ConfigureAwait(false); } catch (OperationCanceledException e) { Logger.LogDebug("Request cancelled! Exception: {0}", e); throw; } }