/// <summary>
        /// Called by the runtime to invoke the middleware logic. In most cases, the middleware component should return the task
        /// generated by calling the next delegate in the request chain with the provided context.
        /// </summary>
        /// <param name="context">The invocation request governing data in this pipeline run.</param>
        /// <param name="next">The next delegate in the chain to be invoked.</param>
        /// <param name="cancelToken">The cancel token.</param>
        /// <returns>Task.</returns>
        public async Task InvokeAsync(GraphFieldExecutionContext context, GraphMiddlewareInvocationDelegate <GraphFieldExecutionContext> next, CancellationToken cancelToken)
        {
            FieldAuthorizationResult result = FieldAuthorizationResult.Default();

            if (context.IsValid)
            {
                // execute the authorization pipeline
                var authRequest = new GraphFieldAuthorizationRequest(context.Request);
                var authContext = new GraphFieldAuthorizationContext(context, authRequest);
                await _authPipeline.InvokeAsync(authContext, cancelToken).ConfigureAwait(false);

                result = authContext.Result ?? FieldAuthorizationResult.Default();

                // by default, deny any stati not explicitly declared as "successful" by this component.
                if (!result.Status.IsAuthorized())
                {
                    context.Messages.Critical(
                        $"Access Denied to field {context.Field.Route.Path}",
                        Constants.ErrorCodes.ACCESS_DENIED,
                        context.Request.Origin);
                }
            }

            if (!result.Status.IsAuthorized())
            {
                context.ResolvedSourceItems.AddRange(context.Request.DataSource.Items);
                context.ResolvedSourceItems.ForEach(x => x.Fail());
            }

            await next(context, cancelToken).ConfigureAwait(false);
        }
示例#2
0
        /// <summary>
        /// Iterates over every secure field in the operation on the context, attempting to authorize the
        /// user to each one.
        /// </summary>
        /// <param name="context">The primary query context.</param>
        /// <param name="cancelToken">The cancel token.</param>
        /// <returns><c>true</c> if authorization was successful, otherwise false.</returns>
        private async Task <bool> AuthorizeOperation(GraphQueryExecutionContext context, CancellationToken cancelToken)
        {
            var  authTasks      = new List <Task>();
            bool anyFieldFailed = false;

            foreach (var fieldContext in context.QueryOperation.SecureFieldContexts)
            {
                var authRequest = new GraphFieldAuthorizationRequest(fieldContext);
                var authContext = new GraphFieldAuthorizationContext(context, authRequest);

                var pipelineTask = _authPipeline.InvokeAsync(authContext, cancelToken)
                                   .ContinueWith(
                    (_) =>
                {
                    var authResult = authContext.Result ?? FieldAuthorizationResult.Default();

                    // fake the path elements from the field route. since we don't have a full resolution chain
                    // when doing query level authorization (no indexers on potential child fields since
                    // nothing is actually resolved yet)
                    if (!authResult.Status.IsAuthorized())
                    {
                        context.Messages.Critical(
                            $"Access Denied to field {fieldContext.Field.Route.Path}",
                            Constants.ErrorCodes.ACCESS_DENIED,
                            fieldContext.Origin);
                        anyFieldFailed = true;
                    }
                }, cancelToken);

                authTasks.Add(pipelineTask);
            }

            await Task.WhenAll(authTasks).ConfigureAwait(false);

            return(anyFieldFailed);
        }
        /// <inheritdoc />
        public async Task <IGraphOperationResult> ExecuteRequest(
            GraphQueryExecutionContext context,
            CancellationToken cancelToken = default)
        {
            Validation.ThrowIfNull(context, nameof(context));

            // *******************************
            // Primary query execution
            // *******************************
            await _pipeline.InvokeAsync(context, cancelToken).ConfigureAwait(false);

            // *******************************
            // Response Generation
            // *******************************
            var queryResponse = context.Result;

            if (queryResponse == null)
            {
                queryResponse = new GraphOperationResult(context.Request);
                queryResponse.Messages.Add(GraphMessageSeverity.Critical, ERROR_NO_RESPONSE, Constants.ErrorCodes.GENERAL_ERROR);
            }

            return(queryResponse);
        }
示例#4
0
        /// <summary>
        /// Submits the GraphQL query for processing.
        /// </summary>
        /// <param name="queryData">The query data.</param>
        /// <returns>Task&lt;IActionResult&gt;.</returns>
        public virtual async Task SubmitGraphQLQuery(GraphQueryData queryData)
        {
            // ensure data was received
            if (queryData == null || string.IsNullOrWhiteSpace(queryData.Query))
            {
                await this.WriteStatusCodeResponse(HttpStatusCode.BadRequest, ERROR_NO_QUERY_PROVIDED).ConfigureAwait(false);

                return;
            }

            using (var cancelSource = new CancellationTokenSource())
            {
                try
                {
                    // *******************************
                    // Setup
                    // *******************************
                    this.GraphQLRequest = this.CreateRequest(queryData);
                    if (this.GraphQLRequest == null)
                    {
                        await this.WriteStatusCodeResponse(HttpStatusCode.InternalServerError, ERROR_NO_REQUEST_CREATED).ConfigureAwait(false);

                        return;
                    }

                    // *******************************
                    // Primary query execution
                    // *******************************
                    var metricPackage = this.EnableMetrics ? _metricsFactory.CreateMetricsPackage() : null;
                    var context       = new GraphQueryExecutionContext(
                        this.GraphQLRequest,
                        this.HttpContext.RequestServices,
                        this.HttpContext.User,
                        metricPackage,
                        _logger);

                    await _queryPipeline.InvokeAsync(context, cancelSource.Token).ConfigureAwait(false);

                    // *******************************
                    // Response Generation
                    // *******************************
                    var queryResponse = context.Result;
                    if (queryResponse == null)
                    {
                        queryResponse = this.ErrorMessageAsGraphQLResponse(ERROR_NO_RESPONSE);
                    }

                    // if any metrics were populated in the execution, allow a child class to process them
                    if (context.Metrics != null)
                    {
                        this.HandleQueryMetrics(context.Metrics);
                    }

                    // all done, finalize and return
                    queryResponse = this.FinalizeResult(queryResponse);
                    await this.WriteResponse(queryResponse).ConfigureAwait(false);
                }
                catch (Exception ex)
                {
                    var exceptionResult = this.HandleQueryException(ex);
                    if (exceptionResult == null)
                    {
                        // no one was able to handle hte exception. Log it if able and just fail out to the caller
                        _logger?.UnhandledExceptionEvent(ex);
                        await this.WriteStatusCodeResponse(HttpStatusCode.InternalServerError, ERROR_INTERNAL_SERVER_ISSUE).ConfigureAwait(false);
                    }
                    else
                    {
                        await this.WriteResponse(exceptionResult).ConfigureAwait(false);
                    }
                }
            }
        }
示例#5
0
        private async Task ExecuteOperation(GraphQueryExecutionContext context)
        {
            // create a cancelation sourc irrespective of the required timeout for exeucting this operation
            // this allows for indication of why a task was canceled (timeout or other user driven reason)
            // vs just "it was canceled" which allows for tighter error messages in the response.
            var operation        = context.QueryOperation;
            var fieldInvocations = new List <FieldPipelineInvocation>();

            // Convert the supplied variable values to usable objects of the type expression
            // of the chosen operation
            var variableResolver = new ResolvedVariableGenerator(_schema, operation);
            var variableData     = variableResolver.Resolve(context.Request.VariableData);
            var cancelSource     = new CancellationTokenSource();

            try
            {
                // begin a field execution pipeline for each top level field
                foreach (var invocationContext in operation.FieldContexts)
                {
                    var path = new SourcePath();
                    path.AddFieldName(invocationContext.Name);

                    object dataSourceValue;

                    // fetch the source data value to use for the field invocation
                    // attempt to retrieve from the master context if it was supplied by the pipeline
                    // invoker, otherwise generate a root source
                    if (!context.DefaultFieldSources.TryRetrieveSource(invocationContext.Field, out dataSourceValue))
                    {
                        dataSourceValue = this.GenerateRootSourceData(operation.OperationType);
                    }

                    var topLevelDataItem = new GraphDataItem(invocationContext, dataSourceValue, path);

                    var sourceData = new GraphFieldDataSource(dataSourceValue, path, topLevelDataItem);

                    var fieldRequest = new GraphFieldRequest(
                        invocationContext,
                        sourceData,
                        new SourceOrigin(invocationContext.Origin.Location, path),
                        context.Items);

                    var fieldContext = new GraphFieldExecutionContext(
                        context,
                        fieldRequest,
                        variableData,
                        context.DefaultFieldSources);

                    var fieldTask = _fieldExecutionPipeline.InvokeAsync(fieldContext, cancelSource.Token);

                    var pipelineInvocation = new FieldPipelineInvocation()
                    {
                        Task         = fieldTask,
                        DataItem     = topLevelDataItem,
                        FieldContext = fieldContext,
                    };

                    fieldInvocations.Add(pipelineInvocation);

                    // top level mutation operatons must be executed in sequential order
                    // https://graphql.github.io/graphql-spec/June2018/#sec-Mutation
                    if (_awaitEachTask || operation.OperationType == GraphCollection.Mutation)
                    {
                        await fieldTask.ConfigureAwait(false);
                    }
                }

                // await all the outstanding tasks or a configured timeout
                var fieldPipelineTasksWrapper = Task.WhenAll(fieldInvocations.Select(x => x.Task));
                var timeOutTask = Task.Delay(_timeoutMs, cancelSource.Token);

                var completedTask = await Task.WhenAny(fieldPipelineTasksWrapper, timeOutTask).ConfigureAwait(false);

                var isTimedOut = completedTask == timeOutTask;
                var cancelationWasRequested = cancelSource.IsCancellationRequested;

                if (!isTimedOut)
                {
                    // Field resolutions completed within the timeout period.
                    // Consider that the task may have faulted or been canceled causing them to complete incorrectly.
                    // "re-await" so that any exceptions/cancellation are rethrown correctly.
                    // and not aggregated under the `WhenAll/WhenAny` task from above
                    // https://stackoverflow.com/questions/4238345/asynchronously-wait-for-taskt-to-complete-with-timeout
                    foreach (var invocation in fieldInvocations)
                    {
                        await invocation.Task.ConfigureAwait(false);

                        // load the reslts of each field (in order) to the context
                        // for further processing
                        context.FieldResults.Add(invocation.DataItem);
                        context.Messages.AddRange(invocation.FieldContext.Messages);
                    }
                }
                else
                {
                    // when the timeout finishes first, process the cancel token in case any outstanding tasks are running
                    // helps in cases where the timeout finished first but any of the field resolutions are perhaps stuck open
                    // instruct all outstanding tasks to clean them selves up at the earlest possible point
                    if (!cancelationWasRequested)
                    {
                        cancelSource.Cancel();
                    }
                }

                if (cancelationWasRequested)
                {
                    context.Messages.Critical("The execution was canceled prior to completion of the requested query.", Constants.ErrorCodes.OPERATION_CANCELED);
                }
                else if (isTimedOut)
                {
                    context.Messages.Critical($"The execution timed out prior to completion of the requested query. (Total Time: {_timeoutMs}ms)", Constants.ErrorCodes.OPERATION_CANCELED);
                }
            }
            finally
            {
                cancelSource.Dispose();
            }
        }
示例#6
0
        /// <summary>
        /// For any resolved, non-leaf items assigned to the result, pass each through the resolution pipeline
        /// and await their individual results.
        /// </summary>
        /// <param name="context">The "parent" context supplying data for downstream reslts.</param>
        /// <param name="cancelToken">The cancel token.</param>
        /// <returns>Task.</returns>
        private async Task ProcessDownStreamFieldContexts(GraphFieldExecutionContext context, CancellationToken cancelToken)
        {
            if (context.InvocationContext.ChildContexts.Count == 0)
            {
                return;
            }

            // items resolved on this active context become the source for any downstream fields
            //  ---
            // can never extract child fields from a null value (even if its valid for the item)
            // or one that isnt read for it
            List <GraphDataItem> allSourceItems = context
                                                  .ResolvedSourceItems
                                                  .SelectMany(x => x.FlattenListItemTree())
                                                  .Where(x => x.ResultData != null && x.Status == FieldItemResolutionStatus.NeedsChildResolution)
                                                  .ToList();

            if (allSourceItems.Count == 0)
            {
                return;
            }

            // find a reference to the graph type for the field
            var graphType = _schema.KnownTypes.FindGraphType(context.Field.TypeExpression.TypeName);

            // theoretically it can't not be found, but you never know
            if (graphType == null)
            {
                var msg = $"Internal Server Error. When processing the results of '{context.Field.Route.Path}' no graph type on the target schema " +
                          $"could be found for the type name '{context.Field.TypeExpression.TypeName}'. " +
                          $"Unable to process the {allSourceItems.Count} item(s) generated.";

                context.Messages.Add(
                    GraphMessageSeverity.Critical,
                    Constants.ErrorCodes.EXECUTION_ERROR,
                    msg,
                    context.Request.Origin);

                context.Cancel();
                return;
            }

            var pipelines = new List <Task>();

            // Step 0
            // -----------------------------------------------------------------------
            // create a lookup of source items by concrete type known to the schema, for easy seperation to the individual
            // downstream child contexts
            var sourceItemLookup = this.MapExpectedConcreteTypeFromSourceItem(allSourceItems, graphType);

            foreach (var childInvocationContext in context.InvocationContext.ChildContexts)
            {
                // Step 1
                // ----------------------------
                // figure out which child items need to be processed through it
                IEnumerable <GraphDataItem> sourceItemsToInclude;
                if (childInvocationContext.ExpectedSourceType == null)
                {
                    sourceItemsToInclude = allSourceItems;
                }
                else
                {
                    // if no children match the required type of the children present, then skip it
                    // this can happen quite often in the case of a union or an interface where multiple invocation contexts
                    // are added to a plan for the same child field in case a parent returns a member of the union or an
                    // implementer of the interface
                    if (!sourceItemLookup.ContainsKey(childInvocationContext.ExpectedSourceType))
                    {
                        continue;
                    }

                    sourceItemsToInclude = sourceItemLookup[childInvocationContext.ExpectedSourceType];
                }

                // Step 1B
                // For any source items replace any virtual objects with defaults found
                // on the context for the field in question
                if (context.DefaultFieldSources.TryRetrieveSource(childInvocationContext.Field, out var defaultSource))
                {
                    sourceItemsToInclude = sourceItemsToInclude.Select((currentValue) =>
                    {
                        if (currentValue.ResultData is VirtualResolvedObject)
                        {
                            currentValue.AssignResult(defaultSource);
                        }

                        return(currentValue);
                    });
                }

                // Step 2
                // ----------------------------
                // when the invocation is as a batch, create one execution context for all children
                // when its "per source" create a context for each child individually
                IEnumerable <GraphFieldExecutionContext> childContexts = this.CreateChildExecutionContexts(
                    context,
                    childInvocationContext,
                    sourceItemsToInclude);

                // Step 3
                // --------------------
                // Fire off the contexts through the pipeline
                foreach (var childContext in childContexts)
                {
                    var task = _fieldExecutionPipeline.InvokeAsync(childContext, cancelToken)
                               .ContinueWith(invokeTask =>
                    {
                        this.CaptureChildFieldExecutionResults(context, childContext, invokeTask);
                    });

                    pipelines.Add(task);
                    if (_awaitEachPipeline)
                    {
                        await task.ConfigureAwait(false);
                    }
                }
            }

            // wait for every pipeline to finish
            await Task.WhenAll(pipelines).ConfigureAwait(false);

            // reawait to allow for unwrapping and throwing of internal exceptions
            if (!_awaitEachPipeline)
            {
                foreach (var task in pipelines.Where(x => x.IsFaulted))
                {
                    await task.ConfigureAwait(false);
                }
            }
        }
示例#7
0
        /// <summary>
        /// For any resolved, non-leaf items assigned to the result, pass each through the resolution pipeline
        /// and await their individual results.
        /// </summary>
        /// <param name="context">The "parent" context supplying data for downstream reslts.</param>
        /// <param name="cancelToken">The cancel token.</param>
        /// <returns>Task.</returns>
        private async Task ProcessDownStreamFieldContexts(GraphFieldExecutionContext context, CancellationToken cancelToken)
        {
            if (context.InvocationContext.ChildContexts.Count == 0)
            {
                return;
            }

            var pipelines = new List <Task>();

            // items resolved on this active context become the source for any downstream fields
            //  ---
            // can never extract child fields from a null value (even if its valid for the item)
            // or one that isnt read for it
            IEnumerable <GraphDataItem> allSourceItems = context
                                                         .ResolvedSourceItems
                                                         .SelectMany(x => x.FlattenListItemTree())
                                                         .Where(x => x.ResultData != null && x.Status == FieldItemResolutionStatus.NeedsChildResolution);

            if (!allSourceItems.Any())
            {
                return;
            }

            // create a lookup of source item by result type for easy seperation to the individual
            // downstream child contexts
            var sourceItemLookup = allSourceItems.ToLookup(x => x.ResultData.GetType());

            IEnumerable <GraphFieldExecutionContext> childContexts = null;

            foreach (var childInvocationContext in context.InvocationContext.ChildContexts)
            {
                // Step 1
                // ----------------------------
                // figure out which child items need to be processed through it
                IEnumerable <GraphDataItem> sourceItemsToInclude;
                if (childInvocationContext.ExpectedSourceType == null)
                {
                    sourceItemsToInclude = allSourceItems;
                }
                else
                {
                    // if no children match the required type of the children present, then skip it
                    // this can happen quite often in the case of a union or an interface where multiple invocation contexts
                    // are added to a plan for the same child field in case a parent returns a member of the union or an
                    // implementer of the interface
                    if (!sourceItemLookup.Contains(childInvocationContext.ExpectedSourceType))
                    {
                        continue;
                    }

                    sourceItemsToInclude = sourceItemLookup[childInvocationContext.ExpectedSourceType];
                }

                // Step 2
                // ----------------------------
                // when the invocation is as a batch, create one execution context for all children
                // when its "per source" create a context for each child individually
                childContexts = this.CreateChildExecutionContexts(context, childInvocationContext, sourceItemsToInclude);

                // Step 3
                // --------------------
                // Fire off the contexts through the pipeline
                foreach (var childContext in childContexts)
                {
                    var task = _fieldExecutionPipeline.InvokeAsync(childContext, cancelToken)
                               .ContinueWith(invokeTask =>
                    {
                        context.Messages.AddRange(childContext.Messages);
                    });

                    pipelines.Add(task);
                    if (_awaitEachPipeline)
                    {
                        await task.ConfigureAwait(false);
                    }
                }
            }

            // wait for every pipeline to finish
            await Task.WhenAll(pipelines).ConfigureAwait(false);

            // reawait to allow for unwrapping and throwing of internal exceptions
            if (!_awaitEachPipeline)
            {
                foreach (var task in pipelines.Where(x => x.IsFaulted))
                {
                    await task.ConfigureAwait(false);
                }
            }
        }