1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-21 12:05:42 +01:00

[PM-1012] Feature access using context (#2764)

* Document online method

* Feature accessors with context

* Direct null assertion

* Establish a constants class for flag keys
This commit is contained in:
Matt Bishop 2023-03-07 13:46:52 -05:00 committed by GitHub
parent 7334de636b
commit 11c59addf4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 143 additions and 2 deletions

View File

@ -22,3 +22,8 @@ public static class AuthenticationSchemes
{
public const string BitwardenExternalCookieAuthenticationScheme = "bw.external";
}
public static class FeatureFlagKeys
{
public const string SecretsManager = "secrets-manager";
}

View File

@ -1,6 +1,39 @@
namespace Bit.Core.Services;
using Bit.Core.Context;
namespace Bit.Core.Services;
public interface IFeatureService
{
/// <summary>
/// Checks whether online access to feature status is available.
/// </summary>
/// <returns>True if the service is online, otherwise false.</returns>
bool IsOnline();
/// <summary>
/// Checks whether a given feature is enabled.
/// </summary>
/// <param name="key">The key of the feature to check.</param>
/// <param name="currentContext">A context providing information that can be used to evaluate whether a feature should be on or off.</param>
/// <param name="defaultValue">The default value for the feature.</param>
/// <returns>True if the feature is enabled, otherwise false.</returns>
bool IsEnabled(string key, ICurrentContext currentContext, bool defaultValue = false);
/// <summary>
/// Gets the integer variation of a feature.
/// </summary>
/// <param name="key">The key of the feature to check.</param>
/// <param name="currentContext">A context providing information that can be used to evaluate the feature value.</param>
/// <param name="defaultValue">The default value for the feature.</param>
/// <returns>The feature variation value.</returns>
int GetIntVariation(string key, ICurrentContext currentContext, int defaultValue = 0);
/// <summary>
/// Gets the string variation of a feature.
/// </summary>
/// <param name="key">The key of the feature to check.</param>
/// <param name="currentContext">A context providing information that can be used to evaluate the feature value.</param>
/// <param name="defaultValue">The default value for the feature.</param>
/// <returns>The feature variation value.</returns>
string GetStringVariation(string key, ICurrentContext currentContext, string defaultValue = null);
}

View File

@ -1,4 +1,5 @@
using Bit.Core.Settings;
using Bit.Core.Context;
using Bit.Core.Settings;
using LaunchDarkly.Sdk.Server;
using LaunchDarkly.Sdk.Server.Integrations;
@ -47,8 +48,44 @@ public class LaunchDarklyFeatureService : IFeatureService, IDisposable
return _client.Initialized && !_client.IsOffline();
}
public bool IsEnabled(string key, ICurrentContext currentContext, bool defaultValue = false)
{
return _client.BoolVariation(key, BuildContext(currentContext), defaultValue);
}
public int GetIntVariation(string key, ICurrentContext currentContext, int defaultValue = 0)
{
return _client.IntVariation(key, BuildContext(currentContext), defaultValue);
}
public string GetStringVariation(string key, ICurrentContext currentContext, string defaultValue = null)
{
return _client.StringVariation(key, BuildContext(currentContext), defaultValue);
}
public void Dispose()
{
_client?.Dispose();
}
private LaunchDarkly.Sdk.Context BuildContext(ICurrentContext currentContext)
{
var builder = LaunchDarkly.Sdk.Context.MultiBuilder();
if (currentContext.UserId.HasValue)
{
var user = LaunchDarkly.Sdk.Context.Builder(currentContext.UserId.Value.ToString());
user.Kind(LaunchDarkly.Sdk.ContextKind.Default);
builder.Add(user.Build());
}
if (currentContext.OrganizationId.HasValue)
{
var org = LaunchDarkly.Sdk.Context.Builder(currentContext.OrganizationId.Value.ToString());
org.Kind("org");
builder.Add(org.Build());
}
return builder.Build();
}
}

View File

@ -1,8 +1,10 @@
using AutoFixture;
using Bit.Core.Context;
using Bit.Core.Services;
using Bit.Core.Settings;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.Services;
@ -25,4 +27,68 @@ public class LaunchDarklyFeatureServiceTests
Assert.False(sutProvider.Sut.IsOnline());
}
[Theory, BitAutoData]
public void DefaultFeatureValue_WhenSelfHost(string key)
{
var sutProvider = GetSutProvider(new Core.Settings.GlobalSettings() { SelfHosted = true });
var currentContext = Substitute.For<ICurrentContext>();
currentContext.UserId.Returns(Guid.NewGuid());
Assert.False(sutProvider.Sut.IsEnabled(key, currentContext));
}
[Fact]
public void DefaultFeatureValue_NoSdkKey()
{
var sutProvider = GetSutProvider(new Core.Settings.GlobalSettings());
var currentContext = Substitute.For<ICurrentContext>();
currentContext.UserId.Returns(Guid.NewGuid());
Assert.False(sutProvider.Sut.IsEnabled(FeatureFlagKeys.SecretsManager, currentContext));
}
[Fact(Skip = "For local development")]
public void FeatureValue_Boolean()
{
var settings = new Core.Settings.GlobalSettings();
settings.LaunchDarkly.SdkKey = "somevalue";
var sutProvider = GetSutProvider(settings);
var currentContext = Substitute.For<ICurrentContext>();
currentContext.UserId.Returns(Guid.NewGuid());
Assert.False(sutProvider.Sut.IsEnabled(FeatureFlagKeys.SecretsManager, currentContext));
}
[Fact(Skip = "For local development")]
public void FeatureValue_Int()
{
var settings = new Core.Settings.GlobalSettings();
settings.LaunchDarkly.SdkKey = "somevalue";
var sutProvider = GetSutProvider(settings);
var currentContext = Substitute.For<ICurrentContext>();
currentContext.UserId.Returns(Guid.NewGuid());
Assert.Equal(0, sutProvider.Sut.GetIntVariation(FeatureFlagKeys.SecretsManager, currentContext));
}
[Fact(Skip = "For local development")]
public void FeatureValue_String()
{
var settings = new Core.Settings.GlobalSettings();
settings.LaunchDarkly.SdkKey = "somevalue";
var sutProvider = GetSutProvider(settings);
var currentContext = Substitute.For<ICurrentContext>();
currentContext.UserId.Returns(Guid.NewGuid());
Assert.Null(sutProvider.Sut.GetStringVariation(FeatureFlagKeys.SecretsManager, currentContext));
}
}