Skip to content

Commit

Permalink
Release 1.6.0 (#54)
Browse files Browse the repository at this point in the history
* risk filter support

* release 1.5.9

* remaining tests

* 1.6.0 version
  • Loading branch information
andreloureiro88 committed Jan 13, 2022
1 parent d8627fe commit d010392
Show file tree
Hide file tree
Showing 13 changed files with 395 additions and 7 deletions.
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@

## master

## 1.6.0 (2022-01-13)

**Features:**

- [#54](https://github.com/castle/castle-dotnet/pull/54) Support for Risk / Filter actions, updated headers

## 1.5.0 (2020-09-30)

**Features:**

- [#43](https://github.com/castle/castle-dotnet/pull/43) Add `IpHeaders`, `TrustedProxies`, `TrustedProxyDepth` and `TrustProxyChain` configuration options


**Enhancement**

- [#48](https://github.com/castle/castle-dotnet/pull/48) Update the readme with new config options
Expand All @@ -34,22 +39,27 @@
## 1.2.0 (2019-03-28)

**Features:**

- [#25](https://github.com/castle/castle-dotnet/pull/25) Adds `CastleClient.ArchiveDevices`

**Enhancement:**

- [#26](https://github.com/castle/castle-dotnet/pull/26) Guard against missing client method arguments and API secret
- [#27](https://github.com/castle/castle-dotnet/pull/27) Return null for requests caught in exception guard

**Bug fixes:**

- [#28](https://github.com/castle/castle-dotnet/pull/28) Include request/response content in info logging

## 1.1.1 (2019-03-26)

**Bug fixes:**

- [#23](https://github.com/castle/castle-dotnet/pull/23) Include xml docs in all build configurations.

## 1.1.0 (2019-03-20)

**Features:**

- [#11](https://github.com/castle/castle-dotnet/pull/11) Allow optional `clientId` parameter in the call to GetDevices(), which will set each device's `IsCurrentDevice` property accordingly.
- [#16](https://github.com/castle/castle-dotnet/pull/16) Can get IP address from request headers when using `Castle.Context.FromHttpRequest()`, by providing the helper with a list of header names to look for.
49 changes: 49 additions & 0 deletions src/Castle.Sdk/Actions/Filter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System;
using System.Threading.Tasks;
using Castle.Config;
using Castle.Infrastructure;
using Castle.Infrastructure.Exceptions;
using Castle.Messages;
using Castle.Messages.Responses;

namespace Castle.Actions
{
internal static class Filter
{
public static async Task<RiskResponse> Execute(
Func<Task<RiskResponse>> send,
CastleConfiguration configuration,
IInternalLogger logger)
{
try
{
return await send();
}
catch (Exception e)
{
logger.Warn(() => "Failover, " + e);
return CreateFailoverResponse(configuration.FailOverStrategy, e);
}
}

private static RiskResponse CreateFailoverResponse(ActionType strategy, string reason)
{
if (strategy == ActionType.None)
{
throw new CastleExternalException("Attempted failover, but no strategy was set.");
}

return new RiskResponse()
{
Action = strategy,
Failover = true,
FailoverReason = reason
};
}

private static RiskResponse CreateFailoverResponse(ActionType strategy, Exception exception)
{
return CreateFailoverResponse(strategy, exception is CastleTimeoutException ? "timeout" : "server error");
}
}
}
49 changes: 49 additions & 0 deletions src/Castle.Sdk/Actions/Risk.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System;
using System.Threading.Tasks;
using Castle.Config;
using Castle.Infrastructure;
using Castle.Infrastructure.Exceptions;
using Castle.Messages;
using Castle.Messages.Responses;

namespace Castle.Actions
{
internal static class Risk
{
public static async Task<RiskResponse> Execute(
Func<Task<RiskResponse>> send,
CastleConfiguration configuration,
IInternalLogger logger)
{
try
{
return await send();
}
catch (Exception e)
{
logger.Warn(() => "Failover, " + e);
return CreateFailoverResponse(configuration.FailOverStrategy, e);
}
}

private static RiskResponse CreateFailoverResponse(ActionType strategy, string reason)
{
if (strategy == ActionType.None)
{
throw new CastleExternalException("Attempted failover, but no strategy was set.");
}

return new RiskResponse()
{
Action = strategy,
Failover = true,
FailoverReason = reason
};
}

private static RiskResponse CreateFailoverResponse(ActionType strategy, Exception exception)
{
return CreateFailoverResponse(strategy, exception is CastleTimeoutException ? "timeout" : "server error");
}
}
}
6 changes: 3 additions & 3 deletions src/Castle.Sdk/Castle.Sdk.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackageProjectUrl>https://github.com/castle/castle-dotnet</PackageProjectUrl>
<RootNamespace>Castle</RootNamespace>
<Version>1.5.0</Version>
<Version>1.6.0</Version>
<Authors>Castle</Authors>
<Product>Castle .NET SDK</Product>
<Description>Castle SDK for C# / .NET</Description>
Expand All @@ -24,8 +24,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="Sentry.PlatformAbstractions" Version="1.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Sentry" Version="3.12.3" />
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' Or '$(TargetFramework)' == 'netcoreapp3.1' ">
Expand Down
42 changes: 42 additions & 0 deletions src/Castle.Sdk/CastleClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,48 @@ public async Task Track(ActionRequest request)
await SendTrackRequest(jsonRequest);
}

public JObject BuildFilterRequest(ActionRequest request)
{
var prepared = (request ?? new ActionRequest()).PrepareApiCopy(_configuration.AllowList, _configuration.DenyList);
return JsonForCastle.FromObject(prepared);
}

public async Task<RiskResponse> SendFilterRequest(JObject request)
{
return await TryRequest(() => Actions.Filter.Execute(
() => _messageSender.Post<RiskResponse>("/v1/filter", request),
_configuration, _logger));
}

public async Task<RiskResponse> Filter(ActionRequest request)
{
var jsonRequest = BuildFilterRequest(request);

return await SendFilterRequest(jsonRequest);
}

public async Task<RiskResponse> Risk(ActionRequest request)
{
var jsonRequest = BuildRiskRequest(request);

return await SendRiskRequest(jsonRequest);
}


public JObject BuildRiskRequest(ActionRequest request)
{
var prepared = (request ?? new ActionRequest()).PrepareApiCopy(_configuration.AllowList, _configuration.DenyList);
return JsonForCastle.FromObject(prepared);
}

public async Task<RiskResponse> SendRiskRequest(JObject request)
{
return await TryRequest(() => Actions.Risk.Execute(
() => _messageSender.Post<RiskResponse>("/v1/risk", request),
_configuration, _logger));
}


/// <exception cref="ArgumentException">Thrown when <paramref name="userId"/> is null or empty</exception>>
public async Task<DeviceList> GetDevicesForUser(string userId, string clientId = null)
{
Expand Down
4 changes: 4 additions & 0 deletions src/Castle.Sdk/Events.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,9 @@ public static class Events
public const string ChallengeRequested = "$challenge.requested";
public const string ChallengeSucceeded = "$challenge.succeeded";
public const string ChallengeFailed = "$challenge.failed";

public const string ProfileUpdate = "$profile_update";

public const string PasswordResetRequest = "$password_reset_request";
}
}
1 change: 1 addition & 0 deletions src/Castle.Sdk/Headers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public static class Headers
"Upgrade-Insecure-Requests",
"User-Agent",
"X-Castle-Client-Id",
"X-Requested-With"
};
}
}
16 changes: 13 additions & 3 deletions src/Castle.Sdk/Messages/Requests/ActionRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,34 @@ public class ActionRequest
{
[JsonProperty]
internal DateTime SentAt { get; set; }

public DateTime? Timestamp { get; set; }

public string DeviceToken { get; set; }

public string RequestToken { get; set; }

public string Event { get; set; }

public string Type { get; set; }

public string Status { get; set; }
public string UserId { get; set; }

public IDictionary<string, string> UserTraits { get; set; } = new Dictionary<string, string>();

public IDictionary<string, string> Properties { get; set; } = new Dictionary<string, string>();
public IDictionary<string, object> Properties { get; set; }

public IDictionary<string, object> User { get; set; } = new Dictionary<string, object>();
public IDictionary<string, string> Product { get; set; }

public IDictionary<string, string> AuthenticationMethod { get; set; }

public RequestContext Context { get; set; } = new RequestContext();

internal ActionRequest PrepareApiCopy(string[] allowList, string[] denyList)
{
var copy = (ActionRequest) MemberwiseClone();
var copy = (ActionRequest)MemberwiseClone();
var scrubbed = HeaderScrubber.Scrub(Context.Headers, allowList, denyList);
copy.Context = Context.WithHeaders(scrubbed);

Expand Down
11 changes: 11 additions & 0 deletions src/Castle.Sdk/Messages/Responses/Policy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

namespace Castle.Messages.Responses
{
public class Policy
{
public string Id { get; set; }
public string RevisionId { get; set; }
public string Name { get; set; }
public ActionType Action { get; set; }
}
}
24 changes: 24 additions & 0 deletions src/Castle.Sdk/Messages/Responses/RiskResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using Newtonsoft.Json.Linq;

namespace Castle.Messages.Responses
{

public class RiskResponse
{
public DeviceItem Device { get; set; }
public float Risk { get; set; }

public Policy Policy { get; set; }
public JObject Signals { get; set; }

// FailOver
public ActionType Action { get; set; }

public bool Failover { get; set; }

public string FailoverReason { get; set; }

}

}
79 changes: 79 additions & 0 deletions src/Tests/Actions/When_sending_filter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using System;
using System.Threading.Tasks;
using Castle.Actions;
using Castle.Config;
using Castle.Infrastructure;
using Castle.Infrastructure.Exceptions;
using Castle.Messages;
using Castle.Messages.Responses;
using FluentAssertions;
using NSubstitute;
using Tests.SetUp;
using Xunit;

namespace Tests
{
public class When_sending_filter
{
[Theory, AutoFakeData]
public async Task Should_return_response_if_successful(
CastleConfiguration configuration, RiskResponse response)
{
Task<RiskResponse> Send() => Task.FromResult(response);

var logger = Substitute.For<IInternalLogger>();

var result = await Filter.Execute(Send, configuration, logger);

result.Should().Be(response);
}


[Theory, AutoFakeData]
public async Task Should_return_failover_response_if_timeout(
string requestUri,
CastleConfiguration configuration)
{
configuration.FailOverStrategy = ActionType.Allow;
var logger = Substitute.For<IInternalLogger>();

Task<RiskResponse> Send() => throw new CastleTimeoutException(requestUri, configuration.Timeout);

var result = await Filter.Execute(Send, configuration, logger);

result.Failover.Should().Be(true);
result.FailoverReason.Should().Be("timeout");
}

[Theory, AutoFakeData]
public async Task Should_log_failover_exception_as_warning(
Exception exception,
CastleConfiguration configuration)
{
configuration.FailOverStrategy = ActionType.Allow;
var logger = Substitute.For<IInternalLogger>();

Task<RiskResponse> Send() => throw exception;

await Filter.Execute(Send, configuration, logger);

logger.Received().Warn(Arg.Is<Func<string>>(x => x() == "Failover, " + exception));
}

[Theory, AutoFakeData]
public async Task Should_throw_exception_if_failing_over_with_no_strategy(
Exception exception,
CastleConfiguration configuration)
{
configuration.FailOverStrategy = ActionType.None;
var logger = Substitute.For<IInternalLogger>();

Task<RiskResponse> Send() => throw exception;

Func<Task> act = async () => await Filter.Execute(Send, configuration, logger);

await act.Should().ThrowAsync<CastleExternalException>();
}

}
}
Loading

0 comments on commit d010392

Please sign in to comment.