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.")))); }
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()); }
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))); }
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()); }
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); } }
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"))); }); } }