public void OnException(ExceptionContext context)
        {
            _logger.Error($"Unhandled exception caught by {nameof(ExceptionHandlingFilter)}: {context.Exception}");

            var restError = _restErrorProvider.GetRestErrorFromException(context.Exception);

            context.Result = new ObjectResult(new { Message = restError.Message })
            {
                StatusCode = restError.Code,
            };
        }
        private IActionResult CreateActionResultFromException(
            Exception exception,
            bool enforceOptimisticLock = false)
        {
            var restError = _restErrorProvider.GetRestErrorFromException(exception);

            if (exception is ConcurrencyException && enforceOptimisticLock)
            {
                // See RFC 5789 - Conflicting modification (with "If-Match" header)
                restError.Code    = StatusCodes.Status412PreconditionFailed;
                restError.Message = "Resource was modified by another consumer.";
            }

            return(string.IsNullOrWhiteSpace(restError.Message)
                ? (IActionResult)StatusCode(restError.Code)
                : StatusCode(restError.Code, ErrorTranslator.GetErrorMessage(restError.Message)));
        }
Exemplo n.º 3
0
        private IHttpActionResult CreateActionResultFromException(
            Exception exception,
            bool enforceOptimisticLock = false)
        {
            var restError = restErrorProvider.GetRestErrorFromException(exception);

            if (exception is ConcurrencyException && enforceOptimisticLock)
            {
                // See RFC 5789 - Conflicting modification (with "If-Match" header)
                restError.Code    = (int)HttpStatusCode.PreconditionFailed;
                restError.Message = "Resource was modified by another consumer.";
            }

            return(string.IsNullOrWhiteSpace(restError.Message)
                ? new StatusCodeResult((HttpStatusCode)restError.Code, this)
                : new StatusCodeResult((HttpStatusCode)restError.Code, this).WithError(restError.Message));
        }
Exemplo n.º 4
0
        public async Task <HttpResponseMessage> ExecuteAuthorizationFilterAsync(
            HttpActionContext actionContext,
            CancellationToken cancellationToken,
            Func <Task <HttpResponseMessage> > continuation)
        {
            var actionAttribute = actionContext.ActionDescriptor.GetCustomAttributes <EdFiAuthorizationAttribute>()
                                  .SingleOrDefault();

            var controllerAttribute = actionContext.ControllerContext.ControllerDescriptor.GetCustomAttributes <EdFiAuthorizationAttribute>()
                                      .SingleOrDefault();

            var authorizationAttribute = actionAttribute ?? controllerAttribute;

            if (authorizationAttribute == null)
            {
                return(await continuation());
            }

            try
            {
                await _authorizationProvider.AuthorizeSingleItemAsync(
                    CreateAuthorizationContext(actionContext, authorizationAttribute),
                    cancellationToken);
            }
            catch (Exception ex)
            {
                var restError = _restErrorProvider.GetRestErrorFromException(ex);

                var result = string.IsNullOrWhiteSpace(restError.Message)
                    ? new StatusCodeResult((HttpStatusCode)restError.Code, actionContext.Request)
                    : new StatusCodeResult((HttpStatusCode)restError.Code, actionContext.Request).WithError(restError.Message);

                return(await result.ExecuteAsync(cancellationToken));
            }

            return(await continuation());
        }
        public virtual IActionResult Get()
        {
            if (!_isEnabled)
            {
                return(NotFound());
            }

            object    json      = null;
            RESTError restError = null;

            try
            {
                var routeDataValues = Request.RouteValues;

                string organizationCode        = (string)routeDataValues["organizationCode"];
                string compositeCategory       = (string)routeDataValues["compositeCategory"];
                string compositeCollectionName = (string)routeDataValues["compositeName"];
                string compositeResourceName   = CompositeTermInflector.MakeSingular(compositeCollectionName);

                // Try to find the composite definition that matches the incoming URIs composite category/name
                if (!_compositeMetadataProvider.TryGetCompositeDefinition(
                        organizationCode,
                        compositeCategory,
                        compositeResourceName,
                        out XElement compositeDefinition))
                {
                    return(NotFound());
                }

                // Prepare query string parameters
                var rawQueryStringParameters = ParseQuery(Request.QueryString.ToString());

                var queryStringParameters =
                    rawQueryStringParameters.Keys.ToDictionary <string, string, object>(

                        // Replace underscores with periods for appropriate processing
                        kvp => kvp.Replace('_', '.'),
                        kvp => rawQueryStringParameters[kvp],
                        StringComparer.InvariantCultureIgnoreCase);

                //respond quickly to DOS style requests (should we catch these earlier?  e.g. attribute filter?)

                if (queryStringParameters.TryGetValue("limit", out object limitAsObject))
                {
                    if (int.TryParse(limitAsObject.ToString(), out int limit) &&
                        (limit <= 0 || limit > 100))
                    {
                        return(BadRequest(ErrorTranslator.GetErrorMessage("Limit must be omitted or set to a value between 1 and 100.")));
                    }
                }

                // Process specification for route and query string parameters
                var specificationParameters = GetCompositeSpecificationParameters();

                // Ensure all matched route key values were used by the current composite
                var suppliedSpecificationParameters =
                    routeDataValues
                    .Where(kvp => !_standardApiRouteKeys.Contains(kvp.Key))
                    .ToList();

                var unusedSpecificationParameters =
                    suppliedSpecificationParameters
                    .Where(kvp => !specificationParameters.ContainsKey(kvp.Key))
                    .ToList();

                if (unusedSpecificationParameters.Any())
                {
                    return(NotFound());
                }

                AddInherentSupportForIdParameter();

                json = _compositeResourceResponseProvider.Get(
                    compositeDefinition,
                    specificationParameters,
                    queryStringParameters,
                    GetNullValueHandling());

                void AddInherentSupportForIdParameter()
                {
                    if (!Request.RouteValues.TryGetValue("id", out object idAsObject))
                    {
                        return;
                    }

                    if (!Guid.TryParse(idAsObject.ToString(), out Guid id))
                    {
                        throw new BadRequestException("The supplied resource identifier is invalid.");
                    }

                    specificationParameters.Add(
                        "Id",
                        new CompositeSpecificationParameter
                    {
                        FilterPath = "Id",
                        Value      = id
                    });
                }

                IDictionary <string, CompositeSpecificationParameter> GetCompositeSpecificationParameters()
                {
                    var specificationElt = compositeDefinition.Element("Specification");

                    if (specificationElt == null)
                    {
                        return(new Dictionary <string, CompositeSpecificationParameter>());
                    }

                    var specificationFilterByName = specificationElt
                                                    .Elements("Parameter")
                                                    .ToDictionary(
                        p => p.AttributeValue("name"),
                        p => new
                    {
                        FilterPath = p.AttributeValue("filterPath"),
                        Queryable  = p.AttributeValue("queryable") == "true"
                    },
                        StringComparer.InvariantCultureIgnoreCase);

                    // Identify relevant route values
                    var matchingRouteValues = routeDataValues
                                              .Where(x => specificationFilterByName.ContainsKey(x.Key));

                    // Copy route values that match the specification to the parameter dictionary
                    var parameters = matchingRouteValues.ToDictionary(
                        kvp => kvp.Key, kvp => new CompositeSpecificationParameter
                    {
                        FilterPath = specificationFilterByName[kvp.Key]
                                     .FilterPath,
                        Value = kvp.Value
                    });

                    // Identify relevant query string values
                    var matchingQueryStringParameters = queryStringParameters

                                                        // Skip query string parameter matching if the key was already matched by the route
                                                        .Where(x => !parameters.ContainsKey(x.Key))
                                                        .Where(
                        x => specificationFilterByName.ContainsKey(x.Key) &&
                        specificationFilterByName[x.Key]
                        .Queryable)
                                                        .ToList();

                    // Copy route values that match the specification to the parameter dictionary
                    foreach (var kvp in matchingQueryStringParameters)
                    {
                        // Guids aren't "coerced" by SqlParameter correctly
                        object value = Guid.TryParse(kvp.Value as string, out Guid guidValue)
                            ? guidValue
                            : kvp.Value;

                        parameters.Add(
                            kvp.Key,
                            new CompositeSpecificationParameter
                        {
                            FilterPath = specificationFilterByName[kvp.Key]
                                         .FilterPath,
                            Value = value
                        });

                        // Remove the processed Specification-based query string parameter
                        queryStringParameters.Remove(kvp.Key);
                    }

                    return(parameters);
                }
            }
            catch (Exception ex)
            {
                _logger.Error(ex);
                restError = _restErrorProvider.GetRestErrorFromException(ex);
            }

            if (restError != null)
            {
                return(string.IsNullOrWhiteSpace(restError.Message)
                    ? (IActionResult)StatusCode(restError.Code)
                    : StatusCode(restError.Code, restError.Message));
            }

            return(Ok(json));

            NullValueHandling GetNullValueHandling()
            {
                // The custom 'IncludeNulls' header is supported for testing purposes.
                if (Request.Headers.TryGetValue("IncludeNulls", out StringValues headerValues) &&
                    headerValues.Contains("true"))
                {
                    return(NullValueHandling.Include);
                }

                return(NullValueHandling.Ignore);
            }
        }
        public virtual IHttpActionResult Get()
        {
            string    json      = null;
            RESTError restError = null;

            try
            {
                var routeDataValues = ActionContext.RequestContext.RouteData.Values;

                string organizationCode        = (string)routeDataValues["organizationCode"];
                string compositeCategory       = (string)routeDataValues["compositeCategory"];
                string compositeCollectionName = (string)routeDataValues["compositeName"];
                string compositeResourceName   = CompositeTermInflector.MakeSingular(compositeCollectionName);

                // Try to find the composite definition that matches the incoming URIs composite category/name
                if (!_compositeMetadataProvider.TryGetCompositeDefinition(
                        organizationCode,
                        compositeCategory,
                        compositeResourceName,
                        out XElement compositeDefinition))
                {
                    return(NotFound());
                }

                // Prepare query string parameters
                var rawQueryStringParameters = ActionContext.Request.RequestUri.ParseQueryString();

                var queryStringParameters =
                    rawQueryStringParameters.AllKeys.ToDictionary <string, string, object>(

                        // Replace underscores with periods for appropriate processing
                        kvp => kvp.Replace('_', '.'),
                        kvp => rawQueryStringParameters[kvp],
                        StringComparer.InvariantCultureIgnoreCase);

                var defaultPageSizeLimit = _defaultPageSizeLimitProvider.GetDefaultPageSizeLimit();

                //respond quickly to DOS style requests (should we catch these earlier?  e.g. attribute filter?)
                if (queryStringParameters.TryGetValue("limit", out object limitAsObject))
                {
                    if (int.TryParse(limitAsObject.ToString(), out int limit) &&
                        (limit <= 0 || limit > defaultPageSizeLimit))
                    {
                        return(BadRequest($"Limit must be omitted or set to a value between 1 and max value defined in configuration file (defaultPageSizeLimit)."));
                    }
                }

                // Process specification for route and query string parameters
                var specificationParameters = GetCompositeSpecificationParameters(
                    compositeDefinition, routeDataValues, queryStringParameters);

                // Ensure all matched route key values were used by the current composite
                var suppliedSpecificationParameters =
                    routeDataValues
                    .Where(kvp => !StandardApiRouteKeys.Contains(kvp.Key));

                var unusedSpecificationParameters =
                    suppliedSpecificationParameters
                    .Where(kvp => !specificationParameters.ContainsKey(kvp.Key));

                if (unusedSpecificationParameters.Any())
                {
                    return(new StatusCodeResult(HttpStatusCode.NotFound, this));
                }

                AddInherentSupportForIdParameter(specificationParameters);

                json = _compositeResourceResponseProvider.GetJson(
                    compositeDefinition,
                    specificationParameters,
                    queryStringParameters,
                    GetNullValueHandling());
            }
            catch (Exception ex)
            {
                _logger.Error(ex);
                restError = _restErrorProvider.GetRestErrorFromException(ex);
            }

            if (restError != null)
            {
                return(string.IsNullOrWhiteSpace(restError.Message)
                    ? new StatusCodeResult((HttpStatusCode)restError.Code, this)
                    : new StatusCodeResult((HttpStatusCode)restError.Code, this).WithError(restError.Message));
            }

            return(new ResponseMessageResult(
                       new HttpResponseMessage(HttpStatusCode.OK)
            {
                Content = new StringContent(json, Encoding.UTF8, "application/json")
            }));
        }