From 4811ec91aa9fe8e6cb004cdc96c4962a1ae21ca2 Mon Sep 17 00:00:00 2001 From: marysieek Date: Mon, 22 Mar 2021 13:26:36 +0100 Subject: [PATCH 01/15] Remove events --- README.md | 8 +++++--- src/Castle.Sdk/Castle.Sdk.xml | 5 ----- src/Castle.Sdk/Events.cs | 26 -------------------------- 3 files changed, 5 insertions(+), 34 deletions(-) delete mode 100644 src/Castle.Sdk/Events.cs diff --git a/README.md b/README.md index 101b608..1d667f2 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,8 @@ For events where you don't require a response. ```csharp castleClient.Track(new ActionRequest() { - Event = Castle.Events.LogoutSucceeded, + Event = "$logout", + Status = "$succeeded", UserId = user.Id, UserTraits = new Dictionary() { @@ -130,7 +131,8 @@ For events where you require a response. It is used in the same way as `Track`, ```csharp var verdict = await castleClient.Authenticate(new ActionRequest() { - Event = Castle.Events.LogoutSucceeded, + Event = "$logout", + Status = "$succeeded", UserId = user.Id, UserTraits = new Dictionary() { @@ -166,7 +168,7 @@ If no failover strategy is set (i.e. `None`), a `Castle.Infrastructure.Exception | Option | Description | --- | --- -| Event | The event generated by the user. It can be either an event from the SDK constants in `Castle.Events` or a custom one. +| Event | The event generated by the user. List of Recognized Events can be found in the [docs](https://docs.castle.io/api_reference/#list-of-recognized-events). | UserId | Your internal ID for the end user. | UserTraits | An optional, recommended, dictionary of user information, such as `email` and `registered_at`. | Properties | An optional dictionary of custom information. diff --git a/src/Castle.Sdk/Castle.Sdk.xml b/src/Castle.Sdk/Castle.Sdk.xml index 2077d72..d35bcc1 100644 --- a/src/Castle.Sdk/Castle.Sdk.xml +++ b/src/Castle.Sdk/Castle.Sdk.xml @@ -111,11 +111,6 @@ Configuration access from within the SDK - - - Castle tracking event constants - - Recommended request context headers diff --git a/src/Castle.Sdk/Events.cs b/src/Castle.Sdk/Events.cs deleted file mode 100644 index 82d53df..0000000 --- a/src/Castle.Sdk/Events.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace Castle -{ - /// - /// Castle tracking event constants - /// - public static class Events - { - public const string LoginSucceeded = "$login.succeeded"; - public const string LoginFailed = "$login.failed"; - public const string LogoutSucceeded = "$logout.succeeded"; - public const string ProfileUpdateSucceeded = "$profile_update.succeeded"; - public const string ProfileUpdateFailed = "$profile_update.failed"; - public const string RegistrationSucceeded = "$registration.succeeded"; - public const string RegistrationFailed = "$registration.failed"; - public const string PasswordResetSucceeded = "$password_reset.succeeded"; - public const string PasswordResetFailed = "$password_reset.failed"; - public const string PasswordResetRequestSucceeded = "$password_reset_request.succeeded"; - public const string PasswordResetRequestFailed = "$password_reset_request.failed"; - public const string IncidentMitigated = "$incident.mitigated"; - public const string ReviewEscalated = "$review.escalated"; - public const string ReviewResolved = "$review.resolved"; - public const string ChallengeRequested = "$challenge.requested"; - public const string ChallengeSucceeded = "$challenge.succeeded"; - public const string ChallengeFailed = "$challenge.failed"; - } -} From d76eb92323adc31e4f5468dfa6e8e907508879a2 Mon Sep 17 00:00:00 2001 From: marysieek Date: Mon, 22 Mar 2021 15:39:49 +0100 Subject: [PATCH 02/15] Update the code --- src/Castle.Sdk/Castle.Sdk.xml | 5 +++ src/Castle.Sdk/Events.cs | 26 ++++++++++++++ .../Messages/Requests/ActionRequest.cs | 32 ++++++++++++++--- .../Messages/Requests/RequestContext.cs | 15 ++------ .../Messages/Requests/RequestOptions.cs | 31 +++++++++++++++++ src/Castle.Sdk/{Context.cs => Options.cs} | 20 +++++------ src/Tests/Actions/When_preparing_request.cs | 12 +++---- .../When_serializing_special_properties.cs | 6 ++-- ... When_creating_request_object_for_Core.cs} | 34 +++++++++---------- ..._creating_request_object_for_Framework.cs} | 30 ++++++++-------- 10 files changed, 141 insertions(+), 70 deletions(-) create mode 100644 src/Castle.Sdk/Events.cs create mode 100644 src/Castle.Sdk/Messages/Requests/RequestOptions.cs rename src/Castle.Sdk/{Context.cs => Options.cs} (91%) rename src/Tests/Messages/{When_creating_request_context_for_Core.cs => When_creating_request_object_for_Core.cs} (87%) rename src/Tests/Messages/{When_creating_request_context_for_Framework.cs => When_creating_request_object_for_Framework.cs} (87%) diff --git a/src/Castle.Sdk/Castle.Sdk.xml b/src/Castle.Sdk/Castle.Sdk.xml index d35bcc1..2077d72 100644 --- a/src/Castle.Sdk/Castle.Sdk.xml +++ b/src/Castle.Sdk/Castle.Sdk.xml @@ -111,6 +111,11 @@ Configuration access from within the SDK + + + Castle tracking event constants + + Recommended request context headers diff --git a/src/Castle.Sdk/Events.cs b/src/Castle.Sdk/Events.cs new file mode 100644 index 0000000..82d53df --- /dev/null +++ b/src/Castle.Sdk/Events.cs @@ -0,0 +1,26 @@ +namespace Castle +{ + /// + /// Castle tracking event constants + /// + public static class Events + { + public const string LoginSucceeded = "$login.succeeded"; + public const string LoginFailed = "$login.failed"; + public const string LogoutSucceeded = "$logout.succeeded"; + public const string ProfileUpdateSucceeded = "$profile_update.succeeded"; + public const string ProfileUpdateFailed = "$profile_update.failed"; + public const string RegistrationSucceeded = "$registration.succeeded"; + public const string RegistrationFailed = "$registration.failed"; + public const string PasswordResetSucceeded = "$password_reset.succeeded"; + public const string PasswordResetFailed = "$password_reset.failed"; + public const string PasswordResetRequestSucceeded = "$password_reset_request.succeeded"; + public const string PasswordResetRequestFailed = "$password_reset_request.failed"; + public const string IncidentMitigated = "$incident.mitigated"; + public const string ReviewEscalated = "$review.escalated"; + public const string ReviewResolved = "$review.resolved"; + public const string ChallengeRequested = "$challenge.requested"; + public const string ChallengeSucceeded = "$challenge.succeeded"; + public const string ChallengeFailed = "$challenge.failed"; + } +} diff --git a/src/Castle.Sdk/Messages/Requests/ActionRequest.cs b/src/Castle.Sdk/Messages/Requests/ActionRequest.cs index 91f43f4..370d8d6 100644 --- a/src/Castle.Sdk/Messages/Requests/ActionRequest.cs +++ b/src/Castle.Sdk/Messages/Requests/ActionRequest.cs @@ -1,8 +1,11 @@ using System; using System.Collections.Generic; using Castle.Infrastructure; +using Castle.Infrastructure.Json; + using Newtonsoft.Json; + namespace Castle.Messages.Requests { public class ActionRequest @@ -16,24 +19,43 @@ public class ActionRequest public string Event { get; set; } + public string Status { get; set; } + + public string Email { get; set; } + public string UserId { get; set; } + [JsonConverter(typeof(EmptyStringToFalseConverter))] + public string Fingerprint { get; set; } + + public string Ip { get; set; } + + [JsonProperty(ItemConverterType = typeof(StringScrubConverter))] + public IDictionary Headers { get; set; } = new Dictionary(); + public IDictionary UserTraits { get; set; } = new Dictionary(); public IDictionary Properties { get; set; } = new Dictionary(); public RequestContext Context { get; set; } = new RequestContext(); + public RequestOptions Options { get; set; } = new RequestOptions(); + internal ActionRequest PrepareApiCopy(string[] allowList, string[] denyList) { var copy = (ActionRequest) MemberwiseClone(); - var scrubbed = HeaderScrubber.Scrub(Context.Headers, allowList, denyList); - copy.Context = Context.WithHeaders(scrubbed); - - copy.SentAt = DateTime.Now; + var scrubbed = HeaderScrubber.Scrub(Options.Headers, allowList, denyList); + var opts = Options.WithHeaders(scrubbed); + // Assign Fingerprint, IP and Headers from options // Newtonsoft.Json doesn't apply custom converter to null values, so this must be empty instead - copy.Context.ClientId = copy.Context.ClientId ?? ""; + copy.Fingerprint = opts.Fingerprint ?? ""; + copy.Ip = opts.Ip; + copy.Headers = opts.Headers; + + copy.Context = Context.WithLibrary(); + + copy.SentAt = DateTime.Now; return copy; } diff --git a/src/Castle.Sdk/Messages/Requests/RequestContext.cs b/src/Castle.Sdk/Messages/Requests/RequestContext.cs index 81fdcb8..255a4c0 100644 --- a/src/Castle.Sdk/Messages/Requests/RequestContext.cs +++ b/src/Castle.Sdk/Messages/Requests/RequestContext.cs @@ -6,25 +6,14 @@ namespace Castle.Messages.Requests { public class RequestContext { - [JsonConverter(typeof(EmptyStringToFalseConverter))] - public string ClientId { get; set; } - - public string Ip { get; set; } - - [JsonProperty(ItemConverterType = typeof(StringScrubConverter))] - public IDictionary Headers { get; set; } = new Dictionary(); - [JsonProperty] internal LibraryInfo Library { get; set; } = new LibraryInfo(); - internal RequestContext WithHeaders(IDictionary headers) + internal RequestContext WithLibrary() { return new RequestContext() { - ClientId = ClientId, - Ip = Ip, - Library = Library, - Headers = headers + Library = Library }; } } diff --git a/src/Castle.Sdk/Messages/Requests/RequestOptions.cs b/src/Castle.Sdk/Messages/Requests/RequestOptions.cs new file mode 100644 index 0000000..259df53 --- /dev/null +++ b/src/Castle.Sdk/Messages/Requests/RequestOptions.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using Castle.Infrastructure.Json; +using Newtonsoft.Json; + +namespace Castle.Messages.Requests +{ + public class RequestOptions + { + [JsonConverter(typeof(EmptyStringToFalseConverter))] + public string Fingerprint { get; set; } + + public string Ip { get; set; } + + [JsonProperty(ItemConverterType = typeof(StringScrubConverter))] + public IDictionary Headers { get; set; } = new Dictionary(); + + [JsonProperty] + internal LibraryInfo Library { get; set; } = new LibraryInfo(); + + internal RequestOptions WithHeaders(IDictionary headers) + { + return new RequestOptions() + { + Fingerprint = Fingerprint, + Ip = Ip, + Library = Library, + Headers = headers + }; + } + } +} \ No newline at end of file diff --git a/src/Castle.Sdk/Context.cs b/src/Castle.Sdk/Options.cs similarity index 91% rename from src/Castle.Sdk/Context.cs rename to src/Castle.Sdk/Options.cs index 6f8d153..01716db 100644 --- a/src/Castle.Sdk/Context.cs +++ b/src/Castle.Sdk/Options.cs @@ -10,10 +10,10 @@ namespace Castle { - public static class Context + public static class Options { #if NET461 || NET48 - public static RequestContext FromHttpRequest(System.Web.HttpRequestBase request, string[] ipHeaders = null) + public static RequestOptions FromHttpRequest(System.Web.HttpRequestBase request, string[] ipHeaders = null) { var headers = new Dictionary(); foreach (string key in request.Headers.Keys) @@ -21,22 +21,22 @@ public static RequestContext FromHttpRequest(System.Web.HttpRequestBase request, headers.Add(key, request.Headers[key]); } - var clientId = GetClientIdForFramework(request.Headers, name => request.Cookies[name]?.Value); + var fingerprint = GetFingerprintForFramework(request.Headers, name => request.Cookies[name]?.Value); var ip = GetIpForFramework(request.Headers, ipHeaders, () => request.UserHostAddress, () => CastleConfiguration.Configuration); - return new RequestContext() + return new RequestOptions() { - ClientId = clientId, + Fingerprint = fingerprint, Headers = headers, Ip = ip }; } #endif - internal static string GetClientIdForFramework(NameValueCollection headers, Func getCookieValue) + internal static string GetFingerprintForFramework(NameValueCollection headers, Func getCookieValue) { return headers.AllKeys.Contains("X-Castle-Client-ID", StringComparer.OrdinalIgnoreCase) ? headers["X-Castle-Client-ID"] @@ -118,11 +118,11 @@ string RemoveProxies(string[] ips) } #if NETSTANDARD2_0 || NETCOREAPP - public static RequestContext FromHttpRequest(Microsoft.AspNetCore.Http.HttpRequest request, string[] ipHeaders = null) + public static RequestOptions FromHttpRequest(Microsoft.AspNetCore.Http.HttpRequest request, string[] ipHeaders = null) { - return new RequestContext() + return new RequestOptions() { - ClientId = GetClientIdForCore(request.Headers, request.Cookies), + Fingerprint = GetFingerprintForCore(request.Headers, request.Cookies), Headers = request.Headers.ToDictionary(x => x.Key, y => y.Value.FirstOrDefault()), Ip = GetIpForCore(request.Headers, ipHeaders, () => request.HttpContext.Connection.RemoteIpAddress?.ToString(), @@ -130,7 +130,7 @@ public static RequestContext FromHttpRequest(Microsoft.AspNetCore.Http.HttpReque }; } - internal static string GetClientIdForCore( + internal static string GetFingerprintForCore( IDictionary headers, Microsoft.AspNetCore.Http.IRequestCookieCollection cookies) { diff --git a/src/Tests/Actions/When_preparing_request.cs b/src/Tests/Actions/When_preparing_request.cs index cc0ec6a..aefce85 100644 --- a/src/Tests/Actions/When_preparing_request.cs +++ b/src/Tests/Actions/When_preparing_request.cs @@ -14,7 +14,7 @@ public void Should_scrub_headers(ActionRequest request, CastleConfiguration opti { var result = request.PrepareApiCopy(options.AllowList, options.DenyList); - result.Context.Headers.Should().NotBeSameAs(request.Context.Headers); + result.Headers.Should().NotBeSameAs(request.Headers); } [Theory, AutoFakeData] @@ -26,21 +26,21 @@ public void Should_set_sent_date(ActionRequest request, CastleConfiguration opti } [Theory, AutoFakeData] - public void Should_set_null_clientid_to_empty(ActionRequest request, CastleConfiguration options) + public void Should_set_null_fingerprint_to_empty(ActionRequest request, CastleConfiguration options) { - request.Context.ClientId = null; + request.Fingerprint = null; var result = request.PrepareApiCopy(options.AllowList, options.DenyList); - result.Context.ClientId.Should().Be(""); + result.Fingerprint.Should().Be(""); } [Theory, AutoFakeData] - public void Should_preserve_valid_clientid(ActionRequest request, CastleConfiguration options) + public void Should_preserve_valid_fingerprint(ActionRequest request, CastleConfiguration options) { var result = request.PrepareApiCopy(options.AllowList, options.DenyList); - result.Context.ClientId.Should().Be(request.Context.ClientId); + result.Fingerprint.Should().Be(request.Fingerprint); } } } diff --git a/src/Tests/Json/When_serializing_special_properties.cs b/src/Tests/Json/When_serializing_special_properties.cs index 583665e..9f9fafa 100644 --- a/src/Tests/Json/When_serializing_special_properties.cs +++ b/src/Tests/Json/When_serializing_special_properties.cs @@ -14,9 +14,9 @@ public class When_serializing_special_properties [Theory] [InlineData("non-empty", "\"client_id\":\"non-empty\"")] [InlineData("", "\"client_id\":false")] - public void Should_serialize_client_id_to_false_if_empty(string value, string expected) + public void Should_serialize_fingerprint_to_false_if_empty(string value, string expected) { - var obj = new RequestContext() { ClientId = value }; + var obj = new RequestOptions() { Fingerprint = value }; var result = JsonForCastle.SerializeObject(obj); @@ -51,7 +51,7 @@ public void Should_only_convert_strings_for_for_empty_string() string value, string expected) { - var obj = new RequestContext() + var obj = new RequestOptions() { Headers = new Dictionary() { diff --git a/src/Tests/Messages/When_creating_request_context_for_Core.cs b/src/Tests/Messages/When_creating_request_object_for_Core.cs similarity index 87% rename from src/Tests/Messages/When_creating_request_context_for_Core.cs rename to src/Tests/Messages/When_creating_request_object_for_Core.cs index caf64f7..d3281da 100644 --- a/src/Tests/Messages/When_creating_request_context_for_Core.cs +++ b/src/Tests/Messages/When_creating_request_object_for_Core.cs @@ -1,8 +1,6 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using Castle; using Castle.Config; -using Castle.Messages.Requests; using FluentAssertions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Internal; @@ -12,7 +10,7 @@ namespace Tests.Messages { - public class When_creating_request_context_for_Core + public class When_creating_request_object_for_Core { [Theory, AutoFakeData] public void Should_get_client_id_from_castle_header_if_present( @@ -29,7 +27,7 @@ public class When_creating_request_context_for_Core ["__cid"] = cookieValue }); - var result = Context.GetClientIdForCore(headers, cookies); + var result = Options.GetFingerprintForCore(headers, cookies); result.Should().Be(castleHeaderValue); } @@ -50,7 +48,7 @@ public class When_creating_request_context_for_Core ["__cid"] = cookieValue }); - var result = Context.GetClientIdForCore(headers, cookies); + var result = Options.GetFingerprintForCore(headers, cookies); result.Should().Be(cookieValue); } @@ -72,7 +70,7 @@ public class When_creating_request_context_for_Core [otherCookie] = otherCookieValue }); - var result = Context.GetClientIdForCore(headers, cookies); + var result = Options.GetFingerprintForCore(headers, cookies); result.Should().Be(""); } @@ -95,7 +93,7 @@ public class When_creating_request_context_for_Core [otherHeader] = otherHeaderValue }; - var result = Context.GetIpForCore(headers, new[] {ipHeader, secondaryIpHeader}, () => httpContextIp, () => cfg); + var result = Options.GetIpForCore(headers, new[] {ipHeader, secondaryIpHeader}, () => httpContextIp, () => cfg); result.Should().Be(ip); } @@ -116,7 +114,7 @@ public class When_creating_request_context_for_Core [otherHeader] = otherHeaderValue }; - var result = Context.GetIpForCore(headers, new[] { ipHeader, secondaryIpHeader }, () => httpContextIp, () => cfg); + var result = Options.GetIpForCore(headers, new[] { ipHeader, secondaryIpHeader }, () => httpContextIp, () => cfg); result.Should().Be(secondaryIp); } @@ -136,7 +134,7 @@ public class When_creating_request_context_for_Core [otherHeader] = otherHeaderValue }; - var result = Context.GetIpForCore(headers, null, () => httpContextIp, () => cfg); + var result = Options.GetIpForCore(headers, null, () => httpContextIp, () => cfg); result.Should().Be(httpContextIp); } @@ -153,7 +151,7 @@ string ip [ipHeader] = ip, }; - var result = Context.GetIpForCore(headers, null, () => ip, () => cfg); + var result = Options.GetIpForCore(headers, null, () => ip, () => cfg); result.Should().Be(ip); } @@ -169,7 +167,7 @@ public void Should_get_other_ip_header(CastleConfiguration cfg, string cfConnect var ipHeaders = new[] {"Cf-Connecting-Ip", "X-Forwarded-For"}; - var result = Context.GetIpForCore(headers, ipHeaders, () => cfConnectiongIp, () => cfg); + var result = Options.GetIpForCore(headers, ipHeaders, () => cfConnectiongIp, () => cfg); result.Should().Be(cfConnectiongIp); } @@ -183,7 +181,7 @@ public void Should_get_first_available_with_all_trusted_proxies(CastleConfigurat ["X-Forwarded-For"] = "127.0.0.1,10.0.0.1,172.31.0.1,192.168.0.1" }; - var result = Context.GetIpForCore(headers, null, () => defaultIp, () => cfg); + var result = Options.GetIpForCore(headers, null, () => defaultIp, () => cfg); result.Should().Be("127.0.0.1"); } @@ -198,7 +196,7 @@ public void Should_get_first_available_with_trust_proxy_chain(CastleConfiguratio cfg.TrustProxyChain = true; - var result = Context.GetIpForCore(headers, null, () => defaultIp, () => cfg); + var result = Options.GetIpForCore(headers, null, () => defaultIp, () => cfg); result.Should().Be("6.6.6.6"); } @@ -211,7 +209,7 @@ public void Should_get_remote_addr_if_others_internal(CastleConfiguration cfg, s ["X-Forwarded-For"] = "127.0.0.1,10.0.0.1,172.31.0.1,192.168.0.1" }; - var result = Context.GetIpForCore(headers, null, () => defaultIp, () => cfg); + var result = Options.GetIpForCore(headers, null, () => defaultIp, () => cfg); result.Should().Be("6.5.4.3"); } @@ -226,7 +224,7 @@ public void Should_get_equivalent_to_trusted_proxy_depth_1(CastleConfiguration c cfg.TrustedProxyDepth = 1; - var result = Context.GetIpForCore(headers, null, () => defaultIp, () => cfg); + var result = Options.GetIpForCore(headers, null, () => defaultIp, () => cfg); result.Should().Be("2.2.2.3"); } @@ -242,7 +240,7 @@ public void Should_get_equivalent_to_trusted_proxy_depth_2_ip_headers(CastleConf cfg.TrustedProxyDepth = 2; cfg.IpHeaders = new[] {"X-Forwarded-For", "Remote-Addr"}; - var result = Context.GetIpForCore(headers, null, () => defaultIp, () => cfg); + var result = Options.GetIpForCore(headers, null, () => defaultIp, () => cfg); result.Should().Be("2.2.2.3"); } @@ -251,7 +249,7 @@ public void Should_get_default_from_http_request(HttpRequest request, CastleConf { CastleConfiguration.SetConfiguration(cfg); - var result = Context.FromHttpRequest(request); + var result = Options.FromHttpRequest(request); result.Should().NotBe(null); } diff --git a/src/Tests/Messages/When_creating_request_context_for_Framework.cs b/src/Tests/Messages/When_creating_request_object_for_Framework.cs similarity index 87% rename from src/Tests/Messages/When_creating_request_context_for_Framework.cs rename to src/Tests/Messages/When_creating_request_object_for_Framework.cs index 51e9646..5619ac0 100644 --- a/src/Tests/Messages/When_creating_request_context_for_Framework.cs +++ b/src/Tests/Messages/When_creating_request_object_for_Framework.cs @@ -10,7 +10,7 @@ namespace Tests.Messages { - public class When_creating_request_context_for_Framework + public class When_creating_request_object_for_Framework { [Theory, AutoFakeData] public void Should_get_client_id_from_castle_header_if_present( @@ -24,7 +24,7 @@ public class When_creating_request_context_for_Framework string GetCookie(string name) => name == "__cid" ? cookieValue : null; - var result = Context.GetClientIdForFramework(headers, GetCookie); + var result = Options.GetFingerprintForFramework(headers, GetCookie); result.Should().Be(castleHeaderValue); } @@ -42,7 +42,7 @@ public class When_creating_request_context_for_Framework string GetCookie(string name) => name == "__cid" ? cookieValue : null; - var result = Context.GetClientIdForFramework(headers, GetCookie); + var result = Options.GetFingerprintForFramework(headers, GetCookie); result.Should().Be(cookieValue); } @@ -61,7 +61,7 @@ public class When_creating_request_context_for_Framework string GetCookie(string name) => name == otherCookie ? otherCookieValue : null; - var result = Context.GetClientIdForFramework(headers, GetCookie); + var result = Options.GetFingerprintForFramework(headers, GetCookie); result.Should().Be(""); } @@ -84,7 +84,7 @@ public class When_creating_request_context_for_Framework [otherHeader] = otherHeaderValue }; - var result = Context.GetIpForFramework(headers, new [] { ipHeader, secondaryIpHeader }, () => httpContextIp, () => cfg); + var result = Options.GetIpForFramework(headers, new [] { ipHeader, secondaryIpHeader }, () => httpContextIp, () => cfg); result.Should().Be(ip); } @@ -105,7 +105,7 @@ public class When_creating_request_context_for_Framework [otherHeader] = otherHeaderValue }; - var result = Context.GetIpForFramework(headers, new[] { ipHeader, secondaryIpHeader }, () => httpContextIp, () => cfg); + var result = Options.GetIpForFramework(headers, new[] { ipHeader, secondaryIpHeader }, () => httpContextIp, () => cfg); result.Should().Be(secondaryIp); } @@ -125,7 +125,7 @@ public class When_creating_request_context_for_Framework [otherHeader] = otherHeaderValue }; - var result = Context.GetIpForFramework(headers, null, () => httpContextIp, () => cfg); + var result = Options.GetIpForFramework(headers, null, () => httpContextIp, () => cfg); result.Should().Be(httpContextIp); } @@ -142,7 +142,7 @@ string ip [ipHeader] = ip, }; - var result = Context.GetIpForFramework(headers, null, () => ip, () => cfg); + var result = Options.GetIpForFramework(headers, null, () => ip, () => cfg); result.Should().Be(ip); } @@ -158,7 +158,7 @@ public void Should_get_other_ip_header(CastleConfiguration cfg, string cfConnect var ipHeaders = new[] {"Cf-Connecting-Ip", "X-Forwarded-For"}; - var result = Context.GetIpForFramework(headers, ipHeaders, () => cfConnectiongIp, () => cfg); + var result = Options.GetIpForFramework(headers, ipHeaders, () => cfConnectiongIp, () => cfg); result.Should().Be(cfConnectiongIp); } @@ -172,7 +172,7 @@ public void Should_get_first_available_with_all_trusted_proxies(CastleConfigurat ["X-Forwarded-For"] = "127.0.0.1,10.0.0.1,172.31.0.1,192.168.0.1" }; - var result = Context.GetIpForFramework(headers, null, () => defaultIp, () => cfg); + var result = Options.GetIpForFramework(headers, null, () => defaultIp, () => cfg); result.Should().Be("127.0.0.1"); } @@ -187,7 +187,7 @@ public void Should_get_first_available_with_trust_proxy_chain(CastleConfiguratio cfg.TrustProxyChain = true; - var result = Context.GetIpForFramework(headers, null, () => defaultIp, () => cfg); + var result = Options.GetIpForFramework(headers, null, () => defaultIp, () => cfg); result.Should().Be("6.6.6.6"); } @@ -200,7 +200,7 @@ public void Should_get_remote_addr_if_others_internal(CastleConfiguration cfg, s ["X-Forwarded-For"] = "127.0.0.1,10.0.0.1,172.31.0.1,192.168.0.1" }; - var result = Context.GetIpForFramework(headers, null, () => defaultIp, () => cfg); + var result = Options.GetIpForFramework(headers, null, () => defaultIp, () => cfg); result.Should().Be("6.5.4.3"); } @@ -215,7 +215,7 @@ public void Should_get_equivalent_to_trusted_proxy_depth_1(CastleConfiguration c cfg.TrustedProxyDepth = 1; - var result = Context.GetIpForFramework(headers, null, () => defaultIp, () => cfg); + var result = Options.GetIpForFramework(headers, null, () => defaultIp, () => cfg); result.Should().Be("2.2.2.3"); } @@ -231,7 +231,7 @@ public void Should_get_equivalent_to_trusted_proxy_depth_2_ip_headers(CastleConf cfg.TrustedProxyDepth = 2; cfg.IpHeaders = new[] {"X-Forwarded-For", "Remote-Addr"}; - var result = Context.GetIpForFramework(headers, null, () => defaultIp, () => cfg); + var result = Options.GetIpForFramework(headers, null, () => defaultIp, () => cfg); result.Should().Be("2.2.2.3"); } @@ -240,7 +240,7 @@ public void Should_get_default_from_http_request(HttpRequest request, CastleConf { CastleConfiguration.SetConfiguration(cfg); - var result = Context.FromHttpRequest(request); + var result = Options.FromHttpRequest(request); result.Should().NotBe(null); } From 12429ce3d9f3f40a9aa78fb10e3df047f7f068ac Mon Sep 17 00:00:00 2001 From: marysieek Date: Mon, 22 Mar 2021 15:44:01 +0100 Subject: [PATCH 03/15] Add special properties test --- src/Tests/Json/When_serializing_special_properties.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tests/Json/When_serializing_special_properties.cs b/src/Tests/Json/When_serializing_special_properties.cs index 9f9fafa..ab54e4e 100644 --- a/src/Tests/Json/When_serializing_special_properties.cs +++ b/src/Tests/Json/When_serializing_special_properties.cs @@ -12,8 +12,8 @@ public class When_serializing_special_properties { // Null values are skipped by Newtonsoft.Json, so we don't test those [Theory] - [InlineData("non-empty", "\"client_id\":\"non-empty\"")] - [InlineData("", "\"client_id\":false")] + [InlineData("non-empty", "\"fingerprint\":\"non-empty\"")] + [InlineData("", "\"fingerprint\":false")] public void Should_serialize_fingerprint_to_false_if_empty(string value, string expected) { var obj = new RequestOptions() { Fingerprint = value }; From 38151f555b7125bb3217d6292e47bd5dece0107c Mon Sep 17 00:00:00 2001 From: marysieek Date: Tue, 23 Mar 2021 11:41:49 +0100 Subject: [PATCH 04/15] Update tests --- src/Castle.Sdk/Messages/Requests/ActionRequest.cs | 3 ++- src/Tests/Actions/When_preparing_request.cs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Castle.Sdk/Messages/Requests/ActionRequest.cs b/src/Castle.Sdk/Messages/Requests/ActionRequest.cs index 370d8d6..4fdee11 100644 --- a/src/Castle.Sdk/Messages/Requests/ActionRequest.cs +++ b/src/Castle.Sdk/Messages/Requests/ActionRequest.cs @@ -49,7 +49,8 @@ internal ActionRequest PrepareApiCopy(string[] allowList, string[] denyList) // Assign Fingerprint, IP and Headers from options // Newtonsoft.Json doesn't apply custom converter to null values, so this must be empty instead - copy.Fingerprint = opts.Fingerprint ?? ""; + var newFingerprint = opts.Fingerprint ?? ""; + copy.Fingerprint = copy.Fingerprint ?? newFingerprint; copy.Ip = opts.Ip; copy.Headers = opts.Headers; diff --git a/src/Tests/Actions/When_preparing_request.cs b/src/Tests/Actions/When_preparing_request.cs index aefce85..bd01347 100644 --- a/src/Tests/Actions/When_preparing_request.cs +++ b/src/Tests/Actions/When_preparing_request.cs @@ -26,13 +26,13 @@ public void Should_set_sent_date(ActionRequest request, CastleConfiguration opti } [Theory, AutoFakeData] - public void Should_set_null_fingerprint_to_empty(ActionRequest request, CastleConfiguration options) + public void Should_set_null_fingerprint_to_default(ActionRequest request, CastleConfiguration options) { request.Fingerprint = null; var result = request.PrepareApiCopy(options.AllowList, options.DenyList); - result.Fingerprint.Should().Be(""); + result.Fingerprint.Should().NotBe(null); } [Theory, AutoFakeData] From e7f69a2207fe9c7d999cf98d27ea279c1a4dd1d3 Mon Sep 17 00:00:00 2001 From: marysieek Date: Tue, 23 Mar 2021 12:10:37 +0100 Subject: [PATCH 05/15] Update license --- LICENSE | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 02c8f7c..8f55b35 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2020 Castle Intelligence, Inc. +Copyright (c) 2021 Castle Intelligence, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. From 8413195e18bfefddf80d141c29207bae23cc1e95 Mon Sep 17 00:00:00 2001 From: marysieek Date: Tue, 23 Mar 2021 12:28:39 +0100 Subject: [PATCH 06/15] Use compound assignment --- src/Castle.Sdk/Messages/Requests/ActionRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Castle.Sdk/Messages/Requests/ActionRequest.cs b/src/Castle.Sdk/Messages/Requests/ActionRequest.cs index 4fdee11..258b156 100644 --- a/src/Castle.Sdk/Messages/Requests/ActionRequest.cs +++ b/src/Castle.Sdk/Messages/Requests/ActionRequest.cs @@ -50,7 +50,7 @@ internal ActionRequest PrepareApiCopy(string[] allowList, string[] denyList) // Assign Fingerprint, IP and Headers from options // Newtonsoft.Json doesn't apply custom converter to null values, so this must be empty instead var newFingerprint = opts.Fingerprint ?? ""; - copy.Fingerprint = copy.Fingerprint ?? newFingerprint; + copy.Fingerprint ??= newFingerprint; copy.Ip = opts.Ip; copy.Headers = opts.Headers; From a5f96ff2f1b1bbc64725a9ca8c19bf5a8b12f635 Mon Sep 17 00:00:00 2001 From: marysieek Date: Tue, 23 Mar 2021 12:41:35 +0100 Subject: [PATCH 07/15] Add private options --- src/Castle.Sdk/Messages/Requests/ActionRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Castle.Sdk/Messages/Requests/ActionRequest.cs b/src/Castle.Sdk/Messages/Requests/ActionRequest.cs index 258b156..6212983 100644 --- a/src/Castle.Sdk/Messages/Requests/ActionRequest.cs +++ b/src/Castle.Sdk/Messages/Requests/ActionRequest.cs @@ -39,7 +39,7 @@ public class ActionRequest public RequestContext Context { get; set; } = new RequestContext(); - public RequestOptions Options { get; set; } = new RequestOptions(); + private RequestOptions Options { get; set; } = new RequestOptions(); internal ActionRequest PrepareApiCopy(string[] allowList, string[] denyList) { From 4ecd3142adfebce7cb2803b41a6feb442987966e Mon Sep 17 00:00:00 2001 From: marysieek Date: Tue, 23 Mar 2021 12:42:51 +0100 Subject: [PATCH 08/15] Revert --- src/Castle.Sdk/Messages/Requests/ActionRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Castle.Sdk/Messages/Requests/ActionRequest.cs b/src/Castle.Sdk/Messages/Requests/ActionRequest.cs index 6212983..c4b0ba9 100644 --- a/src/Castle.Sdk/Messages/Requests/ActionRequest.cs +++ b/src/Castle.Sdk/Messages/Requests/ActionRequest.cs @@ -50,7 +50,7 @@ internal ActionRequest PrepareApiCopy(string[] allowList, string[] denyList) // Assign Fingerprint, IP and Headers from options // Newtonsoft.Json doesn't apply custom converter to null values, so this must be empty instead var newFingerprint = opts.Fingerprint ?? ""; - copy.Fingerprint ??= newFingerprint; + copy.Fingerprint = copy.Fingerprint ?? newFingerprint; copy.Ip = opts.Ip; copy.Headers = opts.Headers; From 26f852254b875f7f6fb2f015e824c418d2c856da Mon Sep 17 00:00:00 2001 From: marysieek Date: Tue, 23 Mar 2021 13:13:16 +0100 Subject: [PATCH 09/15] Do not send options --- src/Castle.Sdk/Messages/Requests/ActionRequest.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Castle.Sdk/Messages/Requests/ActionRequest.cs b/src/Castle.Sdk/Messages/Requests/ActionRequest.cs index c4b0ba9..b8558a2 100644 --- a/src/Castle.Sdk/Messages/Requests/ActionRequest.cs +++ b/src/Castle.Sdk/Messages/Requests/ActionRequest.cs @@ -47,6 +47,8 @@ internal ActionRequest PrepareApiCopy(string[] allowList, string[] denyList) var scrubbed = HeaderScrubber.Scrub(Options.Headers, allowList, denyList); var opts = Options.WithHeaders(scrubbed); + copy.Options = null; + // Assign Fingerprint, IP and Headers from options // Newtonsoft.Json doesn't apply custom converter to null values, so this must be empty instead var newFingerprint = opts.Fingerprint ?? ""; From ee8a800b94c5d89dc1f128c96744686d9aeeb216 Mon Sep 17 00:00:00 2001 From: marysieek Date: Tue, 23 Mar 2021 14:03:03 +0100 Subject: [PATCH 10/15] Update options and readme --- README.md | 17 +++++++---------- .../Messages/Requests/RequestOptions.cs | 4 ---- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 1d667f2..f2e02d4 100644 --- a/README.md +++ b/README.md @@ -139,12 +139,9 @@ var verdict = await castleClient.Authenticate(new ActionRequest() ["email"] = user.Email, ["registered_at"] = user.RegisteredAt }, - Context = new RequestContext() - { - Ip = Request.HttpContext.Connection.RemoteIpAddress.ToString(), - ClientId = Request.Cookies["__cid"], - Headers = Request.Headers.ToDictionary(x => x.Key, y => y.Value.FirstOrDefault()); - } + Ip = Request.HttpContext.Connection.RemoteIpAddress.ToString(), + Fingerprint = Request.Cookies["__cid"], + Headers = Request.Headers.ToDictionary(x => x.Key, y => y.Value.FirstOrDefault()) }); ``` @@ -176,7 +173,7 @@ If no failover strategy is set (i.e. `None`), a `Castle.Infrastructure.Exception | DeviceToken | The optional device token, used for mitigating or escalating. | Context | The request context information. See information below. -#### Request context +#### Request options | Option | Description | --- | --- @@ -184,7 +181,7 @@ If no failover strategy is set (i.e. `None`), a `Castle.Infrastructure.Exception | ClientId | The client ID, generated by the `c.js` integration on the front end. Commonly found in the `__cid` cookie in `Request.Cookies`, or in some cases the `X-CASTLE-CLIENT-ID` header. | Headers | Headers mapped from the the original request (most likely `Request.Headers`). -You can call `Castle.Context.FromHttpRequest(request)` to get a ready-made `RequestContext` instance from your current request. +You can call `Castle.Options.FromHttpRequest(request)` to get a ready-made `RequestOptions` instance from your current request. ##### ASP.NET MVC 5 ```csharp @@ -194,7 +191,7 @@ public class HomeController : Controller { var actionRequest = new ActionRequest() { - Context = Castle.Context.FromHttpRequest(Request) + Options = Castle.Options.FromHttpRequest(Request) ... ``` @@ -206,7 +203,7 @@ public class IndexModel : PageModel { var actionRequest = new ActionRequest() { - Context = Castle.Context.FromHttpRequest(Request) + Options = Castle.Options.FromHttpRequest(Request) ... ``` diff --git a/src/Castle.Sdk/Messages/Requests/RequestOptions.cs b/src/Castle.Sdk/Messages/Requests/RequestOptions.cs index 259df53..efd4b09 100644 --- a/src/Castle.Sdk/Messages/Requests/RequestOptions.cs +++ b/src/Castle.Sdk/Messages/Requests/RequestOptions.cs @@ -14,16 +14,12 @@ public class RequestOptions [JsonProperty(ItemConverterType = typeof(StringScrubConverter))] public IDictionary Headers { get; set; } = new Dictionary(); - [JsonProperty] - internal LibraryInfo Library { get; set; } = new LibraryInfo(); - internal RequestOptions WithHeaders(IDictionary headers) { return new RequestOptions() { Fingerprint = Fingerprint, Ip = Ip, - Library = Library, Headers = headers }; } From b1837825664898e48087ad0995e15a7327d6e7b5 Mon Sep 17 00:00:00 2001 From: marysieek Date: Tue, 23 Mar 2021 14:38:45 +0100 Subject: [PATCH 11/15] Add JsonIgnore --- src/Castle.Sdk/Messages/Requests/ActionRequest.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Castle.Sdk/Messages/Requests/ActionRequest.cs b/src/Castle.Sdk/Messages/Requests/ActionRequest.cs index b8558a2..3b80966 100644 --- a/src/Castle.Sdk/Messages/Requests/ActionRequest.cs +++ b/src/Castle.Sdk/Messages/Requests/ActionRequest.cs @@ -39,6 +39,7 @@ public class ActionRequest public RequestContext Context { get; set; } = new RequestContext(); + [JsonIgnore] private RequestOptions Options { get; set; } = new RequestOptions(); internal ActionRequest PrepareApiCopy(string[] allowList, string[] denyList) @@ -47,8 +48,6 @@ internal ActionRequest PrepareApiCopy(string[] allowList, string[] denyList) var scrubbed = HeaderScrubber.Scrub(Options.Headers, allowList, denyList); var opts = Options.WithHeaders(scrubbed); - copy.Options = null; - // Assign Fingerprint, IP and Headers from options // Newtonsoft.Json doesn't apply custom converter to null values, so this must be empty instead var newFingerprint = opts.Fingerprint ?? ""; From e12f0b1479503c3f0e90890bbac060701cfb8e11 Mon Sep 17 00:00:00 2001 From: marysieek Date: Tue, 23 Mar 2021 14:47:58 +0100 Subject: [PATCH 12/15] Fix --- src/Castle.Sdk/Castle.Sdk.xml | 5 ---- src/Castle.Sdk/Events.cs | 26 ------------------- .../Messages/Requests/ActionRequest.cs | 1 - 3 files changed, 32 deletions(-) delete mode 100644 src/Castle.Sdk/Events.cs diff --git a/src/Castle.Sdk/Castle.Sdk.xml b/src/Castle.Sdk/Castle.Sdk.xml index 2077d72..d35bcc1 100644 --- a/src/Castle.Sdk/Castle.Sdk.xml +++ b/src/Castle.Sdk/Castle.Sdk.xml @@ -111,11 +111,6 @@ Configuration access from within the SDK - - - Castle tracking event constants - - Recommended request context headers diff --git a/src/Castle.Sdk/Events.cs b/src/Castle.Sdk/Events.cs deleted file mode 100644 index 82d53df..0000000 --- a/src/Castle.Sdk/Events.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace Castle -{ - /// - /// Castle tracking event constants - /// - public static class Events - { - public const string LoginSucceeded = "$login.succeeded"; - public const string LoginFailed = "$login.failed"; - public const string LogoutSucceeded = "$logout.succeeded"; - public const string ProfileUpdateSucceeded = "$profile_update.succeeded"; - public const string ProfileUpdateFailed = "$profile_update.failed"; - public const string RegistrationSucceeded = "$registration.succeeded"; - public const string RegistrationFailed = "$registration.failed"; - public const string PasswordResetSucceeded = "$password_reset.succeeded"; - public const string PasswordResetFailed = "$password_reset.failed"; - public const string PasswordResetRequestSucceeded = "$password_reset_request.succeeded"; - public const string PasswordResetRequestFailed = "$password_reset_request.failed"; - public const string IncidentMitigated = "$incident.mitigated"; - public const string ReviewEscalated = "$review.escalated"; - public const string ReviewResolved = "$review.resolved"; - public const string ChallengeRequested = "$challenge.requested"; - public const string ChallengeSucceeded = "$challenge.succeeded"; - public const string ChallengeFailed = "$challenge.failed"; - } -} diff --git a/src/Castle.Sdk/Messages/Requests/ActionRequest.cs b/src/Castle.Sdk/Messages/Requests/ActionRequest.cs index 3b80966..c4b0ba9 100644 --- a/src/Castle.Sdk/Messages/Requests/ActionRequest.cs +++ b/src/Castle.Sdk/Messages/Requests/ActionRequest.cs @@ -39,7 +39,6 @@ public class ActionRequest public RequestContext Context { get; set; } = new RequestContext(); - [JsonIgnore] private RequestOptions Options { get; set; } = new RequestOptions(); internal ActionRequest PrepareApiCopy(string[] allowList, string[] denyList) From d1878ebc0bf0b531eacf112109a65a916b234458 Mon Sep 17 00:00:00 2001 From: marysieek Date: Tue, 23 Mar 2021 14:55:17 +0100 Subject: [PATCH 13/15] Add public options --- src/Castle.Sdk/Messages/Requests/ActionRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Castle.Sdk/Messages/Requests/ActionRequest.cs b/src/Castle.Sdk/Messages/Requests/ActionRequest.cs index c4b0ba9..4fdee11 100644 --- a/src/Castle.Sdk/Messages/Requests/ActionRequest.cs +++ b/src/Castle.Sdk/Messages/Requests/ActionRequest.cs @@ -39,7 +39,7 @@ public class ActionRequest public RequestContext Context { get; set; } = new RequestContext(); - private RequestOptions Options { get; set; } = new RequestOptions(); + public RequestOptions Options { get; set; } = new RequestOptions(); internal ActionRequest PrepareApiCopy(string[] allowList, string[] denyList) { From ddf1c9eec539bb8e4de3093b134e4263db87eebd Mon Sep 17 00:00:00 2001 From: marysieek Date: Tue, 23 Mar 2021 15:12:24 +0100 Subject: [PATCH 14/15] Trigger rebuild --- src/Castle.Sdk/Castle.Sdk.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Castle.Sdk/Castle.Sdk.csproj b/src/Castle.Sdk/Castle.Sdk.csproj index 4bf5ea4..939c7ba 100644 --- a/src/Castle.Sdk/Castle.Sdk.csproj +++ b/src/Castle.Sdk/Castle.Sdk.csproj @@ -5,7 +5,7 @@ false https://github.com/castle/castle-dotnet Castle - 1.5.0 + 1.5.1 Castle Castle .NET SDK Castle SDK for C# / .NET From cfa9bbd7cda7ce68ec18eda1accad6f7fb65b036 Mon Sep 17 00:00:00 2001 From: marysieek Date: Tue, 23 Mar 2021 15:16:59 +0100 Subject: [PATCH 15/15] Add jsonignore --- src/Castle.Sdk/Castle.Sdk.csproj | 2 +- src/Castle.Sdk/Messages/Requests/ActionRequest.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Castle.Sdk/Castle.Sdk.csproj b/src/Castle.Sdk/Castle.Sdk.csproj index 939c7ba..a99bbca 100644 --- a/src/Castle.Sdk/Castle.Sdk.csproj +++ b/src/Castle.Sdk/Castle.Sdk.csproj @@ -5,7 +5,7 @@ false https://github.com/castle/castle-dotnet Castle - 1.5.1 + 1.5.2 Castle Castle .NET SDK Castle SDK for C# / .NET diff --git a/src/Castle.Sdk/Messages/Requests/ActionRequest.cs b/src/Castle.Sdk/Messages/Requests/ActionRequest.cs index 4fdee11..50ce9c7 100644 --- a/src/Castle.Sdk/Messages/Requests/ActionRequest.cs +++ b/src/Castle.Sdk/Messages/Requests/ActionRequest.cs @@ -39,6 +39,7 @@ public class ActionRequest public RequestContext Context { get; set; } = new RequestContext(); + [JsonIgnore] public RequestOptions Options { get; set; } = new RequestOptions(); internal ActionRequest PrepareApiCopy(string[] allowList, string[] denyList)