Ejemplo n.º 1
0
 public override Task <IActionResult> Get(Guid id)
 {
     return(Task.FromResult <IActionResult>(
                StatusCode(StatusCodes.Status405MethodNotAllowed,
                           ErrorTranslator
                           .GetErrorMessage("The allowed methods for this resource with the 'Test-Profile-Resource-WriteOnly' profile are PUT, POST, DELETE and OPTIONS."))));
 }
Ejemplo n.º 2
0
 public override Task <IActionResult> GetAll(UrlQueryParametersRequest urlQueryParametersRequest, SchoolsNullReadRequest specification = null)
 {
     return(Task.FromResult <IActionResult>(
                StatusCode(StatusCodes.Status405MethodNotAllowed,
                           ErrorTranslator
                           .GetErrorMessage("The allowed methods for this resource with the 'Test-Profile-Resource-WriteOnly' profile are PUT, POST, DELETE and OPTIONS."))));
 }
        public virtual async Task <IActionResult> Post([FromBody] TPostRequest request)
        {
            var       validationState = new ValidationState();
            PutResult result;

            // Make sure Id is not already set (no client-assigned Ids)
            if (request.Id != default(Guid))
            {
                return(BadRequest(ErrorTranslator.GetErrorMessage("Resource identifiers cannot be assigned by the client.")));
            }
            else
            {
                result = await PutPipeline.Value.ProcessAsync(
                    new PutContext <TResourceWriteModel, TAggregateRoot>(request, validationState), CancellationToken.None);
            }

            // Throw an exceptions that occurred for global exception handling
            if (result.Exception != null)
            {
                Logger.Error("Post", result.Exception);
                return(CreateActionResultFromException(result.Exception));
            }

            Response.GetTypedHeaders().ETag     = GetEtag(result.ETag);
            Response.GetTypedHeaders().Location = new Uri(GetResourceUrl(result.ResourceId.GetValueOrDefault()));
            return(result.ResourceWasCreated
                ? (IActionResult)Created(new Uri(GetResourceUrl(result.ResourceId.GetValueOrDefault())), null)
                : Ok());
        }
Ejemplo n.º 4
0
 public override Task <IActionResult> Put(SchoolsNullWriteRequest request, Guid id)
 {
     return(Task.FromResult <IActionResult>(
                StatusCode(StatusCodes.Status405MethodNotAllowed,
                           ErrorTranslator
                           .GetErrorMessage("The allowed methods for this resource with the 'Test-Profile-Resource-ReadOnly' profile are GET, DELETE and OPTIONS."))));
 }
        public IActionResult Get()
        {
            if (!_isEnabled)
            {
                return(NotFound());
            }

            try
            {
                var groupedLoadOrder = GetGroupedLoadOrder(_resourceLoadGraphFactory.CreateResourceLoadGraph()).ToList();
                ModifyLoadOrderForAuthorizationConcerns(groupedLoadOrder);
                return(Request.GetTypedHeaders().Accept != null &&
                       Request.GetTypedHeaders().Accept.Any(a => a.MediaType.Value.EqualsIgnoreCase(CustomMediaContentTypes.GraphML))
                ? Ok(CreateGraphML(_resourceLoadGraphFactory.CreateResourceLoadGraph()))
                : Ok(groupedLoadOrder));
            }
            catch (NonAcyclicGraphException e)
            {
                // return a bad request if a circular reference occurs, with the path information of the reference.
                string message = e.Message.Replace($"{Environment.NewLine}    is used by ", " -> ")
                                 .Replace(Environment.NewLine, " ");

                return(BadRequest(ErrorTranslator.GetErrorMessage(message)));
            }
        }
        public virtual async Task <IActionResult> GetAll(
            [FromQuery] UrlQueryParametersRequest urlQueryParametersRequest,
            [FromQuery] TGetByExampleRequest request = default(TGetByExampleRequest))
        {
            //respond quickly to DOS style requests (should we catch these earlier?  e.g. attribute filter?)
            if (urlQueryParametersRequest.Limit != null &&
                (urlQueryParametersRequest.Limit < 0 || urlQueryParametersRequest.Limit > _defaultPageLimitSize))
            {
                return(BadRequest(
                           ErrorTranslator.GetErrorMessage(
                               "Limit must be omitted or set to a value between 1 and max value defined in configuration file (defaultPageSizeLimit).")));
            }

            var internalRequestAsResource = new TResourceReadModel();
            var internalRequest           = internalRequestAsResource as TEntityInterface;

            if (request != null)
            {
                MapAll(request, internalRequest);
            }

            //TODO: Add support for If-None-Match; current implementation cannot verify value without going to the db
            // Read the incoming ETag header, if present
            Request.TryGetRequestHeader(HeaderConstants.IfNoneMatch, out string etagValue);

            var queryParameters = new QueryParameters(urlQueryParametersRequest);

            // Execute the pipeline (synchronously)
            var result = await GetManyPipeline.Value
                         .ProcessAsync(
                new GetManyContext <TResourceReadModel, TAggregateRoot>(internalRequestAsResource, queryParameters),
                new CancellationToken());

            // Handle exception result
            if (result.Exception != null)
            {
                Logger.Error(GetAllRequest, result.Exception);
                return(CreateActionResultFromException(result.Exception));
            }

            // Return multiple results
            if (queryParameters.TotalCount)
            {
                Response.Headers.Add(HeaderConstants.TotalCount, result.ResultMetadata.TotalCount.ToString());
            }

            Response.GetTypedHeaders().ContentType = new MediaTypeHeaderValue(GetReadContentType());

            return(Ok(result.Resources));
        }
        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)));
        }
Ejemplo n.º 8
0
        public virtual async Task <IActionResult> Post([FromBody] TPostRequest request)
        {
            var validationState = new ValidationState();

            // Make sure Id is not already set (no client-assigned Ids)
            if (request.Id != default(Guid))
            {
                return(BadRequest(ErrorTranslator.GetErrorMessage("Resource identifiers cannot be assigned by the client.")));
            }

            // Read the If-Match header and populate the resource DTO with an etag value.
            string etag;

            bool enforceOptimisticLock = Request.TryGetRequestHeader(HeaderConstants.IfMatch, out etag);

            request.ETag = Unquoted(etag);

            var result = await _retryPolicy.ExecuteAsync(
                (ctx) => PutPipeline.Value.ProcessAsync(
                    new PutContext <TResourceWriteModel, TAggregateRoot>(request, validationState),
                    CancellationToken.None),
                contextData : new Dictionary <string, object>()
            {
                { "Logger", new Lazy <ILog>(() => Logger) }
            });

            // Throw an exceptions that occurred for global exception handling
            if (result.Exception != null)
            {
                Logger.Error("Post", result.Exception);
                return(CreateActionResultFromException(result.Exception, enforceOptimisticLock));
            }

            var resourceUri = new Uri($"{GetResourceUrl()}/{result.ResourceId.GetValueOrDefault():n}");

            Response.GetTypedHeaders().Location = resourceUri;
            Response.GetTypedHeaders().ETag     = GetEtag(result.ETag);

            return(result.ResourceWasCreated
                ? (IActionResult)Created(resourceUri, null)
                : Ok());
        }
Ejemplo n.º 9
0
        private async Task <IActionResult> GetTokenInformation(TokenInfoRequest tokenInfoRequest)
        {
            if (!_isEnabled)
            {
                return(NotFound());
            }

            // see https://tools.ietf.org/html/rfc7662#section-2.2 for oauth token_info spec
            if (tokenInfoRequest == null || tokenInfoRequest.Token == null ||
                !Guid.TryParse(tokenInfoRequest.Token, out Guid accessToken))
            {
                return(BadRequest(ErrorTranslator.GetErrorMessage("Invalid token")));
            }

            var oAuthTokenClient = (await _tokenClientRepo.GetClientForTokenAsync(accessToken)).FirstOrDefault();

            if (oAuthTokenClient == null)
            {
                return(NotFound());
            }

            ApiKeyContext apiContext = _apiKeyContextProvider.GetApiKeyContext();

            // must be able to see my specific items ie vendor a cannot look at vendor b
            if (oAuthTokenClient.Key != apiContext.ApiKey)
            {
                return(Unauthorized());
            }

            var tokenInfo = await _tokenInfoProvider.GetTokenInfoAsync(apiContext);

            Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue {
                NoCache = true
            };
            return(Ok(tokenInfo));
        }
        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);
            }
        }
Ejemplo n.º 11
0
        public void ConfigureServices(IServiceCollection services)
        {
            _logger.Debug("Building services collection");

            services.AddSingleton(ApiSettings);
            services.AddSingleton(Configuration);

            AssemblyLoaderHelper.LoadAssembliesFromExecutingFolder();

            var pluginInfos = LoadPlugins();

            services.AddSingleton(pluginInfos);

            // this allows the solution to resolve the claims principal. this is not best practice defined by the
            // netcore team, as the claims principal is on the controllers.
            // c.f. https://docs.microsoft.com/en-us/aspnet/core/migration/claimsprincipal-current?view=aspnetcore-3.1
            services.AddHttpContextAccessor();

            // this is opening up all sites to connect to the server. this should probably be reviewed.
            services.AddCors(
                options =>
            {
                options.AddPolicy(
                    CorsPolicyName,
                    builder => builder
                    .AllowAnyOrigin()
                    .AllowAnyMethod()
                    .AllowAnyHeader()
                    .WithExposedHeaders("*"));
            });

            // will apply the MvcConfigurator at runtime.
            var mvcBuilder = services
                             .AddControllers(options => options.OutputFormatters.Add(new GraphMLMediaTypeOutputFormatter()))
                             .AddNewtonsoftJson(
                options =>
            {
                options.SerializerSettings.NullValueHandling    = NullValueHandling.Ignore;
                options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
                options.SerializerSettings.DateParseHandling    = DateParseHandling.None;
                options.SerializerSettings.Formatting           = Formatting.Indented;
                options.SerializerSettings.ContractResolver     = new CamelCasePropertyNamesContractResolver();
            });

            // Add controllers for the plugins
            foreach (var pluginInfo in pluginInfos)
            {
                var pluginAssembly = pluginInfo.Assembly;

                // This loads MVC application parts from plugin assemblies
                var partFactory = ApplicationPartFactory.GetApplicationPartFactory(pluginAssembly);

                foreach (var part in partFactory.GetApplicationParts(pluginAssembly))
                {
                    mvcBuilder.PartManager.ApplicationParts.Add(part);
                }
            }

            mvcBuilder.AddControllersAsServices();

            services.AddMvc()
            .ConfigureApiBehaviorOptions(
                options =>
            {
                options.InvalidModelStateResponseFactory = actionContext
                                                           => new BadRequestObjectResult(ErrorTranslator.GetErrorMessage(actionContext.ModelState));
            });

            services.AddAuthentication(EdFiAuthenticationTypes.OAuth)
            .AddScheme <AuthenticationSchemeOptions, EdFiOAuthAuthenticationHandler>(EdFiAuthenticationTypes.OAuth, null);

            services.AddApplicationInsightsTelemetry(
                options => { options.ApplicationVersion = ApiVersionConstants.Version; });

            if (ApiSettings.IsFeatureEnabled(ApiFeature.IdentityManagement.GetConfigKeyName()))
            {
                services.AddAuthorization(
                    options =>
                {
                    options.AddPolicy("IdentityManagement",
                                      policy => policy.RequireAssertion(
                                          context => context.User
                                          .HasClaim(c => c.Type == $"{EdFiConventions.EdFiOdsResourceClaimBaseUri}/domains/identity")));
                });
            }
        }