private async Task <IEnumerable <Dependency> > ReadDependenciesAsync()
        {
            Dependency[] dependencies;

            if (!DependenciesExists)
            {
                return(Enumerable.Empty <Dependency>());
            }

            using (var reader = new StreamReader(Filename))
            {
                var dependenciesJson = await reader.ReadToEndAsync().ConfigureAwait(false);

                dependencies = JsonConvert.DeserializeObject <Dependency[]>(dependenciesJson);
                reader.Close();
            }

            foreach (var dependency in dependencies)
            {
                var dependencySegments = dependency.Resource.Split('/');
                var resource           = dependencySegments[2];

                dependency.Namespace = dependencySegments[1].ToLower();
                dependency.Resource  = CompositeTermInflector.MakeSingular(resource).ToLower();
            }

            return(dependencies.OrderBy(o => o.Order).GroupBy(x => x.Resource)
                   .Select(GetFirstDependencySetPriorityToDependencyWithEdFiNamespace).AsEnumerable());
        }
示例#2
0
        public void WhenAGETByIdRequestIsSubmittedToTheCompositeWithAnInvalidResourceIdentifier(
            string compositeCategoryName,
            string compositeName,
            string resourceIdentifier)
        {
            // Default the category to test, if not specified
            compositeCategoryName = GetCompositeCategoryName(compositeCategoryName);

            var httpClient = FeatureContext.Current.Get<HttpClient>();

            var pluralizedCompositeName = CompositeTermInflector.MakePlural(compositeName);

            ScenarioContext.Current.Set(pluralizedCompositeName, ScenarioContextKeys.PluralizedCompositeName);

            var uri = OwinUriHelper.BuildCompositeUri(
                string.Format(
                    "{0}/{1}/{2}",
                    compositeCategoryName,
                    pluralizedCompositeName,
                    resourceIdentifier));

            var getResponseMessage = httpClient.GetAsync(uri)
                                               .GetResultSafely();

            // Save the response, and the resource collection name for the scenario
            ScenarioContext.Current.Set(getResponseMessage);
        }
            protected override void Arrange()
            {
                _swaggerDocumentContext =
                    new SwaggerDocumentContext(
                        ResourceModelProvider.GetResourceModel())
                {
                    ProfileContext = new SwaggerProfileContext
                    {
                        ProfileName = "Test-ParentNonAbstractBaseClass-ExcludeOnly"
                    },
                    RenderType = RenderType.GeneralizedExtensions
                };

                string profileDefinition = @"<Profile name='Test-ParentNonAbstractBaseClass-ExcludeOnly'>
                                                <Resource name='StudentSpecialEducationProgramAssociation'>
                                                  <ReadContentType memberSelection='ExcludeOnly'>
                                                    <Property name='SpecialEducationHoursPerWeek'/>
                                                  </ReadContentType>
                                                  <WriteContentType memberSelection='IncludeAll'/>
                                                </Resource>
                                                <Resource name='StudentProgramAssociation'>
                                                  <ReadContentType memberSelection='IncludeOnly'/>
                                                </Resource>
                                              </Profile>";

                var profileResourceModel =
                    new ProfileResourceModel(
                        ResourceModelProvider.GetResourceModel(),
                        XElement.Parse(profileDefinition));

                var readableResourceModel =
                    profileResourceModel.ResourceByName.Values.Where(r => r.Readable != null);

                var writableResourceModel =
                    profileResourceModel.ResourceByName.Values.Where(r => r.Writable != null);

                _swaggerResources = readableResourceModel
                                    .Select(
                    r => new SwaggerResource(r.Readable)
                {
                    Name =
                        $"{CompositeTermInflector.MakeSingular(r.Readable.Name)}_{ContentTypeUsage.Readable}"
                        .ToCamelCase(),
                    Readable = true, IsProfileResource = true
                })
                                    .Concat(
                    writableResourceModel.Select(
                        r => new SwaggerResource(r.Writable)
                {
                    Name =
                        $"{CompositeTermInflector.MakeSingular(r.Writable.Name)}_{ContentTypeUsage.Writable}"
                        .ToCamelCase(),
                    Writable = true, IsProfileResource = true
                }
                        ))
                                    .ToList();
            }
示例#4
0
        public async Task <HttpResponseMessage> GetAll(string elementName, int offset = 0)
        {
            var contentType = BuildJsonMimeType(elementName);
            var resource    = CompositeTermInflector.MakePlural(elementName);

            var uriBuilder = new UriBuilder(Path.Combine(_configuration.Url, resource, $"?offset={offset}"));

            return(await Get(uriBuilder, contentType));
        }
        /// <summary>
        /// Applies the composite resource's root resource to the build result using the supplied builder context.
        /// </summary>
        /// <param name="processorContext"></param>
        /// <param name="builderContext">The builder context.</param>
        public void ApplyRootResource(CompositeDefinitionProcessorContext processorContext, CompositeResourceModelBuilderContext builderContext)
        {
            string compositeName = processorContext.CurrentElement.Ancestors("Composite")
                                   .First()
                                   .AttributeValue("name");

            string resourceName = CompositeTermInflector.MakeSingular(compositeName);

            builderContext.RootResource    = new Resource(resourceName); //resource.Entity.Name);
            builderContext.CurrentResource = builderContext.RootResource;
        }
            public void Should_include_queries_for_parent_entities_before_their_children_so_that_NHibernate_does_not_perform_lazy_loading()
            {
                var domainModel = _domainModelProvider.GetDomainModel();

                var queryIndexByChildEntityName = _actualHqlQueries

                                                  // Skip the aggregate root query
                                                  .Skip(1)

                                                  // Use Regex to match the child entity collection names from the HQL
                                                  .Select(
                    (hql, i) =>
                    new
                {
                    Index   = i,
                    Matches = Regex.Matches(
                        hql, "left join fetch [a-z]{1,2}\\.(?<EntityCollection>\\w+(?<!ReferenceData)) ")
                })

                                                  // Extract the child entity name from the HQL
                                                  .Select(
                    x =>
                    new
                {
                    Index      = x.Index,
                    EntityName = CompositeTermInflector.MakeSingular(
                        x.Matches[x.Matches.Count - 1]
                        .Groups["EntityCollection"]
                        .Value),
                }
                    )

                                                  // Create a map of query indices by child entity name
                                                  .ToDictionary(x => x.EntityName, x => x.Index);

                var aggregateFullName = new FullName(EdFiConventions.PhysicalSchemaName, "StudentEducationOrganizationAssociation");

                Aggregate aggregate              = domainModel.AggregateByName[aggregateFullName];
                var       aggregateRoot          = aggregate.AggregateRoot;
                var       aggregateChildEntities = aggregate.Members.Except(new[] { aggregateRoot });

                // Assign the aggregate root to the known index (asserted by previous test)
                queryIndexByChildEntityName[aggregateRoot.Name] = 0;

                // Ensure that all child entity queries appear AFTER their parent's query in the batch
                // This prevents N+1 behavior where NHibernate performs lazy loading on child records
                Assert.That(
                    aggregateChildEntities
                    .Where(childEntity => queryIndexByChildEntityName[childEntity.Name] < queryIndexByChildEntityName[childEntity.Parent.Name])
                    .Select(childEntity => childEntity.FullName),
                    Is.Empty,
                    "Queries for some child entities in the aggregate appeared before their parents in the batch. This will cause NHibernate to perform lazy loading (i.e. the ORM \"n+1\" issue), breaking the optimized query batch behavior.");
            }
示例#7
0
        public async Task <HttpResponseMessage> GetResourceByExample(string json, string elementName)
        {
            var contentType = BuildJsonMimeType(elementName);
            var resource    = CompositeTermInflector.MakePlural(elementName);

            var uriBuilder = new UriBuilder(Path.Combine(_configuration.Url, resource))
            {
                Query = Utilities.ConvertJsonToQueryString(json)
            };

            return(await Get(uriBuilder, contentType));
        }
示例#8
0
        public async Task <HttpResponseMessage> PostResource(string json, string elementName,
                                                             string elementSchemaName = "", bool refreshToken = false)
        {
            var contentType = BuildJsonMimeType(elementName);
            var content     = new StringContent(json, Encoding.UTF8, contentType);
            var resource    = CompositeTermInflector.MakePlural(elementName);

            if (_log.IsDebugEnabled)
            {
                _log.Debug($"json: {json}");
                _log.Debug($"elementName: {elementName}");
            }

            try
            {
                string uri = $"{_configuration.Url.TrimEnd('/')}/{GetResourcePath(resource, elementSchemaName)}";

                _log.Debug($"Posting to {uri}");

                var uriBuilder = new UriBuilder(uri);

                var request = new HttpRequestMessage(HttpMethod.Post, uriBuilder.Uri)
                {
                    Content = content
                };

                request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(contentType));
                request.Headers.Authorization = GetAuthHeaderValue(refreshToken);

                var response = await _httpClient.SendAsync(request).ConfigureAwait(false);

                return(response);
            }
            catch (WebException ex)
            {
                // Handling intermittent network issues
                _log.Error("Unexpected WebException on resource post", ex);
                return(CreateFakeErrorResponse(HttpStatusCode.ServiceUnavailable));
            }
            catch (TaskCanceledException ex)
            {
                // Handling web timeout
                _log.Error("Http Client timeout.", ex);
                return(CreateFakeErrorResponse(HttpStatusCode.RequestTimeout));
            }
            catch (Exception ex)
            {
                // Handling other issues
                _log.Error("Unexpected Exception on resource post", ex);
                return(CreateFakeErrorResponse(HttpStatusCode.SeeOther));
            }
        }
 public static string CreateContentType(
     string resourceCollectionName,
     string profileName,
     ContentTypeUsage usage)
 {
     // ReSharper disable once UseStringInterpolation
     return(string.Format(
                "application/vnd.ed-fi{0}.{1}.{2}.{3}+json",
                string.Empty,
                CompositeTermInflector.MakeSingular(resourceCollectionName).ToLower(),
                profileName.ToLower(),
                usage.ToString().ToLower()));
 }
示例#10
0
        public static bool CompareTypeNames(string resource, string key, string separator,
                                            IEnumerable <string> schemaNames = null)
        {
            var resourceName = CompositeTermInflector.MakeSingular(resource.Split('/').Last());

            if (schemaNames != null && schemaNames.Any())
            {
                return(schemaNames.Any(
                           schemaName => key.Equals(
                               schemaName.Trim() + separator + resourceName,
                               StringComparison.InvariantCultureIgnoreCase)));
            }

            return(key.Equals(resourceName, StringComparison.InvariantCultureIgnoreCase));
        }
        public string GetResourceName(Resource resource, IOpenApiMetadataResourceContext resourceContext)
        {
            var name = resourceContext.ContextualResource == null
                ? string.Format(
                @"{0}_{1}_{2}",
                resource.SchemaProperCaseName,
                CompositeTermInflector.MakeSingular(resource.Name),
                resourceContext.OperationNamingContext)
                       .ToCamelCase()
                : string.Format(
                @"{0}_{1}_{2}_{3}",
                resource.SchemaProperCaseName,
                CompositeTermInflector.MakeSingular(resource.Name),
                resourceContext.ContextualResource.Name,
                resourceContext.OperationNamingContext);

            return(OpenApiMetadataDocumentHelper.CamelCaseSegments(name));
        }
        public async Task <UpstreamEdFiApiResponse> Get(
            Type upstreamModelType,
            HttpRequestHeaders requestHeaders,
            IEnumerable <KeyValuePair <string, string> > queryParameters,
            short schoolYearFromRoute,
            Guid?id = null)
        {
            string resourceCollectionName =
                CompositeTermInflector.MakePlural(upstreamModelType.Name)
                .ToCamelCase();

            string requestUrl = GetRequestUrl(resourceCollectionName, queryParameters, schoolYearFromRoute, id);

            var passthroughRequestMessage = new HttpRequestMessage(HttpMethod.Get, requestUrl);

            passthroughRequestMessage.Headers.Authorization = AuthenticationHeaderValue.Parse(requestHeaders.Authorization.ToString());

            HttpResponseMessage response;

            try
            {
                response = await _httpClient.SendAsync(passthroughRequestMessage).ConfigureAwait(false);
            }
            catch (WebException ex)
            {
                return(new UpstreamEdFiApiResponse
                {
                    Status = (ex.Response as HttpWebResponse)?.StatusCode ?? HttpStatusCode.InternalServerError,
                    ResponseStream = ex.Response.GetResponseStream(),
                });
            }

            return(new UpstreamEdFiApiResponse
            {
                Status = response.StatusCode,
                ReasonPhrase = response.ReasonPhrase,
                ResponseHeaders = response.Headers.Where(IsPassthroughResponseHeader).ToArray(),
                ResponseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false),
            });
        }
        public async Task <UpstreamEdFiApiResponse> Put <TResourceWriteModel>(Guid id, HttpRequestHeaders requestHeaders, object v2RequestBody, short schoolYearFromRoute)
        {
            string resourceCollectionName =
                CompositeTermInflector.MakePlural(typeof(TResourceWriteModel).Name)
                .ToCamelCase();

            string resourceUrl = GetResourceUrl(resourceCollectionName, schoolYearFromRoute);

            var requestUri = new Uri($"{resourceUrl}/{id:N}");
            var jsonBytes  = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(v2RequestBody, _serializerSettings));

            var passthroughRequestMessage = new HttpRequestMessage(HttpMethod.Put, requestUri);

            passthroughRequestMessage.Content = new ByteArrayContent(jsonBytes);
            passthroughRequestMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
            passthroughRequestMessage.Headers.Authorization       = AuthenticationHeaderValue.Parse(requestHeaders.Authorization.ToString());

            HttpResponseMessage response;

            try
            {
                response = await _httpClient.SendAsync(passthroughRequestMessage).ConfigureAwait(false);
            }
            catch (WebException ex)
            {
                return(new UpstreamEdFiApiResponse
                {
                    Status = (ex.Response as HttpWebResponse)?.StatusCode ?? HttpStatusCode.InternalServerError,
                    ResponseStream = ex.Response.GetResponseStream(),
                });
            }

            return(new UpstreamEdFiApiResponse
            {
                Status = response.StatusCode,
                ReasonPhrase = response.ReasonPhrase,
                ResponseHeaders = response.Headers.Where(IsPassthroughResponseHeader).ToArray(),
                ResponseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false),
            });
        }
示例#14
0
        public string GetResourceName(OpenApiMetadataResource openApiMetadataResource, ContentTypeUsage contentTypeUsage)
        {
            var schemaPrefix =
                openApiMetadataResource.Resource.Entity.DomainModel.SchemaNameMapProvider
                .GetSchemaMapByPhysicalName(openApiMetadataResource.Resource.Entity.Schema)
                .ProperCaseName;

            var name = openApiMetadataResource.ContextualResource == null
                ? string.Format(
                "{0}_{1}_{2}",
                schemaPrefix,
                CompositeTermInflector.MakeSingular(openApiMetadataResource.Resource.Name),
                contentTypeUsage)
                : string.Format(
                "{0}_{1}_{2}_{3}",
                schemaPrefix,
                CompositeTermInflector.MakeSingular(openApiMetadataResource.Resource.Name),
                openApiMetadataResource.ContextualResource.Name,
                contentTypeUsage);

            return(OpenApiMetadataDocumentHelper.CamelCaseSegments(name));
        }
示例#15
0
        public async Task WhenAGETByIdRequestIsSubmittedToTheComposite(string compositeName)
        {
            // Default the category to test, if not specified
            var compositeCategoryName = "test";

            var httpClient = StepsHelper.GetHttpClient();

            var subjectId = _scenarioContext.Get <Guid>(ScenarioContextKeys.CompositeSubjectId);

            var pluralizedCompositeName = CompositeTermInflector.MakePlural(compositeName);

            string correlationId = Guid.NewGuid().ToString("n");

            SetCorrelationId(correlationId);

            string requestUrl = _edFiTestUriHelper.BuildCompositeUri(
                $"{compositeCategoryName}/{pluralizedCompositeName}/{subjectId:n}{StepsHelper.GetQueryString(correlationId)}");

            var response = await httpClient.GetAsync(requestUrl, _cancellationToken.Value);

            response.StatusCode.ShouldBe(HttpStatusCode.OK);
        }
            protected override void Arrange()
            {
                _openApiMetadataDocumentContext =
                    new OpenApiMetadataDocumentContext(ResourceModelProvider.GetResourceModel())
                    {
                        ProfileContext = new OpenApiMetadataProfileContext()
                    };

                string profileDefinition =
                    @"<Profile name='Test-ParentNonAbstractBaseClass-ExcludeOnly'> <Resource name='StudentSpecialEducationProgramAssociation'> <ReadContentType memberSelection='ExcludeOnly'> <Property name='SpecialEducationHoursPerWeek'/> </ReadContentType> <WriteContentType memberSelection='IncludeAll'/> </Resource> <Resource name='Staff'> <ReadContentType memberSelection='IncludeOnly'> <Extension name='GrandBend'  memberSelection='IncludeOnly' logicalSchema='GrandBend'> <Property name='Tenured'/> </Extension> </ReadContentType> <WriteContentType memberSelection='IncludeAll' /> </Resource> <Resource name='StudentProgramAssociation'> <ReadContentType memberSelection='IncludeOnly'/> </Resource> </Profile>";

                var profileResourceModel = new ProfileResourceModel(
                    ResourceModelProvider.GetResourceModel(), XElement.Parse(profileDefinition));

                var readableResourceModel = profileResourceModel.ResourceByName.Values.Where(r => r.Readable != null);
                var writableResourceModel = profileResourceModel.ResourceByName.Values.Where(r => r.Writable != null);
                var edFiSchemaPrefix = "edFi";

                _openApiMetadataResources = readableResourceModel
                    .Select(
                        r => new OpenApiMetadataResource(r.Readable)
                        {
                            Name =
                                $"{edFiSchemaPrefix}_{CompositeTermInflector.MakeSingular(r.Readable.Name)}_{ContentTypeUsage.Readable}"
                                    .ToCamelCase(),
                            Readable = true,
                            IsProfileResource = true
                        }).Concat(
                        writableResourceModel.Select(
                            r => new OpenApiMetadataResource(r.Writable)
                            {
                                Name =
                                    $"{edFiSchemaPrefix}_{CompositeTermInflector.MakeSingular(r.Writable.Name)}_{ContentTypeUsage.Writable}"
                                        .ToCamelCase(),
                                Writable = true,
                                IsProfileResource = true
                            })).ToList();
            }
        public async Task <UpstreamEdFiApiResponse> Delete <TResourceWriteModel>(Guid id, HttpRequestHeaders requestHeaders, short schoolYearFromRoute)
        {
            string resourceCollectionName =
                CompositeTermInflector.MakePlural(typeof(TResourceWriteModel).Name)
                .ToCamelCase();

            string resourceUrl = GetResourceUrl(resourceCollectionName, schoolYearFromRoute);

            var requestUri = new Uri($"{resourceUrl}/{id:N}");

            var passthroughRequestMessage = new HttpRequestMessage(HttpMethod.Delete, requestUri);

            passthroughRequestMessage.Headers.Authorization = AuthenticationHeaderValue.Parse(requestHeaders.Authorization.ToString());

            HttpResponseMessage response;

            try
            {
                response = await _httpClient.SendAsync(passthroughRequestMessage).ConfigureAwait(false);
            }
            catch (WebException ex)
            {
                return(new UpstreamEdFiApiResponse
                {
                    Status = (ex.Response as HttpWebResponse)?.StatusCode ?? HttpStatusCode.InternalServerError,
                    ResponseStream = ex.Response.GetResponseStream(),
                });
            }

            return(new UpstreamEdFiApiResponse
            {
                Status = response.StatusCode,
                ReasonPhrase = response.ReasonPhrase,
                ResponseHeaders = response.Headers.Where(IsPassthroughResponseHeader).ToArray(),
                ResponseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false),
            });
        }
示例#18
0
        // Generates a list of assigned profiles that can be used by the client for the sent resource
        private IEnumerable <string> GetApplicableContentTypes(
            IEnumerable <string> assignedProfiles,
            string resourceCollectionName,
            string resourceItemName,
            HttpMethod httpMethod)
        {
            ContentTypeUsage contentTypeUsage = httpMethod == HttpMethod.Get
                ? ContentTypeUsage.Readable
                : ContentTypeUsage.Writable;

            var assignedContentTypes = assignedProfiles
                                       .Select(x => ProfilesContentTypeHelper.CreateContentType(resourceCollectionName, x, contentTypeUsage));

            return(assignedContentTypes
                   .Intersect(
                       _profileResourceNamesProvider.GetProfileResourceNames()
                       .Where(x => x.ResourceName.EqualsIgnoreCase(resourceItemName))
                       .Select(
                           x => ProfilesContentTypeHelper.CreateContentType(
                               CompositeTermInflector.MakePlural(x.ResourceName),
                               x.ProfileName,
                               contentTypeUsage)))
                   .ToList());
        }
示例#19
0
        public void WhenAGETByExampleRequestIsSubmittedToTheCompositeUsingTheFollowingParameters(
            string requestPattern,
            string compositeCategoryName,
            string resourceName,
            Table parameters)
        {
            // Default the category to test, if not specified
            compositeCategoryName = GetCompositeCategoryName(compositeCategoryName);

            ScenarioContext.Current.Set(requestPattern, ScenarioContextKeys.RequestPattern);

            var valueByName = parameters.Rows.ToDictionary(
                r => r["Name"],
                r => (object) r["Value"],
                StringComparer.InvariantCultureIgnoreCase);

            ScenarioContext.Current.Set(valueByName, ScenarioContextKeys.RequestParameters);

            var pluralizedCompositeName = CompositeTermInflector.MakePlural(resourceName);
            ScenarioContext.Current.Set(pluralizedCompositeName, ScenarioContextKeys.PluralizedCompositeName);

            var uri = OwinUriHelper.BuildCompositeUri(
                string.Format(
                    "{0}/{1}?{2}",
                    compositeCategoryName,
                    pluralizedCompositeName,
                    string.Join("&", valueByName.Select(kvp => Uri.EscapeDataString(kvp.Key) + "=" + Uri.EscapeDataString(kvp.Value.ToString())))));

            var httpClient = FeatureContext.Current.Get<HttpClient>();

            var getResponseMessage = httpClient.GetAsync(uri)
                                               .GetResultSafely();

            // Save the response, and the resource collection name for the scenario
            ScenarioContext.Current.Set(getResponseMessage);
        }
        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")
            }));
        }
示例#21
0
        public Task <HttpResponseMessage> ExecuteAuthorizationFilterAsync(
            HttpActionContext actionContext,
            CancellationToken cancellationToken,
            Func <Task <HttpResponseMessage> > continuation)
        {
            // check if the http method sent needs to be checked by this filter
            if (!(actionContext.Request.Method == HttpMethod.Put ||
                  actionContext.Request.Method == HttpMethod.Post ||
                  actionContext.Request.Method == HttpMethod.Get))
            {
                return(continuation());
            }

            // Try to get the current API key context
            var assignedProfiles = new List <string>();
            var apiKeyContext    = _apiKeyContextProvider.GetApiKeyContext();

            // check that the ApiKeyContext is available so that we can get the assigned profiles
            if (apiKeyContext != null && apiKeyContext != ApiKeyContext.Empty)
            {
                assignedProfiles = _apiKeyContextProvider.GetApiKeyContext()
                                   .Profiles.ToList();
            }

            // declare profile content type variable because it can be retrieved from the
            // accept header or the content-type header
            string profileContentType = null;
            ProfileContentTypeDetails profileContentTypeDetails = null;

            // get singluar spelling of the controller name for comparison to the resource in the profiles
            string resourceCollectionName =
                actionContext.ControllerContext.ControllerDescriptor.ControllerName.TrimSuffix("Controller");

            string resourceItemName = CompositeTermInflector.MakeSingular(resourceCollectionName);

            // try to get the Profile Content type and parse if successful
            if (TryGetProfileContentType(actionContext, out profileContentType))
            {
                profileContentTypeDetails = profileContentType.GetContentTypeDetails();
            }

            // If the caller has not specified a profile specific content type and there are no assigned
            // profiles then allow the request to be processed (there is nothing left to check)
            if (string.IsNullOrEmpty(profileContentType) && !assignedProfiles.Any())
            {
                return(continuation());
            }

            // If the caller has not specified a profile specific content type but the targeted
            // resource is covered by an assigned profile then we must refuse the request
            if (string.IsNullOrEmpty(profileContentType) &&
                IsResourceCoveredByAssignedProfile(assignedProfiles, resourceItemName))
            {
                if (actionContext.Request.Method == HttpMethod.Get)
                {
                    return
                        (Task.FromResult(
                             ForbiddenHttpResponseMessage(
                                 actionContext,
                                 string.Format(
                                     "One of the following profile-specific content types is required when requesting this resource: '{0}'.",
                                     string.Join(
                                         "', '",
                                         assignedProfiles.Select(
                                             p => ProfilesContentTypeHelper.CreateContentType(
                                                 resourceCollectionName,
                                                 p,
                                                 ContentTypeUsage.Readable)))))));
                }

                // PUT or POST
                return
                    (Task.FromResult(
                         ForbiddenHttpResponseMessage(
                             actionContext,
                             string.Format(
                                 "Based on the assigned profiles, one of the following profile-specific content types is required when updating this resource: '{0}'.",
                                 string.Join(
                                     "', '",
                                     assignedProfiles.Select(
                                         p => ProfilesContentTypeHelper.CreateContentType(resourceCollectionName, p, ContentTypeUsage.Writable)))))));
            }

            // if there is no profile specific content at this point there are no more checks to make so proceed with request processing
            if (string.IsNullOrEmpty(profileContentType))
            {
                return(continuation());
            }

            // If the caller is "opting in" to a profile, and the targeted resource exists in the specified profile, proceed with request processing
            if (!assignedProfiles.Any() &&
                IsTheResourceInTheProfile(resourceItemName, profileContentTypeDetails.Profile))
            {
                return(continuation());
            }

            // If the caller is not assigned any profiles that cover the targeted resource, then proceed with request processing.
            if (!AnyAssignedProfilesCoverTheResource(assignedProfiles, resourceItemName))
            {
                return(continuation());
            }

            // Check if the resource is covered under an assigned profile that is not the profile content type sent
            if (!IsResourceCoveredByAnotherAssignedProfile(assignedProfiles, resourceItemName, profileContentTypeDetails.Profile))
            {
                // create the response based on the method
                if (actionContext.Request.Method == HttpMethod.Get)
                {
                    return(Task.FromResult(
                               ForbiddenHttpResponseMessage(
                                   actionContext,
                                   string.Format(
                                       "One of the following profile-specific content types is required when requesting this resource: '{0}'.",
                                       string.Join(
                                           "', '",
                                           GetApplicableContentTypes(
                                               assignedProfiles,
                                               resourceCollectionName,
                                               resourceItemName,
                                               actionContext.Request.Method))))));
                }

                if (actionContext.Request.Method == HttpMethod.Put || actionContext.Request.Method == HttpMethod.Post)
                {
                    return(Task.FromResult(
                               ForbiddenHttpResponseMessage(
                                   actionContext,
                                   string.Format(
                                       "Based on the assigned profiles, one of the following profile-specific content types is required when updating this resource: '{0}'.",
                                       string.Join(
                                           "', '",
                                           GetApplicableContentTypes(
                                               assignedProfiles,
                                               resourceCollectionName,
                                               resourceItemName,
                                               actionContext.Request.Method))))));
                }
            }

            return(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);
            }
        }
示例#23
0
        public void WhenAGetAlldRequestIsSubmittedToTheCompositeWithAFilteredQuery(
            string compositeCategoryName,
            string compositeName,
            string hasParameters)
        {
            var httpClient = FeatureContext.Current.Get<HttpClient>();

            var pluralizedCompositeName = CompositeTermInflector.MakePlural(compositeName);

            compositeCategoryName = GetCompositeCategoryName(compositeCategoryName);

            var queryParameterDictionary = new Dictionary<string, QueryParameterObject>();

            if (!string.IsNullOrEmpty(hasParameters))
            {
                queryParameterDictionary =
                    ScenarioContext.Current.Get<Dictionary<string, QueryParameterObject>>(ScenarioContextKeys.CompositeQueryParameterDictionary);
            }

            ScenarioContext.Current.Set(pluralizedCompositeName, ScenarioContextKeys.PluralizedCompositeName);

            string queryStringParameterText = string.Join(
                "&",

                // Add the criteria 
                queryParameterDictionary.Select(
                                             kvp =>
                                             {
                                                 string parameterName = kvp.Value.Name;

                                                 if (IsDescriptorParameter(kvp.Value.Name))
                                                 {
                                                     parameterName = ScenarioContext.Current.Get<string>(
                                                         ScenarioContextKeys.CompositeQueryStringDescriptorParameter);
                                                 }

                                                 return $"{parameterName}={Uri.EscapeDataString(kvp.Value.Value.ToString())}";
                                             })

                                         // Add the correlation Id
                                        .Concat(
                                             new[]
                                             {
                                                 SpecialQueryStringParameters.CorrelationId + "=" + EstablishRequestCorrelationIdForScenario()
                                             }));

            var uri = OwinUriHelper.BuildCompositeUri(
                string.Format(
                    "{0}/{1}{2}{3}",
                    compositeCategoryName,
                    pluralizedCompositeName,
                    queryStringParameterText.Length > 0
                        ? "?"
                        : string.Empty,
                    queryStringParameterText));

            var getResponseMessage = httpClient.GetAsync(uri)
                                               .GetResultSafely();

            // Save the response, and the resource collection name for the scenario
            ScenarioContext.Current.Set(getResponseMessage);
        }
示例#24
0
        public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
        {
            if (_duplicates.Any())
            {
                throw new HttpResponseException(
                          request.CreateErrorResponse(
                              HttpStatusCode.InternalServerError,
                              $@"Duplicate controllers have been detected. 
                           Due to ambiguity, no requests will be served until this is resolved.  
                           The following controllers have been detected as duplicates: {string.Join(", ", _duplicates)}"));
            }

            IHttpRouteData routeData = request.GetRouteData();

            if (routeData == null)
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }

            string controllerName = GetRouteVariable <string>(routeData, ControllerKey);

            // schema section of url ie /data/v3/{schema}/{resource} ex: /data/v3/ed-fi/schools
            string resourceSchema = GetRouteVariable <string>(routeData, ResourceOwnerKey);

            if (ShouldUseDefaultSelectController(controllerName, resourceSchema))
            {
                // If there's no controller or schema name, defer to the base class for direct route handling
                // Also if the controller is a composite controller we defer to base class.
                return(base.SelectController(request));
            }

            string resourceSchemaNamespace = string.Empty;

            if (controllerName.EqualsIgnoreCase("deletes"))
            {
                resourceSchemaNamespace = "EdFi.Ods.ChangeQueries.Controllers";
            }
            else
            {
                try
                {
                    string properCaseName = _schemaNameMapProvider
                                            .GetSchemaMapByUriSegment(resourceSchema)
                                            .ProperCaseName;

                    // for now the check is including the ignore case due to the way the schema name map provider is implemented
                    // this should be address on the changes in phase 4 where the map provider is using dictionaries.
                    resourceSchemaNamespace =
                        EdFiConventions.BuildNamespace(
                            Namespaces.Api.Controllers,
                            properCaseName,
                            controllerName,
                            !EdFiConventions.ProperCaseName.EqualsIgnoreCase(properCaseName));
                }
                // Quietly ignore any failure to find the schema name, allowing NotFound response logic below to run
                catch (KeyNotFoundException) {}
            }

            // Verify org section matches a known resource owner derived from any assembly implementing ISchemaNameMap
            if (string.IsNullOrEmpty(resourceSchemaNamespace))
            {
                throw new HttpResponseException(
                          request.CreateErrorResponse(
                              HttpStatusCode.NotFound,
                              "Resource schema value provided in uri does not match any known values."));
            }

            ProfileContentTypeDetails profileContentTypeDetails = null;

            // http method type determines where the profile specific content type will be sent.
            if (request.Method == HttpMethod.Get)
            {
                profileContentTypeDetails =
                    (from a in request.Headers.Accept
                     where a.MediaType.StartsWith("application/vnd.ed-fi.")
                     let details = a.MediaType.GetContentTypeDetails()
                                   select details)
                    .SingleOrDefault();
            }
            else if (request.Method == HttpMethod.Put || request.Method == HttpMethod.Post)
            {
                if (request.Content != null)
                {
                    var contentType = request.Content.Headers.ContentType;

                    // check that there was a content type on the request
                    if (contentType != null)
                    {
                        // check if the content type is a profile specific content type
                        if (contentType.MediaType.StartsWith("application/vnd.ed-fi."))
                        {
                            // parse the profile specific content type string into the object.
                            profileContentTypeDetails = contentType.MediaType.GetContentTypeDetails();

                            // Was a profile-specific content type for the controller/resource found and was it parseable?
                            // if it was not parsable but started with "application/vnd.ed-fi." then throw an error
                            if (profileContentTypeDetails == null)
                            {
                                throw new HttpResponseException(
                                          BadRequestHttpResponseMessage(
                                              request,
                                              "Content type not valid on this resource."));
                            }
                        }
                    }
                }
            }

            HttpControllerDescriptor controllerDescriptor;
            string key;
            bool   profileControllerNotFound = false;

            // Was a profile-specific content type for the controller/resource found?
            if (profileContentTypeDetails != null)
            {
                // Ensure that the content type resource matchs requested resource
                if (!profileContentTypeDetails.Resource
                    .EqualsIgnoreCase(CompositeTermInflector.MakeSingular(controllerName)))
                {
                    throw new HttpResponseException(
                              BadRequestHttpResponseMessage(
                                  request,
                                  "The resource is not accessible through the profile specified by the content type."));
                }

                // Ensure that if the method is get that the profile specific content type sent readable
                if (request.Method == HttpMethod.Get && profileContentTypeDetails.Usage != ContentTypeUsage.Readable)
                {
                    throw new HttpResponseException(
                              BadRequestHttpResponseMessage(
                                  request,
                                  "The resource is not accessible through the profile specified by the content type."));
                }

                // Ensure that if the http method is PUT or POST that the profile specific type sent was writable
                if ((request.Method == HttpMethod.Put || request.Method == HttpMethod.Post) &&
                    profileContentTypeDetails.Usage != ContentTypeUsage.Writable)
                {
                    throw new HttpResponseException(
                              BadRequestHttpResponseMessage(
                                  request,
                                  "The resource is not accessible through the profile specified by the content type."));
                }

                // Use the profile name as the namespace for controller matching
                string profileName = profileContentTypeDetails.Profile.Replace('-', '_'); // + "_" + profileContentTypeDetails.Usage;

                // Find a matching controller.
                // ex : EdFi.Ods.Api.Services.Controllers.AcademicHonorCategoryTypes.Academic_Week_Readable_Excludes_Optional_References.AcademicHonorCategoryTypes
                key = string.Format(
                    CultureInfo.InvariantCulture,
                    "{0}.{1}.{2}",
                    resourceSchemaNamespace,
                    profileName,
                    controllerName);

                if (_controllers.Value.TryGetValue(key, out controllerDescriptor))
                {
                    return(controllerDescriptor);
                }

                profileControllerNotFound = true;
            }

            // Find a matching controller.
            // ex : EdFi.Ods.Api.Services.Controllers.AcademicHonorCategoryTypes.AcademicHonorCategoryTypes
            key = string.Format(
                CultureInfo.InvariantCulture,
                "{0}.{1}",
                resourceSchemaNamespace,
                controllerName);

            if (_controllers.Value.TryGetValue(key, out controllerDescriptor))
            {
                // If there is a controller, just not with the content type specified, it's a bad request
                if (profileControllerNotFound)
                {
                    // If the profile does not exist in this installation of the api throw an error dependent on the http method
                    if (!ProfileExists(profileContentTypeDetails.Profile))
                    {
                        if (request.Method == HttpMethod.Get)
                        {
                            throw new HttpResponseException(NotAcceptableHttpResponseMessage(request));
                        }

                        if (request.Method == HttpMethod.Put || request.Method == HttpMethod.Post)
                        {
                            throw new HttpResponseException(UnsupportedMediaTypeHttpResponseMessage(request));
                        }
                    }

                    // The profile exists but the resource doesnt exist under it
                    throw new HttpResponseException(
                              BadRequestHttpResponseMessage(
                                  request,
                                  string.Format(
                                      "The '{0}' resource is not accessible through the '{1}' profile specified by the content type.",
                                      CompositeTermInflector.MakeSingular(controllerName),
                                      profileContentTypeDetails.Profile)));
                }

                return(controllerDescriptor);
            }

            throw new HttpResponseException(HttpStatusCode.NotFound);
        }
 private string GetBaseResourceName(Resource baseResource, Resource resource, ContentTypeUsage contentTypeUsage)
 => $"{baseResource.Name}_{CompositeTermInflector.MakeSingular(resource.Name)}_{contentTypeUsage}".ToCamelCase();
示例#26
0
        public void WhenAGETRequestIsSubmittedToTheCompositeWithParameters(
            string requestPattern,
            string compositeCategoryName,
            string compositeName,
            string excludeCorrelationText,
            Table parametersTable)
        {
            ScenarioContext.Current.Set(requestPattern, ScenarioContextKeys.RequestPattern);

            // Default the category to test, if not specified
            compositeCategoryName = GetCompositeCategoryName(compositeCategoryName);

            var httpClient = FeatureContext.Current.Get<HttpClient>();

            var subjectId = ScenarioContext.Current.Get<Guid>(ScenarioContextKeys.CompositeSubjectId);

            var pluralizedCompositeName = CompositeTermInflector.MakePlural(compositeName);

            ScenarioContext.Current.Set(pluralizedCompositeName, ScenarioContextKeys.PluralizedCompositeName);

            bool includeCorrelation = string.IsNullOrEmpty(excludeCorrelationText);

            string requestUrl = null;

            if (requestPattern.EqualsIgnoreCase("id"))
            {
                var dictionary = new Dictionary<string, object>();
                ScenarioContext.Current.Set(dictionary, ScenarioContextKeys.RequestParameters);

                requestUrl = OwinUriHelper.BuildCompositeUri(
                    string.Format(
                        "{0}/{1}/{2}{3}",
                        compositeCategoryName,
                        pluralizedCompositeName,
                        subjectId.ToString("n"),
                        GetQueryString(parametersTable, includeCorrelation)));
            }
            else if (requestPattern.EqualsIgnoreCase("key"))
            {
                var valueByName = parametersTable == null
                    ? ScenarioContext.Current.Get<Dictionary<string, object>>(ScenarioContextKeys.CompositeSubjectKey)
                    : parametersTable.Rows.ToDictionary(
                        r => r["Name"],
                        r => (object) r["Value"],
                        StringComparer.InvariantCultureIgnoreCase);

                ScenarioContext.Current.Set(valueByName, ScenarioContextKeys.RequestParameters);

                requestUrl = OwinUriHelper.BuildCompositeUri(
                    string.Format(
                        "{0}/{1}?{2}",
                        compositeCategoryName,
                        pluralizedCompositeName,
                        string.Join("&", valueByName.Select(kvp => kvp.Key + "=" + Uri.EscapeDataString(kvp.Value.ToString())))));
            }
            else
            {
                throw new NotSupportedException(
                    string.Format("Request pattern '{0}' is not yet explicity supported by the test step definition.", requestPattern));
            }

            if (!includeCorrelation)
            {
                ScenarioContext.Current.Remove(ScenarioContextKeys.RequestCorrelationId);
            }

            var getResponseMessage = httpClient.GetAsync(requestUrl)
                                               .GetResultSafely();

            // Save the response, and the resource collection name for the scenario
            ScenarioContext.Current.Set(getResponseMessage);
        }