Skip to content

Commit

Permalink
Merge pull request #46 from marcwittke/release/4.1.0
Browse files Browse the repository at this point in the history
Release/4.1.0
  • Loading branch information
marcwittke authored Oct 28, 2018
2 parents cd6c63e + 450aa29 commit b8d2d1c
Show file tree
Hide file tree
Showing 16 changed files with 447 additions and 35 deletions.
7 changes: 7 additions & 0 deletions Backend.Fx.sln
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx.NetCore", "src\e
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx.InMemoryPersistence", "src\implementations\Backend.Fx.InMemoryPersistence\Backend.Fx.InMemoryPersistence.csproj", "{0B8F13CA-1347-4655-9D41-AED21B1AFAC4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.Log4NetLogging", "src\implementations\Backend.Fx.Log4NetLogging\Backend.Fx.Log4NetLogging.csproj", "{C27BA4CE-882B-405F-906E-4DFA6E9F1216}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -97,6 +99,10 @@ Global
{0B8F13CA-1347-4655-9D41-AED21B1AFAC4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0B8F13CA-1347-4655-9D41-AED21B1AFAC4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0B8F13CA-1347-4655-9D41-AED21B1AFAC4}.Release|Any CPU.Build.0 = Release|Any CPU
{C27BA4CE-882B-405F-906E-4DFA6E9F1216}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C27BA4CE-882B-405F-906E-4DFA6E9F1216}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C27BA4CE-882B-405F-906E-4DFA6E9F1216}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C27BA4CE-882B-405F-906E-4DFA6E9F1216}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -117,6 +123,7 @@ Global
{D98AED23-ABB8-4130-9612-54AEFE9D2272} = {C7885592-A4B8-4BA8-8D3A-1EDA4025D17A}
{45EC5987-1C85-4940-8E5E-3B4F0FA90AF8} = {56ACAE69-F7F0-4FF2-BEE6-4B079481CF9A}
{0B8F13CA-1347-4655-9D41-AED21B1AFAC4} = {739A7296-579F-4D9A-BC73-DCECD260D7A0}
{C27BA4CE-882B-405F-906E-4DFA6E9F1216} = {739A7296-579F-4D9A-BC73-DCECD260D7A0}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {45648557-C751-44AD-9C87-0F12EB673969}
Expand Down
6 changes: 3 additions & 3 deletions src/abstractions/Backend.Fx/BuildingBlocks/Repository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public void Delete([NotNull] TAggregateRoot aggregateRoot)
Logger.Debug($"Deleting {AggregateTypeName}[{aggregateRoot.Id}]");
if (aggregateRoot.TenantId != _tenantIdHolder.Current.Value || !_aggregateAuthorization.CanDelete(aggregateRoot))
{
throw new UnauthorizedException($"You are not allowed to delete {typeof(TAggregateRoot).Name}[{aggregateRoot.Id}]");
throw new ForbiddenException($"You are not allowed to delete {typeof(TAggregateRoot).Name}[{aggregateRoot.Id}]");
}

DeletePersistent(aggregateRoot);
Expand All @@ -96,7 +96,7 @@ public void Add([NotNull] TAggregateRoot aggregateRoot)
}
else
{
throw new UnauthorizedException($"You are not allowed to create records of type {typeof(TAggregateRoot).Name}");
throw new ForbiddenException($"You are not allowed to create records of type {typeof(TAggregateRoot).Name}");
}
}

Expand All @@ -111,7 +111,7 @@ public void AddRange([NotNull] TAggregateRoot[] aggregateRoots)
{
if (!_aggregateAuthorization.CanCreate(agg))
{
throw new UnauthorizedException($"You are not allowed to create records of type {typeof(TAggregateRoot).Name}");
throw new ForbiddenException($"You are not allowed to create records of type {typeof(TAggregateRoot).Name}");
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
public class ConflictedException : ClientException
{
public ConflictedException()
: base("Conflicted.")
: base("Conflicted")
{ }

public ConflictedException(params Error[] errors)
Expand Down
36 changes: 36 additions & 0 deletions src/abstractions/Backend.Fx/Exceptions/ForbiddenException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
namespace Backend.Fx.Exceptions
{
using System;

public class ForbiddenException : ClientException
{
public enum Code
{
AccessDenied,
NotAuthenticated,
AccountLocked,
InvalidLogonAttempt,
}

public ForbiddenException()
: base("Unauthorized")
{ }

public ForbiddenException(params Error[] errors)
: base("Unauthorized", errors)
{ }

public ForbiddenException(string message, params Error[] errors)
: base(message, errors)
{ }

public ForbiddenException(string message, Exception innerException, params Error[] errors)
: base(message, innerException, errors)
{ }

public static IExceptionBuilder UseBuilder()
{
return new ExceptionBuilder<ForbiddenException>();
}
}
}
29 changes: 29 additions & 0 deletions src/abstractions/Backend.Fx/Exceptions/TooManyRequestsException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;

namespace Backend.Fx.Exceptions
{
public class TooManyRequestsException : ClientException
{
public TooManyRequestsException(int retryAfter)
{
RetryAfter = retryAfter;
}

public TooManyRequestsException(int retryAfter, params Error[] errors) : base(errors)
{
RetryAfter = retryAfter;
}

public TooManyRequestsException(int retryAfter, string message, params Error[] errors) : base(message, errors)
{
RetryAfter = retryAfter;
}

public TooManyRequestsException(int retryAfter, string message, Exception innerException, params Error[] errors) : base(message, innerException, errors)
{
RetryAfter = retryAfter;
}

public int RetryAfter { get; }
}
}
35 changes: 11 additions & 24 deletions src/abstractions/Backend.Fx/Exceptions/UnauthorizedException.cs
Original file line number Diff line number Diff line change
@@ -1,36 +1,23 @@
namespace Backend.Fx.Exceptions
{
using System;
using System;

namespace Backend.Fx.Exceptions
{
public class UnauthorizedException : ClientException
{
public enum Code
{
AccessDenied,
NotAuthenticated,
AccountLocked,
InvalidLogonAttempt,
}

public UnauthorizedException()
: base("Unauthorized")
public UnauthorizedException()
: base("Unauthorized")
{ }

public UnauthorizedException(params Error[] errors)
: base("Unauthorized", errors)
public UnauthorizedException(params Error[] errors)
: base("Unauthorized", errors)
{ }

public UnauthorizedException(string message, params Error[] errors)
: base(message, errors)
public UnauthorizedException(string message, params Error[] errors)
: base(message, errors)
{ }

public UnauthorizedException(string message, Exception innerException, params Error[] errors)
: base(message, innerException, errors)
: base(message, innerException, errors)
{ }

public static IExceptionBuilder UseBuilder()
{
return new ExceptionBuilder<UnauthorizedException>();
}
}
}
}
2 changes: 1 addition & 1 deletion src/abstractions/Backend.Fx/Logging/IExceptionLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public void LogException(Exception exception)
{
if (exception is ClientException cex)
{
_logger.Warn(cex, cex.Message + Environment.NewLine + cex.Errors);
_logger.Warn(cex);
}
else
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using Backend.Fx.Exceptions;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;

namespace Backend.Fx.AspNetCore.Mvc.Throttling
{
public class ExceptionThrottlingAttribute : ThrottlingBaseAttribute
{
public override void OnActionExecuted(ActionExecutedContext actionContext)
{
var cache = actionContext.HttpContext.RequestServices.GetRequiredService<IMemoryCache>();
var key = string.Concat(Name, "-", actionContext.HttpContext.Connection.RemoteIpAddress);

if (actionContext.Exception == null)
{
cache.Remove(key);
return;
}

if (cache.TryGetValue(key, out int repetition))
{
var retryAfter = Math.Max(1, CalculateRepeatedTimeoutFactor(repetition)) * Seconds;
cache.Set(key, ++repetition, TimeSpan.FromSeconds(retryAfter));
throw new TooManyRequestsException(retryAfter,"Request canceled due to throttling", new Error("Throttled", string.Format(Message, retryAfter)));
}

cache.Set(key, 1, TimeSpan.FromSeconds(Seconds));
}

protected override int CalculateRepeatedTimeoutFactor(int repetition)
{
return repetition * repetition;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using Backend.Fx.Exceptions;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;

namespace Backend.Fx.AspNetCore.Mvc.Throttling
{
public class ThrottlingAttribute : ThrottlingBaseAttribute
{
public override void OnActionExecuting(ActionExecutingContext actionContext)
{
var cache = actionContext.HttpContext.RequestServices.GetRequiredService<IMemoryCache>();
var key = string.Concat(Name, "-", actionContext.HttpContext.Connection.RemoteIpAddress);

if (cache.TryGetValue(key, out int repetition))
{
repetition++;
var retryAfter = Math.Max(1, CalculateRepeatedTimeoutFactor(repetition)) * Seconds;
cache.Set(key, repetition, TimeSpan.FromSeconds(retryAfter));
throw new TooManyRequestsException(retryAfter, "Request canceled due to throttling", new Error("Throttled", string.Format(Message, retryAfter)));
}

cache.Set(key, 1, TimeSpan.FromSeconds(Seconds));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using Microsoft.AspNetCore.Mvc.Filters;

namespace Backend.Fx.AspNetCore.Mvc.Throttling
{
public abstract class ThrottlingBaseAttribute : ActionFilterAttribute
{
/// <summary>
/// A unique name for this Throttle.
/// </summary>
/// <remarks>
/// We'll be inserting a Cache record based on this name and client IP, e.g. "Name-192.168.0.1"
/// </remarks>
public string Name { get; set; }

/// <summary>
/// The number of seconds clients must wait before executing this decorated route again.
/// </summary>
public int Seconds { get; set; }

/// <summary>
/// A text message that will be sent to the client upon throttling. You can include the token {0} to
/// show this.Seconds in the message, e.g. "Wait {0} seconds before trying again".
/// </summary>
public string Message { get; set; } = "Wait {0} seconds before trying again";

protected virtual int CalculateRepeatedTimeoutFactor(int repetition)
{
return 1;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace Backend.Fx.AspNetCore.ErrorHandling
using System.Globalization;

namespace Backend.Fx.AspNetCore.ErrorHandling
{
using System;
using System.Collections.Generic;
Expand Down Expand Up @@ -52,6 +54,15 @@ public async Task Invoke(HttpContext context)
{
await _next.Invoke(context);
}
catch (TooManyRequestsException tmrex)
{
if (tmrex.RetryAfter > 0)
{
context.Response.Headers.Add("Retry-After", tmrex.RetryAfter.ToString(CultureInfo.InvariantCulture));
}

await HandleClientError(context, 429, "TooManyRequests", tmrex);
}
catch (UnprocessableException uex)
{
await HandleClientError(context, 422, "Unprocessable", uex);
Expand All @@ -64,6 +75,10 @@ public async Task Invoke(HttpContext context)
{
await HandleClientError(context, (int)HttpStatusCode.Conflict, HttpStatusCode.Conflict.ToString(), confex);
}
catch (ForbiddenException uex)
{
await HandleClientError(context, (int)HttpStatusCode.Forbidden, HttpStatusCode.Forbidden.ToString(), uex);
}
catch (UnauthorizedException uex)
{
await HandleClientError(context, (int)HttpStatusCode.Unauthorized, HttpStatusCode.Unauthorized.ToString(), uex);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard1.5</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="log4net" Version="2.0.8" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\abstractions\Backend.Fx\Backend.Fx.csproj" />
</ItemGroup>

</Project>
Loading

0 comments on commit b8d2d1c

Please sign in to comment.