From 2cb9f8fb0dfee1bbfa974015835ed0ccd40c083e Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Wed, 20 Mar 2024 11:45:29 -0400 Subject: [PATCH 1/4] Add a flag context to config response. Useful for debugging --- src/Api/Controllers/ConfigController.cs | 2 +- src/Api/Models/Response/ConfigResponseModel.cs | 16 +++++++++++++++- src/Core/Services/IFeatureService.cs | 7 +++++++ .../LaunchDarklyFeatureService.cs | 12 ++++++++++++ 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/Api/Controllers/ConfigController.cs b/src/Api/Controllers/ConfigController.cs index 7699c6b11558..91f6103d6d27 100644 --- a/src/Api/Controllers/ConfigController.cs +++ b/src/Api/Controllers/ConfigController.cs @@ -23,6 +23,6 @@ public ConfigController( [HttpGet("")] public ConfigResponseModel GetConfigs() { - return new ConfigResponseModel(_globalSettings, _featureService.GetAll()); + return new ConfigResponseModel(_globalSettings, _featureService.GetAll(), _featureService.GetFlagContext()); } } diff --git a/src/Api/Models/Response/ConfigResponseModel.cs b/src/Api/Models/Response/ConfigResponseModel.cs index e560271c0044..2b27e868c396 100644 --- a/src/Api/Models/Response/ConfigResponseModel.cs +++ b/src/Api/Models/Response/ConfigResponseModel.cs @@ -1,4 +1,5 @@ using Bit.Core.Models.Api; +using Bit.Core.Services; using Bit.Core.Settings; using Bit.Core.Utilities; @@ -11,6 +12,7 @@ public class ConfigResponseModel : ResponseModel public ServerConfigResponseModel Server { get; set; } public EnvironmentConfigResponseModel Environment { get; set; } public IDictionary FeatureStates { get; set; } + public ContextResponseModel Context { get; set; } public ConfigResponseModel() : base("config") { @@ -22,7 +24,7 @@ public ConfigResponseModel() : base("config") public ConfigResponseModel( IGlobalSettings globalSettings, - IDictionary featureStates) : base("config") + IDictionary featureStates, FeatureFlagContext featureFlagContext) : base("config") { Version = AssemblyHelpers.GetVersion(); GitHash = AssemblyHelpers.GetGitHash(); @@ -36,6 +38,7 @@ public ConfigResponseModel( Sso = globalSettings.BaseServiceUri.Sso }; FeatureStates = featureStates; + Context = new ContextResponseModel(featureFlagContext); } } @@ -54,3 +57,14 @@ public class EnvironmentConfigResponseModel public string Notifications { get; set; } public string Sso { get; set; } } + +public class ContextResponseModel +{ + public Guid? UserId { get; set; } + public Guid[] OrganizationIds { get; set; } + public ContextResponseModel(FeatureFlagContext context) + { + UserId = context.UserId; + OrganizationIds = context.OrganizationIds; + } +} diff --git a/src/Core/Services/IFeatureService.cs b/src/Core/Services/IFeatureService.cs index 0ac168a0cdcf..a8b082d254dd 100644 --- a/src/Core/Services/IFeatureService.cs +++ b/src/Core/Services/IFeatureService.cs @@ -1,5 +1,11 @@ namespace Bit.Core.Services; +public struct FeatureFlagContext +{ + public Guid? UserId { get; init; } + public Guid[] OrganizationIds { get; init; } +} + public interface IFeatureService { /// @@ -37,4 +43,5 @@ public interface IFeatureService /// /// A dictionary of feature keys and their values. Dictionary GetAll(); + FeatureFlagContext GetFlagContext(); } diff --git a/src/Core/Services/Implementations/LaunchDarklyFeatureService.cs b/src/Core/Services/Implementations/LaunchDarklyFeatureService.cs index b1fd9c5643d4..380fb26edf38 100644 --- a/src/Core/Services/Implementations/LaunchDarklyFeatureService.cs +++ b/src/Core/Services/Implementations/LaunchDarklyFeatureService.cs @@ -96,12 +96,24 @@ public string GetStringVariation(string key, string defaultValue = null) return _client.StringVariation(key, BuildContext(), defaultValue); } + public FeatureFlagContext GetFlagContext() + { + return new FeatureFlagContext() + { + UserId = _currentContext.UserId, + OrganizationIds = _currentContext.Organizations?.Select(o => o.Id).ToArray() + }; + } + public Dictionary GetAll() { var results = new Dictionary(); var keys = FeatureFlagKeys.GetAllKeys(); + results.Add("userId", _currentContext.UserId); + results.Add("OrgIds", _currentContext.Organizations?.Select(o => o.Id).ToList()); + var values = _client.AllFlagsState(BuildContext()); if (values.Valid) { From 4c649a43e51075b76cae7b6cffcfbae25de13261 Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Wed, 20 Mar 2024 12:03:47 -0400 Subject: [PATCH 2/4] Test Flag context --- .../LaunchDarklyFeatureServiceTests.cs | 58 ++++++++++++++++--- 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/test/Core.Test/Services/LaunchDarklyFeatureServiceTests.cs b/test/Core.Test/Services/LaunchDarklyFeatureServiceTests.cs index 6970b5f90446..48d1db812f1b 100644 --- a/test/Core.Test/Services/LaunchDarklyFeatureServiceTests.cs +++ b/test/Core.Test/Services/LaunchDarklyFeatureServiceTests.cs @@ -29,14 +29,56 @@ private static SutProvider GetSutProvider(IGlobalSet return new SutProvider(fixture) .SetDependency(globalSettings) .SetDependency(currentContext) - .SetDependency(client) - .Create(); + .SetDependency(client); + } + + [Fact] + public void NoContext_WhenUnauthed() + { + var sutProvider = GetSutProvider(new Settings.GlobalSettings()); + + var currentContext = Substitute.For(); + currentContext.UserId.Returns(null as Guid?); + sutProvider.SetDependency(currentContext); + + Assert.Equivalent(sutProvider.Create().Sut.GetFlagContext(), new FeatureFlagContext + { + UserId = null, + OrganizationIds = null, + }); + } + + [Fact] + public void UserContext_WhenAuthed() + { + var sutProvider = GetSutProvider(new Settings.GlobalSettings()); + + var userId = Guid.NewGuid(); + var organizations = new List + { + new CurrentContextOrganization { Id = Guid.NewGuid() }, + new CurrentContextOrganization { Id = Guid.NewGuid() }, + }; + + var currentContext = Substitute.For(); + currentContext.UserId.Returns(userId); + currentContext.Organizations.Returns(organizations); + sutProvider.SetDependency(currentContext); + + var expected = new FeatureFlagContext + { + UserId = userId, + OrganizationIds = organizations.Select(o => o.Id).ToArray(), + }; + + Assert.Equivalent(sutProvider.Create().Sut.GetFlagContext(), expected); + } [Theory, BitAutoData] public void DefaultFeatureValue_WhenSelfHost(string key) { - var sutProvider = GetSutProvider(new Settings.GlobalSettings { SelfHosted = true }); + var sutProvider = GetSutProvider(new Settings.GlobalSettings { SelfHosted = true }).Create(); Assert.False(sutProvider.Sut.IsEnabled(key)); } @@ -44,7 +86,7 @@ public void DefaultFeatureValue_WhenSelfHost(string key) [Fact] public void DefaultFeatureValue_NoSdkKey() { - var sutProvider = GetSutProvider(new Settings.GlobalSettings()); + var sutProvider = GetSutProvider(new Settings.GlobalSettings()).Create(); Assert.False(sutProvider.Sut.IsEnabled(_fakeFeatureKey)); } @@ -54,7 +96,7 @@ public void FeatureValue_Boolean() { var settings = new Settings.GlobalSettings { LaunchDarkly = { SdkKey = _fakeSdkKey } }; - var sutProvider = GetSutProvider(settings); + var sutProvider = GetSutProvider(settings).Create(); Assert.False(sutProvider.Sut.IsEnabled(_fakeFeatureKey)); } @@ -64,7 +106,7 @@ public void FeatureValue_Int() { var settings = new Settings.GlobalSettings { LaunchDarkly = { SdkKey = _fakeSdkKey } }; - var sutProvider = GetSutProvider(settings); + var sutProvider = GetSutProvider(settings).Create(); Assert.Equal(0, sutProvider.Sut.GetIntVariation(_fakeFeatureKey)); } @@ -74,7 +116,7 @@ public void FeatureValue_String() { var settings = new Settings.GlobalSettings { LaunchDarkly = { SdkKey = _fakeSdkKey } }; - var sutProvider = GetSutProvider(settings); + var sutProvider = GetSutProvider(settings).Create(); Assert.Null(sutProvider.Sut.GetStringVariation(_fakeFeatureKey)); } @@ -82,7 +124,7 @@ public void FeatureValue_String() [Fact(Skip = "For local development")] public void GetAll() { - var sutProvider = GetSutProvider(new Settings.GlobalSettings()); + var sutProvider = GetSutProvider(new Settings.GlobalSettings()).Create(); var results = sutProvider.Sut.GetAll(); From 77df7e18fcd3a23c2e516b9af8a6f513540de7fb Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Wed, 20 Mar 2024 12:09:54 -0400 Subject: [PATCH 3/4] Fixup remove initial work --- .../Services/Implementations/LaunchDarklyFeatureService.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Core/Services/Implementations/LaunchDarklyFeatureService.cs b/src/Core/Services/Implementations/LaunchDarklyFeatureService.cs index 380fb26edf38..2be0200f3c86 100644 --- a/src/Core/Services/Implementations/LaunchDarklyFeatureService.cs +++ b/src/Core/Services/Implementations/LaunchDarklyFeatureService.cs @@ -111,9 +111,6 @@ public Dictionary GetAll() var keys = FeatureFlagKeys.GetAllKeys(); - results.Add("userId", _currentContext.UserId); - results.Add("OrgIds", _currentContext.Organizations?.Select(o => o.Id).ToList()); - var values = _client.AllFlagsState(BuildContext()); if (values.Valid) { From 61f26dac16c9c465a9a1e3ef675c6d20f43a2beb Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Wed, 20 Mar 2024 13:06:22 -0400 Subject: [PATCH 4/4] Fix deserialization --- src/Api/Models/Response/ConfigResponseModel.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Api/Models/Response/ConfigResponseModel.cs b/src/Api/Models/Response/ConfigResponseModel.cs index 2b27e868c396..ab2348f3c922 100644 --- a/src/Api/Models/Response/ConfigResponseModel.cs +++ b/src/Api/Models/Response/ConfigResponseModel.cs @@ -38,7 +38,7 @@ public ConfigResponseModel( Sso = globalSettings.BaseServiceUri.Sso }; FeatureStates = featureStates; - Context = new ContextResponseModel(featureFlagContext); + Context = new ContextResponseModel(featureFlagContext.UserId, featureFlagContext.OrganizationIds); } } @@ -62,9 +62,9 @@ public class ContextResponseModel { public Guid? UserId { get; set; } public Guid[] OrganizationIds { get; set; } - public ContextResponseModel(FeatureFlagContext context) + public ContextResponseModel(Guid? userId, Guid[] organizationIds) { - UserId = context.UserId; - OrganizationIds = context.OrganizationIds; + UserId = userId; + OrganizationIds = organizationIds; } }