Browse Source

Disable tracking and blocking for some calls.

pull/195/head
Sebastian Stehle 8 years ago
parent
commit
af1bf97038
  1. 2
      src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs
  2. 2
      src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs
  3. 1
      src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs
  4. 2
      src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs
  5. 1
      src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs
  6. 4
      src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs
  7. 10
      src/Squidex/Areas/IdentityServer/Config/LazyClientStore.cs
  8. 1
      src/Squidex/Config/Web/WebExtensions.cs
  9. 3
      src/Squidex/Config/Web/WebServices.cs
  10. 19
      src/Squidex/Pipeline/ApiCostsAttribute.cs
  11. 76
      src/Squidex/Pipeline/ApiCostsFilter.cs
  12. 36
      src/Squidex/Pipeline/AppApiFilter.cs
  13. 22
      src/Squidex/Pipeline/AppFeature.cs
  14. 45
      src/Squidex/Pipeline/AppTrackingMiddleware.cs
  15. 15
      src/Squidex/Pipeline/IAppTrackingWeightFeature.cs

2
src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs

@ -48,7 +48,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
[HttpGet] [HttpGet]
[Route("apps/{app}/clients/")] [Route("apps/{app}/clients/")]
[ProducesResponseType(typeof(ClientDto[]), 200)] [ProducesResponseType(typeof(ClientDto[]), 200)]
[ApiCosts(1)] [ApiCosts(0)]
public IActionResult GetClients(string app) public IActionResult GetClients(string app)
{ {
var response = App.Clients.Select(x => SimpleMapper.Map(x.Value, new ClientDto { Id = x.Key })).ToList(); var response = App.Clients.Select(x => SimpleMapper.Map(x.Value, new ClientDto { Id = x.Key })).ToList();

2
src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs

@ -49,7 +49,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
[HttpGet] [HttpGet]
[Route("apps/{app}/contributors/")] [Route("apps/{app}/contributors/")]
[ProducesResponseType(typeof(ContributorsDto), 200)] [ProducesResponseType(typeof(ContributorsDto), 200)]
[ApiCosts(1)] [ApiCosts(0)]
public IActionResult GetContributors(string app) public IActionResult GetContributors(string app)
{ {
var contributors = App.Contributors.Select(x => new ContributorDto { ContributorId = x.Key, Permission = x.Value }).ToArray(); var contributors = App.Contributors.Select(x => new ContributorDto { ContributorId = x.Key, Permission = x.Value }).ToArray();

1
src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs

@ -49,6 +49,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
[HttpGet] [HttpGet]
[Route("apps/{app}/languages/")] [Route("apps/{app}/languages/")]
[ProducesResponseType(typeof(AppLanguageDto[]), 200)] [ProducesResponseType(typeof(AppLanguageDto[]), 200)]
[ApiCosts(0)]
public IActionResult GetLanguages(string app) public IActionResult GetLanguages(string app)
{ {
var response = App.LanguagesConfig.OfType<LanguageConfig>().Select(x => var response = App.LanguagesConfig.OfType<LanguageConfig>().Select(x =>

2
src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs

@ -56,7 +56,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
[HttpGet] [HttpGet]
[Route("apps/")] [Route("apps/")]
[ProducesResponseType(typeof(AppDto[]), 200)] [ProducesResponseType(typeof(AppDto[]), 200)]
[ApiCosts(1)] [ApiCosts(0)]
public async Task<IActionResult> GetApps() public async Task<IActionResult> GetApps()
{ {
var subject = HttpContext.User.OpenIdSubject(); var subject = HttpContext.User.OpenIdSubject();

1
src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs

@ -60,6 +60,7 @@ namespace Squidex.Areas.Api.Controllers.Assets
[HttpGet] [HttpGet]
[Route("assets/{id}/")] [Route("assets/{id}/")]
[ProducesResponseType(200)] [ProducesResponseType(200)]
[ApiCosts(0.5)]
public async Task<IActionResult> GetAssetContent(string app, Guid id, [FromQuery] int version = -1, [FromQuery] int? width = null, [FromQuery] int? height = null, [FromQuery] string mode = null) public async Task<IActionResult> GetAssetContent(string app, Guid id, [FromQuery] int version = -1, [FromQuery] int? width = null, [FromQuery] int? height = null, [FromQuery] string mode = null)
{ {
var asset = await assetRepository.FindAssetAsync(id); var asset = await assetRepository.FindAssetAsync(id);

4
src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs

@ -53,7 +53,7 @@ namespace Squidex.Areas.Api.Controllers.Plans
[HttpGet] [HttpGet]
[Route("apps/{app}/plans/")] [Route("apps/{app}/plans/")]
[ProducesResponseType(typeof(AppPlansDto), 200)] [ProducesResponseType(typeof(AppPlansDto), 200)]
[ApiCosts(0.5)] [ApiCosts(0)]
public IActionResult GetPlans(string app) public IActionResult GetPlans(string app)
{ {
var planId = appPlansProvider.GetPlanForApp(App).Id; var planId = appPlansProvider.GetPlanForApp(App).Id;
@ -87,7 +87,7 @@ namespace Squidex.Areas.Api.Controllers.Plans
[Route("apps/{app}/plan/")] [Route("apps/{app}/plan/")]
[ProducesResponseType(typeof(PlanChangedDto), 200)] [ProducesResponseType(typeof(PlanChangedDto), 200)]
[ProducesResponseType(typeof(ErrorDto), 400)] [ProducesResponseType(typeof(ErrorDto), 400)]
[ApiCosts(0.5)] [ApiCosts(0)]
public async Task<IActionResult> ChangePlanAsync(string app, [FromBody] ChangePlanDto request) public async Task<IActionResult> ChangePlanAsync(string app, [FromBody] ChangePlanDto request)
{ {
var redirectUri = (string)null; var redirectUri = (string)null;

10
src/Squidex/Areas/IdentityServer/Config/LazyClientStore.cs

@ -22,15 +22,15 @@ namespace Squidex.Areas.IdentityServer.Config
{ {
public class LazyClientStore : IClientStore public class LazyClientStore : IClientStore
{ {
private readonly IAppProvider appState; private readonly IAppProvider appProvider;
private readonly Dictionary<string, Client> staticClients = new Dictionary<string, Client>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<string, Client> staticClients = new Dictionary<string, Client>(StringComparer.OrdinalIgnoreCase);
public LazyClientStore(IOptions<MyUrlsOptions> urlsOptions, IAppProvider appState) public LazyClientStore(IOptions<MyUrlsOptions> urlsOptions, IAppProvider appProvider)
{ {
Guard.NotNull(urlsOptions, nameof(urlsOptions)); Guard.NotNull(urlsOptions, nameof(urlsOptions));
Guard.NotNull(appState, nameof(appState)); Guard.NotNull(appProvider, nameof(appProvider));
this.appState = appState; this.appProvider = appProvider;
CreateStaticClients(urlsOptions); CreateStaticClients(urlsOptions);
} }
@ -51,7 +51,7 @@ namespace Squidex.Areas.IdentityServer.Config
return null; return null;
} }
var app = await appState.GetAppAsync(token[0]); var app = await appProvider.GetAppAsync(token[0]);
var appClient = app?.Clients.GetOrDefault(token[1]); var appClient = app?.Clients.GetOrDefault(token[1]);

1
src/Squidex/Config/Web/WebExtensions.cs

@ -17,7 +17,6 @@ namespace Squidex.Config.Web
public static IApplicationBuilder UseMyTracking(this IApplicationBuilder app) public static IApplicationBuilder UseMyTracking(this IApplicationBuilder app)
{ {
app.UseMiddleware<LogPerformanceMiddleware>(); app.UseMiddleware<LogPerformanceMiddleware>();
app.UseMiddleware<AppTrackingMiddleware>();
return app; return app;
} }

3
src/Squidex/Config/Web/WebServices.cs

@ -16,8 +16,9 @@ namespace Squidex.Config.Web
{ {
public static void AddMyMvc(this IServiceCollection services) public static void AddMyMvc(this IServiceCollection services)
{ {
services.AddSingletonAs<AppApiFilter>();
services.AddSingletonAs<FileCallbackResultExecutor>(); services.AddSingletonAs<FileCallbackResultExecutor>();
services.AddSingletonAs<AppApiFilter>();
services.AddSingletonAs<ApiCostsFilter>();
services.AddMvc().AddMySerializers(); services.AddMvc().AddMySerializers();
services.AddCors(); services.AddCors();

19
src/Squidex/Pipeline/ApiCostsAttribute.cs

@ -7,28 +7,19 @@
// ========================================================================== // ==========================================================================
using System; using System;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc;
namespace Squidex.Pipeline namespace Squidex.Pipeline
{ {
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class ApiCostsAttribute : ActionFilterAttribute public sealed class ApiCostsAttribute : ServiceFilterAttribute
{ {
private readonly double weight; public double Weight { get; }
private sealed class WeightFeature : IAppTrackingWeightFeature
{
public double Weight { get; set; }
}
public ApiCostsAttribute(double weight) public ApiCostsAttribute(double weight)
: base(typeof(ApiCostsFilter))
{ {
this.weight = weight; Weight = weight;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
context.HttpContext.Features.Set<IAppTrackingWeightFeature>(new WeightFeature { Weight = weight });
} }
} }
} }

76
src/Squidex/Pipeline/ApiCostsFilter.cs

@ -0,0 +1,76 @@
// ==========================================================================
// ApiCostsFilter.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Squidex.Domain.Apps.Read.Apps.Services;
using Squidex.Infrastructure.UsageTracking;
namespace Squidex.Pipeline
{
public sealed class ApiCostsFilter : IAsyncActionFilter, IFilterContainer
{
private readonly IAppPlansProvider appPlanProvider;
private readonly IUsageTracker usageTracker;
public ApiCostsFilter(IAppPlansProvider appPlanProvider, IUsageTracker usageTracker)
{
this.appPlanProvider = appPlanProvider;
this.usageTracker = usageTracker;
}
IFilterMetadata IFilterContainer.FilterDefinition { get; set; }
public ApiCostsAttribute FilterDefinition
{
get
{
return (ApiCostsAttribute)((IFilterContainer)this).FilterDefinition;
}
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var appFeature = context.HttpContext.Features.Get<IAppFeature>();
if (appFeature?.App != null && FilterDefinition.Weight > 0)
{
var stopWatch = Stopwatch.StartNew();
try
{
var plan = appPlanProvider.GetPlanForApp(appFeature.App);
var usage = await usageTracker.GetMonthlyCalls(appFeature.App.Id.ToString(), DateTime.Today);
if (plan.MaxApiCalls >= 0 && (usage * 1.1) > plan.MaxApiCalls)
{
context.Result = new StatusCodeResult(429);
return;
}
await next();
}
finally
{
stopWatch.Stop();
await usageTracker.TrackAsync(appFeature.App.Id.ToString(), FilterDefinition.Weight, stopWatch.ElapsedMilliseconds);
}
}
else
{
await next();
}
}
}
}

36
src/Squidex/Pipeline/AppApiFilter.cs

@ -6,27 +6,31 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Squidex.Domain.Apps.Read.Apps.Services; using Squidex.Domain.Apps.Read;
using Squidex.Infrastructure.UsageTracking; using Squidex.Domain.Apps.Read.Apps;
namespace Squidex.Pipeline namespace Squidex.Pipeline
{ {
public sealed class AppApiFilter : IAsyncActionFilter public sealed class AppApiFilter : IAsyncActionFilter
{ {
private readonly Domain.Apps.Read.IAppProvider appState; private readonly IAppProvider appProvider;
private readonly IAppPlansProvider appPlanProvider;
private readonly IUsageTracker usageTracker;
public AppApiFilter(Domain.Apps.Read.IAppProvider appState, IAppPlansProvider appPlanProvider, IUsageTracker usageTracker) private sealed class AppFeature : IAppFeature
{ {
this.appState = appState; public IAppEntity App { get; }
this.appPlanProvider = appPlanProvider;
this.usageTracker = usageTracker; public AppFeature(IAppEntity app)
{
App = app;
}
}
public AppApiFilter(IAppProvider appProvider)
{
this.appProvider = appProvider;
} }
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
@ -35,7 +39,7 @@ namespace Squidex.Pipeline
if (!string.IsNullOrWhiteSpace(appName)) if (!string.IsNullOrWhiteSpace(appName))
{ {
var app = await appState.GetAppAsync(appName); var app = await appProvider.GetAppAsync(appName);
if (app == null) if (app == null)
{ {
@ -43,16 +47,6 @@ namespace Squidex.Pipeline
return; return;
} }
var plan = appPlanProvider.GetPlanForApp(app);
var usage = await usageTracker.GetMonthlyCalls(app.Id.ToString(), DateTime.Today);
if (plan.MaxApiCalls >= 0 && (usage * 1.1) > plan.MaxApiCalls)
{
context.Result = new StatusCodeResult(429);
return;
}
context.HttpContext.Features.Set<IAppFeature>(new AppFeature(app)); context.HttpContext.Features.Set<IAppFeature>(new AppFeature(app));
} }

22
src/Squidex/Pipeline/AppFeature.cs

@ -1,22 +0,0 @@
// ==========================================================================
// AppFeature.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Squidex.Domain.Apps.Read.Apps;
namespace Squidex.Pipeline
{
public sealed class AppFeature : IAppFeature
{
public IAppEntity App { get; }
public AppFeature(IAppEntity app)
{
App = app;
}
}
}

45
src/Squidex/Pipeline/AppTrackingMiddleware.cs

@ -1,45 +0,0 @@
// ==========================================================================
// AppTrackingMiddleware.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Squidex.Infrastructure.UsageTracking;
namespace Squidex.Pipeline
{
public sealed class AppTrackingMiddleware
{
private readonly RequestDelegate next;
private readonly IUsageTracker usageTracker;
public AppTrackingMiddleware(RequestDelegate next, IUsageTracker usageTracker)
{
this.next = next;
this.usageTracker = usageTracker;
}
public async Task Invoke(HttpContext context)
{
var stopWatch = Stopwatch.StartNew();
await next(context);
var appFeature = context.Features.Get<IAppFeature>();
if (appFeature?.App != null)
{
stopWatch.Stop();
var weight = context.Features.Get<IAppTrackingWeightFeature>()?.Weight ?? 1;
await usageTracker.TrackAsync(appFeature.App.Id.ToString(), weight, stopWatch.ElapsedMilliseconds);
}
}
}
}

15
src/Squidex/Pipeline/IAppTrackingWeightFeature.cs

@ -1,15 +0,0 @@
// ==========================================================================
// IAppTrackingWeightFeature.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
namespace Squidex.Pipeline
{
public interface IAppTrackingWeightFeature
{
double Weight { get; }
}
}
Loading…
Cancel
Save