#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,
            }));
        }
Example #2
0
#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,
            }));
        }
Example #3
0
        /// <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;
            }
        }
Example #5
0
        /// <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;
            }
        }