Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include Context information in config response #58

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Api/Controllers/ConfigController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ public ConfigController(
[HttpGet("")]
public ConfigResponseModel GetConfigs()
{
return new ConfigResponseModel(_globalSettings, _featureService.GetAll());
return new ConfigResponseModel(_globalSettings, _featureService.GetAll(), _featureService.GetFlagContext());
}
}
16 changes: 15 additions & 1 deletion src/Api/Models/Response/ConfigResponseModel.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Bit.Core.Models.Api;
using Bit.Core.Services;
using Bit.Core.Settings;
using Bit.Core.Utilities;

Expand All @@ -11,6 +12,7 @@ public class ConfigResponseModel : ResponseModel
public ServerConfigResponseModel Server { get; set; }
public EnvironmentConfigResponseModel Environment { get; set; }
public IDictionary<string, object> FeatureStates { get; set; }
public ContextResponseModel Context { get; set; }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Including sensitive context information in the config response may expose user data unnecessarily. Consider the security implications of this change.


public ConfigResponseModel() : base("config")
{
Expand All @@ -22,7 +24,7 @@ public ConfigResponseModel() : base("config")

public ConfigResponseModel(
IGlobalSettings globalSettings,
IDictionary<string, object> featureStates) : base("config")
IDictionary<string, object> featureStates, FeatureFlagContext featureFlagContext) : base("config")
{
Version = AssemblyHelpers.GetVersion();
GitHash = AssemblyHelpers.GetGitHash();
Expand All @@ -36,6 +38,7 @@ public ConfigResponseModel(
Sso = globalSettings.BaseServiceUri.Sso
};
FeatureStates = featureStates;
Context = new ContextResponseModel(featureFlagContext.UserId, featureFlagContext.OrganizationIds);
}
}

Expand All @@ -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; }
Comment on lines +63 to +64
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Exposing UserId and OrganizationIds in the response could potentially be used for user enumeration attacks. Evaluate the necessity of including this information.

public ContextResponseModel(Guid? userId, Guid[] organizationIds)
{
UserId = userId;
OrganizationIds = organizationIds;
}
Comment on lines +65 to +69
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Consider adding input validation to ensure userId and organizationIds are not null or empty before assigning.

}
7 changes: 7 additions & 0 deletions src/Core/Services/IFeatureService.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
namespace Bit.Core.Services;

public struct FeatureFlagContext
{
public Guid? UserId { get; init; }
public Guid[] OrganizationIds { get; init; }
}

public interface IFeatureService
{
/// <summary>
Expand Down Expand Up @@ -37,4 +43,5 @@ public interface IFeatureService
/// </summary>
/// <returns>A dictionary of feature keys and their values.</returns>
Dictionary<string, object> GetAll();
FeatureFlagContext GetFlagContext();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Consider adding XML documentation for the new GetFlagContext() method

}
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,15 @@ 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()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: This line may return null if _currentContext.Organizations is null. Consider using the null-coalescing operator to return an empty array instead.

};
}
Comment on lines +99 to +106
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Consider adding null checks for _currentContext and its properties to prevent potential null reference exceptions.


public Dictionary<string, object> GetAll()
{
var results = new Dictionary<string, object>();
Expand Down
58 changes: 50 additions & 8 deletions test/Core.Test/Services/LaunchDarklyFeatureServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,64 @@ private static SutProvider<LaunchDarklyFeatureService> GetSutProvider(IGlobalSet
return new SutProvider<LaunchDarklyFeatureService>(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<ICurrentContext>();
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<CurrentContextOrganization>
{
new CurrentContextOrganization { Id = Guid.NewGuid() },
new CurrentContextOrganization { Id = Guid.NewGuid() },
};

var currentContext = Substitute.For<ICurrentContext>();
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));
}

[Fact]
public void DefaultFeatureValue_NoSdkKey()
{
var sutProvider = GetSutProvider(new Settings.GlobalSettings());
var sutProvider = GetSutProvider(new Settings.GlobalSettings()).Create();

Assert.False(sutProvider.Sut.IsEnabled(_fakeFeatureKey));
}
Expand All @@ -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));
}
Expand All @@ -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));
}
Expand All @@ -74,15 +116,15 @@ 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));
}

[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();

Expand Down