1
0
mirror of https://github.com/bitwarden/server.git synced 2025-05-20 19:14:32 -05:00

Establish IFeatureService as scoped (#3679)

* Establish IFeatureService as scoped

* Lint

* Feedback around injection
This commit is contained in:
Matt Bishop 2024-01-18 09:47:34 -05:00 committed by GitHub
parent cd006f3779
commit 974d23efdd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 96 additions and 131 deletions

View File

@ -27,7 +27,7 @@ public class UsersController : Controller
private readonly IFeatureService _featureService; private readonly IFeatureService _featureService;
private bool UseFlexibleCollections => private bool UseFlexibleCollections =>
_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext); _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections);
public UsersController( public UsersController(
IUserRepository userRepository, IUserRepository userRepository,

View File

@ -37,7 +37,6 @@ public class GroupsController : Controller
ICreateGroupCommand createGroupCommand, ICreateGroupCommand createGroupCommand,
IUpdateGroupCommand updateGroupCommand, IUpdateGroupCommand updateGroupCommand,
IDeleteGroupCommand deleteGroupCommand, IDeleteGroupCommand deleteGroupCommand,
IFeatureService featureService,
IAuthorizationService authorizationService, IAuthorizationService authorizationService,
IApplicationCacheService applicationCacheService) IApplicationCacheService applicationCacheService)
{ {

View File

@ -803,7 +803,7 @@ public class OrganizationsController : Controller
throw new BadRequestException("Organization does not have collection enhancements enabled"); throw new BadRequestException("Organization does not have collection enhancements enabled");
} }
var v1Enabled = _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1, _currentContext); var v1Enabled = _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1);
if (!v1Enabled) if (!v1Enabled)
{ {

View File

@ -21,7 +21,6 @@ using Bit.Core.Auth.Services;
using Bit.Core.Auth.UserFeatures.UserKey; using Bit.Core.Auth.UserFeatures.UserKey;
using Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces; using Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces;
using Bit.Core.Auth.Utilities; using Bit.Core.Auth.Utilities;
using Bit.Core.Context;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
@ -63,10 +62,9 @@ public class AccountsController : Controller
private readonly ISetInitialMasterPasswordCommand _setInitialMasterPasswordCommand; private readonly ISetInitialMasterPasswordCommand _setInitialMasterPasswordCommand;
private readonly IRotateUserKeyCommand _rotateUserKeyCommand; private readonly IRotateUserKeyCommand _rotateUserKeyCommand;
private readonly IFeatureService _featureService; private readonly IFeatureService _featureService;
private readonly ICurrentContext _currentContext;
private bool UseFlexibleCollections => private bool UseFlexibleCollections =>
_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext); _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections);
private readonly IRotationValidator<IEnumerable<CipherWithIdRequestModel>, IEnumerable<Cipher>> _cipherValidator; private readonly IRotationValidator<IEnumerable<CipherWithIdRequestModel>, IEnumerable<Cipher>> _cipherValidator;
private readonly IRotationValidator<IEnumerable<FolderWithIdRequestModel>, IEnumerable<Folder>> _folderValidator; private readonly IRotationValidator<IEnumerable<FolderWithIdRequestModel>, IEnumerable<Folder>> _folderValidator;
@ -95,7 +93,6 @@ public class AccountsController : Controller
ISetInitialMasterPasswordCommand setInitialMasterPasswordCommand, ISetInitialMasterPasswordCommand setInitialMasterPasswordCommand,
IRotateUserKeyCommand rotateUserKeyCommand, IRotateUserKeyCommand rotateUserKeyCommand,
IFeatureService featureService, IFeatureService featureService,
ICurrentContext currentContext,
IRotationValidator<IEnumerable<CipherWithIdRequestModel>, IEnumerable<Cipher>> cipherValidator, IRotationValidator<IEnumerable<CipherWithIdRequestModel>, IEnumerable<Cipher>> cipherValidator,
IRotationValidator<IEnumerable<FolderWithIdRequestModel>, IEnumerable<Folder>> folderValidator, IRotationValidator<IEnumerable<FolderWithIdRequestModel>, IEnumerable<Folder>> folderValidator,
IRotationValidator<IEnumerable<SendWithIdRequestModel>, IReadOnlyList<Send>> sendValidator, IRotationValidator<IEnumerable<SendWithIdRequestModel>, IReadOnlyList<Send>> sendValidator,
@ -121,7 +118,6 @@ public class AccountsController : Controller
_setInitialMasterPasswordCommand = setInitialMasterPasswordCommand; _setInitialMasterPasswordCommand = setInitialMasterPasswordCommand;
_rotateUserKeyCommand = rotateUserKeyCommand; _rotateUserKeyCommand = rotateUserKeyCommand;
_featureService = featureService; _featureService = featureService;
_currentContext = currentContext;
_cipherValidator = cipherValidator; _cipherValidator = cipherValidator;
_folderValidator = folderValidator; _folderValidator = folderValidator;
_sendValidator = sendValidator; _sendValidator = sendValidator;
@ -425,7 +421,7 @@ public class AccountsController : Controller
} }
IdentityResult result; IdentityResult result;
if (_featureService.IsEnabled(FeatureFlagKeys.KeyRotationImprovements, _currentContext)) if (_featureService.IsEnabled(FeatureFlagKeys.KeyRotationImprovements))
{ {
var dataModel = new RotateUserKeyData var dataModel = new RotateUserKeyData
{ {

View File

@ -1,5 +1,4 @@
using Bit.Api.Models.Response; using Bit.Api.Models.Response;
using Bit.Core.Context;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Settings; using Bit.Core.Settings;
@ -11,22 +10,19 @@ namespace Bit.Api.Controllers;
public class ConfigController : Controller public class ConfigController : Controller
{ {
private readonly IGlobalSettings _globalSettings; private readonly IGlobalSettings _globalSettings;
private readonly ICurrentContext _currentContext;
private readonly IFeatureService _featureService; private readonly IFeatureService _featureService;
public ConfigController( public ConfigController(
IGlobalSettings globalSettings, IGlobalSettings globalSettings,
ICurrentContext currentContext,
IFeatureService featureService) IFeatureService featureService)
{ {
_globalSettings = globalSettings; _globalSettings = globalSettings;
_currentContext = currentContext;
_featureService = featureService; _featureService = featureService;
} }
[HttpGet("")] [HttpGet("")]
public ConfigResponseModel GetConfigs() public ConfigResponseModel GetConfigs()
{ {
return new ConfigResponseModel(_globalSettings, _featureService.GetAll(_currentContext)); return new ConfigResponseModel(_globalSettings, _featureService.GetAll());
} }
} }

View File

@ -43,7 +43,7 @@ public class CiphersController : Controller
private readonly IFeatureService _featureService; private readonly IFeatureService _featureService;
private bool UseFlexibleCollections => private bool UseFlexibleCollections =>
_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext); _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections);
public CiphersController( public CiphersController(
ICipherRepository cipherRepository, ICipherRepository cipherRepository,

View File

@ -3,7 +3,6 @@ using Bit.Core;
using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums.Provider; using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Context;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
@ -32,11 +31,10 @@ public class SyncController : Controller
private readonly IPolicyRepository _policyRepository; private readonly IPolicyRepository _policyRepository;
private readonly ISendRepository _sendRepository; private readonly ISendRepository _sendRepository;
private readonly GlobalSettings _globalSettings; private readonly GlobalSettings _globalSettings;
private readonly ICurrentContext _currentContext;
private readonly IFeatureService _featureService; private readonly IFeatureService _featureService;
private bool UseFlexibleCollections => private bool UseFlexibleCollections =>
_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext); _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections);
public SyncController( public SyncController(
IUserService userService, IUserService userService,
@ -49,7 +47,6 @@ public class SyncController : Controller
IPolicyRepository policyRepository, IPolicyRepository policyRepository,
ISendRepository sendRepository, ISendRepository sendRepository,
GlobalSettings globalSettings, GlobalSettings globalSettings,
ICurrentContext currentContext,
IFeatureService featureService) IFeatureService featureService)
{ {
_userService = userService; _userService = userService;
@ -62,7 +59,6 @@ public class SyncController : Controller
_policyRepository = policyRepository; _policyRepository = policyRepository;
_sendRepository = sendRepository; _sendRepository = sendRepository;
_globalSettings = globalSettings; _globalSettings = globalSettings;
_currentContext = currentContext;
_featureService = featureService; _featureService = featureService;
} }

View File

@ -1,7 +1,6 @@
using Bit.Api.Auth.Validators; using Bit.Api.Auth.Validators;
using Bit.Api.Vault.Models.Request; using Bit.Api.Vault.Models.Request;
using Bit.Core; using Bit.Core;
using Bit.Core.Context;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Services; using Bit.Core.Services;
@ -13,19 +12,15 @@ namespace Bit.Api.Vault.Validators;
public class CipherRotationValidator : IRotationValidator<IEnumerable<CipherWithIdRequestModel>, IEnumerable<Cipher>> public class CipherRotationValidator : IRotationValidator<IEnumerable<CipherWithIdRequestModel>, IEnumerable<Cipher>>
{ {
private readonly ICipherRepository _cipherRepository; private readonly ICipherRepository _cipherRepository;
private readonly ICurrentContext _currentContext;
private readonly IFeatureService _featureService; private readonly IFeatureService _featureService;
private bool UseFlexibleCollections => private bool UseFlexibleCollections =>
_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext); _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections);
public CipherRotationValidator(ICipherRepository cipherRepository, ICurrentContext currentContext, public CipherRotationValidator(ICipherRepository cipherRepository, IFeatureService featureService)
IFeatureService featureService)
{ {
_cipherRepository = cipherRepository; _cipherRepository = cipherRepository;
_currentContext = currentContext;
_featureService = featureService; _featureService = featureService;
} }
public async Task<IEnumerable<Cipher>> ValidateAsync(User user, IEnumerable<CipherWithIdRequestModel> ciphers) public async Task<IEnumerable<Cipher>> ValidateAsync(User user, IEnumerable<CipherWithIdRequestModel> ciphers)

View File

@ -442,10 +442,10 @@ public class OrganizationService : IOrganizationService
} }
var flexibleCollectionsSignupEnabled = var flexibleCollectionsSignupEnabled =
_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollectionsSignup, _currentContext); _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollectionsSignup);
var flexibleCollectionsV1IsEnabled = var flexibleCollectionsV1IsEnabled =
_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1, _currentContext); _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1);
var organization = new Organization var organization = new Organization
{ {
@ -572,7 +572,7 @@ public class OrganizationService : IOrganizationService
await ValidateSignUpPoliciesAsync(owner.Id); await ValidateSignUpPoliciesAsync(owner.Id);
var flexibleCollectionsSignupEnabled = var flexibleCollectionsSignupEnabled =
_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollectionsSignup, _currentContext); _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollectionsSignup);
var organization = new Organization var organization = new Organization
{ {

View File

@ -5,7 +5,6 @@ using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Models; using Bit.Core.Auth.Models;
using Bit.Core.Auth.Models.Business.Tokenables; using Bit.Core.Auth.Models.Business.Tokenables;
using Bit.Core.Auth.Models.Data; using Bit.Core.Auth.Models.Data;
using Bit.Core.Context;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
@ -34,11 +33,10 @@ public class EmergencyAccessService : IEmergencyAccessService
private readonly IPasswordHasher<User> _passwordHasher; private readonly IPasswordHasher<User> _passwordHasher;
private readonly IOrganizationService _organizationService; private readonly IOrganizationService _organizationService;
private readonly IDataProtectorTokenFactory<EmergencyAccessInviteTokenable> _dataProtectorTokenizer; private readonly IDataProtectorTokenFactory<EmergencyAccessInviteTokenable> _dataProtectorTokenizer;
private readonly ICurrentContext _currentContext;
private readonly IFeatureService _featureService; private readonly IFeatureService _featureService;
private bool UseFlexibleCollections => private bool UseFlexibleCollections =>
_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext); _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections);
public EmergencyAccessService( public EmergencyAccessService(
IEmergencyAccessRepository emergencyAccessRepository, IEmergencyAccessRepository emergencyAccessRepository,
@ -53,7 +51,6 @@ public class EmergencyAccessService : IEmergencyAccessService
GlobalSettings globalSettings, GlobalSettings globalSettings,
IOrganizationService organizationService, IOrganizationService organizationService,
IDataProtectorTokenFactory<EmergencyAccessInviteTokenable> dataProtectorTokenizer, IDataProtectorTokenFactory<EmergencyAccessInviteTokenable> dataProtectorTokenizer,
ICurrentContext currentContext,
IFeatureService featureService) IFeatureService featureService)
{ {
_emergencyAccessRepository = emergencyAccessRepository; _emergencyAccessRepository = emergencyAccessRepository;
@ -68,7 +65,6 @@ public class EmergencyAccessService : IEmergencyAccessService
_globalSettings = globalSettings; _globalSettings = globalSettings;
_organizationService = organizationService; _organizationService = organizationService;
_dataProtectorTokenizer = dataProtectorTokenizer; _dataProtectorTokenizer = dataProtectorTokenizer;
_currentContext = currentContext;
_featureService = featureService; _featureService = featureService;
} }

View File

@ -8,7 +8,6 @@ using Bit.Core.Enums;
using Bit.Core.Identity; using Bit.Core.Identity;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings; using Bit.Core.Settings;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@ -19,7 +18,6 @@ public class CurrentContext : ICurrentContext
{ {
private readonly IProviderOrganizationRepository _providerOrganizationRepository; private readonly IProviderOrganizationRepository _providerOrganizationRepository;
private readonly IProviderUserRepository _providerUserRepository; private readonly IProviderUserRepository _providerUserRepository;
private readonly IFeatureService _featureService;
private bool _builtHttpContext; private bool _builtHttpContext;
private bool _builtClaimsPrincipal; private bool _builtClaimsPrincipal;
private IEnumerable<ProviderOrganizationProviderDetails> _providerOrganizationProviderDetails; private IEnumerable<ProviderOrganizationProviderDetails> _providerOrganizationProviderDetails;
@ -46,12 +44,10 @@ public class CurrentContext : ICurrentContext
public CurrentContext( public CurrentContext(
IProviderOrganizationRepository providerOrganizationRepository, IProviderOrganizationRepository providerOrganizationRepository,
IProviderUserRepository providerUserRepository, IProviderUserRepository providerUserRepository)
IFeatureService featureService)
{ {
_providerOrganizationRepository = providerOrganizationRepository; _providerOrganizationRepository = providerOrganizationRepository;
_providerUserRepository = providerUserRepository; _providerUserRepository = providerUserRepository;
_featureService = featureService; ;
} }
public async virtual Task BuildAsync(HttpContext httpContext, GlobalSettings globalSettings) public async virtual Task BuildAsync(HttpContext httpContext, GlobalSettings globalSettings)

View File

@ -1,6 +1,4 @@
using Bit.Core.Context; namespace Bit.Core.Services;
namespace Bit.Core.Services;
public interface IFeatureService public interface IFeatureService
{ {
@ -14,33 +12,29 @@ public interface IFeatureService
/// Checks whether a given feature is enabled. /// Checks whether a given feature is enabled.
/// </summary> /// </summary>
/// <param name="key">The key of the feature to check.</param> /// <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> /// <param name="defaultValue">The default value for the feature.</param>
/// <returns>True if the feature is enabled, otherwise false.</returns> /// <returns>True if the feature is enabled, otherwise false.</returns>
bool IsEnabled(string key, ICurrentContext currentContext, bool defaultValue = false); bool IsEnabled(string key, bool defaultValue = false);
/// <summary> /// <summary>
/// Gets the integer variation of a feature. /// Gets the integer variation of a feature.
/// </summary> /// </summary>
/// <param name="key">The key of the feature to check.</param> /// <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> /// <param name="defaultValue">The default value for the feature.</param>
/// <returns>The feature variation value.</returns> /// <returns>The feature variation value.</returns>
int GetIntVariation(string key, ICurrentContext currentContext, int defaultValue = 0); int GetIntVariation(string key, int defaultValue = 0);
/// <summary> /// <summary>
/// Gets the string variation of a feature. /// Gets the string variation of a feature.
/// </summary> /// </summary>
/// <param name="key">The key of the feature to check.</param> /// <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> /// <param name="defaultValue">The default value for the feature.</param>
/// <returns>The feature variation value.</returns> /// <returns>The feature variation value.</returns>
string GetStringVariation(string key, ICurrentContext currentContext, string defaultValue = null); string GetStringVariation(string key, string defaultValue = null);
/// <summary> /// <summary>
/// Gets all feature values. /// Gets all feature values.
/// </summary> /// </summary>
/// <param name="currentContext">A context providing information that can be used to evaluate the feature values.</param>
/// <returns>A dictionary of feature keys and their values.</returns> /// <returns>A dictionary of feature keys and their values.</returns>
Dictionary<string, object> GetAll(ICurrentContext currentContext); Dictionary<string, object> GetAll();
} }

View File

@ -56,7 +56,7 @@ public class CollectionService : ICollectionService
var usersList = users?.ToList(); var usersList = users?.ToList();
// If using Flexible Collections - a collection should always have someone with Can Manage permissions // If using Flexible Collections - a collection should always have someone with Can Manage permissions
if (_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1, _currentContext)) if (_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1))
{ {
var groupHasManageAccess = groupsList?.Any(g => g.Manage) ?? false; var groupHasManageAccess = groupsList?.Any(g => g.Manage) ?? false;
var userHasManageAccess = usersList?.Any(u => u.Manage) ?? false; var userHasManageAccess = usersList?.Any(u => u.Manage) ?? false;
@ -124,7 +124,7 @@ public class CollectionService : ICollectionService
{ {
var collections = await _collectionRepository.GetManyByUserIdAsync( var collections = await _collectionRepository.GetManyByUserIdAsync(
_currentContext.UserId.Value, _currentContext.UserId.Value,
_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext) _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections)
); );
orgCollections = collections.Where(c => c.OrganizationId == organizationId); orgCollections = collections.Where(c => c.OrganizationId == organizationId);
} }

View File

@ -4,16 +4,25 @@ using Bit.Core.Utilities;
using LaunchDarkly.Logging; using LaunchDarkly.Logging;
using LaunchDarkly.Sdk.Server; using LaunchDarkly.Sdk.Server;
using LaunchDarkly.Sdk.Server.Integrations; using LaunchDarkly.Sdk.Server.Integrations;
using LaunchDarkly.Sdk.Server.Interfaces;
namespace Bit.Core.Services; namespace Bit.Core.Services;
public class LaunchDarklyFeatureService : IFeatureService, IDisposable public class LaunchDarklyFeatureService : IFeatureService
{ {
private readonly LdClient _client; private readonly ILdClient _client;
private readonly ICurrentContext _currentContext;
private const string _anonymousUser = "25a15cac-58cf-4ac0-ad0f-b17c4bd92294"; private const string _anonymousUser = "25a15cac-58cf-4ac0-ad0f-b17c4bd92294";
public LaunchDarklyFeatureService( public LaunchDarklyFeatureService(
IGlobalSettings globalSettings) ILdClient client,
ICurrentContext currentContext)
{
_client = client;
_currentContext = currentContext;
}
public static Configuration GetConfiguredClient(GlobalSettings globalSettings)
{ {
var ldConfig = Configuration.Builder(globalSettings.LaunchDarkly?.SdkKey); var ldConfig = Configuration.Builder(globalSettings.LaunchDarkly?.SdkKey);
ldConfig.Logging(Components.Logging().Level(LogLevel.Error)); ldConfig.Logging(Components.Logging().Level(LogLevel.Error));
@ -64,7 +73,7 @@ public class LaunchDarklyFeatureService : IFeatureService, IDisposable
ldConfig.Offline(true); ldConfig.Offline(true);
} }
_client = new LdClient(ldConfig.Build()); return ldConfig.Build();
} }
public bool IsOnline() public bool IsOnline()
@ -72,28 +81,28 @@ public class LaunchDarklyFeatureService : IFeatureService, IDisposable
return _client.Initialized && !_client.IsOffline(); return _client.Initialized && !_client.IsOffline();
} }
public bool IsEnabled(string key, ICurrentContext currentContext, bool defaultValue = false) public bool IsEnabled(string key, bool defaultValue = false)
{ {
return _client.BoolVariation(key, BuildContext(currentContext), defaultValue); return _client.BoolVariation(key, BuildContext(), defaultValue);
} }
public int GetIntVariation(string key, ICurrentContext currentContext, int defaultValue = 0) public int GetIntVariation(string key, int defaultValue = 0)
{ {
return _client.IntVariation(key, BuildContext(currentContext), defaultValue); return _client.IntVariation(key, BuildContext(), defaultValue);
} }
public string GetStringVariation(string key, ICurrentContext currentContext, string defaultValue = null) public string GetStringVariation(string key, string defaultValue = null)
{ {
return _client.StringVariation(key, BuildContext(currentContext), defaultValue); return _client.StringVariation(key, BuildContext(), defaultValue);
} }
public Dictionary<string, object> GetAll(ICurrentContext currentContext) public Dictionary<string, object> GetAll()
{ {
var results = new Dictionary<string, object>(); var results = new Dictionary<string, object>();
var keys = FeatureFlagKeys.GetAllKeys(); var keys = FeatureFlagKeys.GetAllKeys();
var values = _client.AllFlagsState(BuildContext(currentContext)); var values = _client.AllFlagsState(BuildContext());
if (values.Valid) if (values.Valid)
{ {
foreach (var key in keys) foreach (var key in keys)
@ -119,23 +128,18 @@ public class LaunchDarklyFeatureService : IFeatureService, IDisposable
return results; return results;
} }
public void Dispose() private LaunchDarkly.Sdk.Context BuildContext()
{
_client?.Dispose();
}
private LaunchDarkly.Sdk.Context BuildContext(ICurrentContext currentContext)
{ {
var builder = LaunchDarkly.Sdk.Context.MultiBuilder(); var builder = LaunchDarkly.Sdk.Context.MultiBuilder();
switch (currentContext.ClientType) switch (_currentContext.ClientType)
{ {
case Identity.ClientType.User: case Identity.ClientType.User:
{ {
LaunchDarkly.Sdk.ContextBuilder ldUser; LaunchDarkly.Sdk.ContextBuilder ldUser;
if (currentContext.UserId.HasValue) if (_currentContext.UserId.HasValue)
{ {
ldUser = LaunchDarkly.Sdk.Context.Builder(currentContext.UserId.Value.ToString()); ldUser = LaunchDarkly.Sdk.Context.Builder(_currentContext.UserId.Value.ToString());
} }
else else
{ {
@ -146,9 +150,9 @@ public class LaunchDarklyFeatureService : IFeatureService, IDisposable
ldUser.Kind(LaunchDarkly.Sdk.ContextKind.Default); ldUser.Kind(LaunchDarkly.Sdk.ContextKind.Default);
if (currentContext.Organizations?.Any() ?? false) if (_currentContext.Organizations?.Any() ?? false)
{ {
var ldOrgs = currentContext.Organizations.Select(o => LaunchDarkly.Sdk.LdValue.Of(o.Id.ToString())); var ldOrgs = _currentContext.Organizations.Select(o => LaunchDarkly.Sdk.LdValue.Of(o.Id.ToString()));
ldUser.Set("organizations", LaunchDarkly.Sdk.LdValue.ArrayFrom(ldOrgs)); ldUser.Set("organizations", LaunchDarkly.Sdk.LdValue.ArrayFrom(ldOrgs));
} }
@ -158,9 +162,9 @@ public class LaunchDarklyFeatureService : IFeatureService, IDisposable
case Identity.ClientType.Organization: case Identity.ClientType.Organization:
{ {
if (currentContext.OrganizationId.HasValue) if (_currentContext.OrganizationId.HasValue)
{ {
var ldOrg = LaunchDarkly.Sdk.Context.Builder(currentContext.OrganizationId.Value.ToString()); var ldOrg = LaunchDarkly.Sdk.Context.Builder(_currentContext.OrganizationId.Value.ToString());
ldOrg.Kind("organization"); ldOrg.Kind("organization");
builder.Add(ldOrg.Build()); builder.Add(ldOrg.Build());
} }
@ -169,16 +173,16 @@ public class LaunchDarklyFeatureService : IFeatureService, IDisposable
case Identity.ClientType.ServiceAccount: case Identity.ClientType.ServiceAccount:
{ {
if (currentContext.UserId.HasValue) if (_currentContext.UserId.HasValue)
{ {
var ldServiceAccount = LaunchDarkly.Sdk.Context.Builder(currentContext.UserId.Value.ToString()); var ldServiceAccount = LaunchDarkly.Sdk.Context.Builder(_currentContext.UserId.Value.ToString());
ldServiceAccount.Kind("service-account"); ldServiceAccount.Kind("service-account");
builder.Add(ldServiceAccount.Build()); builder.Add(ldServiceAccount.Build());
} }
if (currentContext.OrganizationId.HasValue) if (_currentContext.OrganizationId.HasValue)
{ {
var ldOrg = LaunchDarkly.Sdk.Context.Builder(currentContext.OrganizationId.Value.ToString()); var ldOrg = LaunchDarkly.Sdk.Context.Builder(_currentContext.OrganizationId.Value.ToString());
ldOrg.Kind("organization"); ldOrg.Kind("organization");
builder.Add(ldOrg.Build()); builder.Add(ldOrg.Build());
} }
@ -189,7 +193,7 @@ public class LaunchDarklyFeatureService : IFeatureService, IDisposable
return builder.Build(); return builder.Build();
} }
private TestData BuildDataSource(Dictionary<string, string> values) private static TestData BuildDataSource(Dictionary<string, string> values)
{ {
var source = TestData.DataSource(); var source = TestData.DataSource();
foreach (var kvp in values) foreach (var kvp in values)

View File

@ -1,5 +1,4 @@
using Bit.Core.Context; using Bit.Core.Exceptions;
using Bit.Core.Exceptions;
using Bit.Core.Services; using Bit.Core.Services;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -25,10 +24,9 @@ public class RequireFeatureAttribute : ActionFilterAttribute
public override void OnActionExecuting(ActionExecutingContext context) public override void OnActionExecuting(ActionExecutingContext context)
{ {
var currentContext = context.HttpContext.RequestServices.GetRequiredService<ICurrentContext>();
var featureService = context.HttpContext.RequestServices.GetRequiredService<IFeatureService>(); var featureService = context.HttpContext.RequestServices.GetRequiredService<IFeatureService>();
if (!featureService.IsEnabled(_featureFlagKey, currentContext)) if (!featureService.IsEnabled(_featureFlagKey))
{ {
throw new FeatureUnavailableException(); throw new FeatureUnavailableException();
} }

View File

@ -41,7 +41,7 @@ public class CipherService : ICipherService
private readonly IFeatureService _featureService; private readonly IFeatureService _featureService;
private bool UseFlexibleCollections => private bool UseFlexibleCollections =>
_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext); _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections);
public CipherService( public CipherService(
ICipherRepository cipherRepository, ICipherRepository cipherRepository,

View File

@ -73,7 +73,7 @@ public class CollectController : Controller
} }
else else
{ {
var useFlexibleCollections = _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext); var useFlexibleCollections = _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections);
cipher = await _cipherRepository.GetByIdAsync(eventModel.CipherId.Value, cipher = await _cipherRepository.GetByIdAsync(eventModel.CipherId.Value,
_currentContext.UserId.Value, _currentContext.UserId.Value,
useFlexibleCollections); useFlexibleCollections);

View File

@ -70,7 +70,7 @@ public class Startup
services.AddSingleton<IEventWriteService, RepositoryEventWriteService>(); services.AddSingleton<IEventWriteService, RepositoryEventWriteService>();
} }
services.AddSingleton<IFeatureService, LaunchDarklyFeatureService>(); services.AddOptionality();
// Mvc // Mvc
services.AddMvc(config => services.AddMvc(config =>

View File

@ -66,7 +66,7 @@ public class WebAuthnGrantValidator : BaseRequestValidator<ExtensionGrantValidat
public async Task ValidateAsync(ExtensionGrantValidationContext context) public async Task ValidateAsync(ExtensionGrantValidationContext context)
{ {
if (!FeatureService.IsEnabled(FeatureFlagKeys.PasswordlessLogin, CurrentContext)) if (!FeatureService.IsEnabled(FeatureFlagKeys.PasswordlessLogin))
{ {
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant); context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant);
return; return;

View File

@ -18,7 +18,7 @@ public class NotificationsHub : Microsoft.AspNetCore.SignalR.Hub
public override async Task OnConnectedAsync() public override async Task OnConnectedAsync()
{ {
var currentContext = new CurrentContext(null, null, null); var currentContext = new CurrentContext(null, null);
await currentContext.BuildAsync(Context.User, _globalSettings); await currentContext.BuildAsync(Context.User, _globalSettings);
if (currentContext.Organizations != null) if (currentContext.Organizations != null)
{ {
@ -33,7 +33,7 @@ public class NotificationsHub : Microsoft.AspNetCore.SignalR.Hub
public override async Task OnDisconnectedAsync(Exception exception) public override async Task OnDisconnectedAsync(Exception exception)
{ {
var currentContext = new CurrentContext(null, null, null); var currentContext = new CurrentContext(null, null);
await currentContext.BuildAsync(Context.User, _globalSettings); await currentContext.BuildAsync(Context.User, _globalSettings);
if (currentContext.Organizations != null) if (currentContext.Organizations != null)
{ {

View File

@ -35,6 +35,8 @@ using Bit.Infrastructure.EntityFramework;
using DnsClient; using DnsClient;
using Duende.IdentityServer.Configuration; using Duende.IdentityServer.Configuration;
using IdentityModel; using IdentityModel;
using LaunchDarkly.Sdk.Server;
using LaunchDarkly.Sdk.Server.Interfaces;
using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@ -222,7 +224,7 @@ public static class ServiceCollectionExtensions
return new LookupClient(options); return new LookupClient(options);
}); });
services.AddSingleton<IDnsResolverService, DnsResolverService>(); services.AddSingleton<IDnsResolverService, DnsResolverService>();
services.AddSingleton<IFeatureService, LaunchDarklyFeatureService>(); services.AddOptionality();
services.AddTokenizers(); services.AddTokenizers();
if (CoreHelpers.SettingHasValue(globalSettings.ServiceBus.ConnectionString) && if (CoreHelpers.SettingHasValue(globalSettings.ServiceBus.ConnectionString) &&
@ -426,8 +428,6 @@ public static class ServiceCollectionExtensions
return identityBuilder; return identityBuilder;
} }
public static void AddIdentityAuthenticationServices( public static void AddIdentityAuthenticationServices(
this IServiceCollection services, GlobalSettings globalSettings, IWebHostEnvironment environment, this IServiceCollection services, GlobalSettings globalSettings, IWebHostEnvironment environment,
Action<AuthorizationOptions> addAuthorization) Action<AuthorizationOptions> addAuthorization)
@ -727,4 +727,17 @@ public static class ServiceCollectionExtensions
}); });
}); });
} }
public static IServiceCollection AddOptionality(this IServiceCollection services)
{
services.AddSingleton<ILdClient>(s =>
{
return new LdClient(LaunchDarklyFeatureService.GetConfiguredClient(
s.GetRequiredService<GlobalSettings>()));
});
services.AddScoped<IFeatureService, LaunchDarklyFeatureService>();
return services;
}
} }

View File

@ -14,7 +14,6 @@ using Bit.Core.Auth.Models.Api.Request.Accounts;
using Bit.Core.Auth.Services; using Bit.Core.Auth.Services;
using Bit.Core.Auth.UserFeatures.UserKey; using Bit.Core.Auth.UserFeatures.UserKey;
using Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces; using Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces;
using Bit.Core.Context;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
@ -54,7 +53,6 @@ public class AccountsControllerTests : IDisposable
private readonly ISetInitialMasterPasswordCommand _setInitialMasterPasswordCommand; private readonly ISetInitialMasterPasswordCommand _setInitialMasterPasswordCommand;
private readonly IRotateUserKeyCommand _rotateUserKeyCommand; private readonly IRotateUserKeyCommand _rotateUserKeyCommand;
private readonly IFeatureService _featureService; private readonly IFeatureService _featureService;
private readonly ICurrentContext _currentContext;
private readonly IRotationValidator<IEnumerable<CipherWithIdRequestModel>, IEnumerable<Cipher>> _cipherValidator; private readonly IRotationValidator<IEnumerable<CipherWithIdRequestModel>, IEnumerable<Cipher>> _cipherValidator;
private readonly IRotationValidator<IEnumerable<FolderWithIdRequestModel>, IEnumerable<Folder>> _folderValidator; private readonly IRotationValidator<IEnumerable<FolderWithIdRequestModel>, IEnumerable<Folder>> _folderValidator;
@ -84,7 +82,6 @@ public class AccountsControllerTests : IDisposable
_setInitialMasterPasswordCommand = Substitute.For<ISetInitialMasterPasswordCommand>(); _setInitialMasterPasswordCommand = Substitute.For<ISetInitialMasterPasswordCommand>();
_rotateUserKeyCommand = Substitute.For<IRotateUserKeyCommand>(); _rotateUserKeyCommand = Substitute.For<IRotateUserKeyCommand>();
_featureService = Substitute.For<IFeatureService>(); _featureService = Substitute.For<IFeatureService>();
_currentContext = Substitute.For<ICurrentContext>();
_cipherValidator = _cipherValidator =
Substitute.For<IRotationValidator<IEnumerable<CipherWithIdRequestModel>, IEnumerable<Cipher>>>(); Substitute.For<IRotationValidator<IEnumerable<CipherWithIdRequestModel>, IEnumerable<Cipher>>>();
_folderValidator = _folderValidator =
@ -113,7 +110,6 @@ public class AccountsControllerTests : IDisposable
_setInitialMasterPasswordCommand, _setInitialMasterPasswordCommand,
_rotateUserKeyCommand, _rotateUserKeyCommand,
_featureService, _featureService,
_currentContext,
_cipherValidator, _cipherValidator,
_folderValidator, _folderValidator,
_sendValidator, _sendValidator,

View File

@ -1,6 +1,5 @@
using AutoFixture.Xunit2; using AutoFixture.Xunit2;
using Bit.Api.Controllers; using Bit.Api.Controllers;
using Bit.Core.Context;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Settings; using Bit.Core.Settings;
using NSubstitute; using NSubstitute;
@ -13,17 +12,14 @@ public class ConfigControllerTests : IDisposable
private readonly ConfigController _sut; private readonly ConfigController _sut;
private readonly GlobalSettings _globalSettings; private readonly GlobalSettings _globalSettings;
private readonly IFeatureService _featureService; private readonly IFeatureService _featureService;
private readonly ICurrentContext _currentContext;
public ConfigControllerTests() public ConfigControllerTests()
{ {
_globalSettings = new GlobalSettings(); _globalSettings = new GlobalSettings();
_currentContext = Substitute.For<ICurrentContext>();
_featureService = Substitute.For<IFeatureService>(); _featureService = Substitute.For<IFeatureService>();
_sut = new ConfigController( _sut = new ConfigController(
_globalSettings, _globalSettings,
_currentContext,
_featureService _featureService
); );
} }
@ -36,7 +32,7 @@ public class ConfigControllerTests : IDisposable
[Theory, AutoData] [Theory, AutoData]
public void GetConfigs_WithFeatureStates(Dictionary<string, object> featureStates) public void GetConfigs_WithFeatureStates(Dictionary<string, object> featureStates)
{ {
_featureService.GetAll(_currentContext).Returns(featureStates); _featureService.GetAll().Returns(featureStates);
var response = _sut.GetConfigs(); var response = _sut.GetConfigs();

View File

@ -114,7 +114,7 @@ public class CollectionServiceTest
collection.Id = default; collection.Id = default;
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization); sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
sutProvider.GetDependency<IFeatureService>() sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1, Arg.Any<ICurrentContext>(), Arg.Any<bool>()) .IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1, Arg.Any<bool>())
.Returns(true); .Returns(true);
organization.AllowAdminAccessToAllCollectionItems = false; organization.AllowAdminAccessToAllCollectionItems = false;

View File

@ -4,6 +4,7 @@ using Bit.Core.Services;
using Bit.Core.Settings; using Bit.Core.Settings;
using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.AutoFixture.Attributes;
using LaunchDarkly.Sdk.Server.Interfaces;
using NSubstitute; using NSubstitute;
using Xunit; using Xunit;
@ -19,9 +20,16 @@ public class LaunchDarklyFeatureServiceTests
{ {
globalSettings.ProjectName = "LaunchDarkly Tests"; globalSettings.ProjectName = "LaunchDarkly Tests";
var currentContext = Substitute.For<ICurrentContext>();
currentContext.UserId.Returns(Guid.NewGuid());
var client = Substitute.For<ILdClient>();
var fixture = new Fixture(); var fixture = new Fixture();
return new SutProvider<LaunchDarklyFeatureService>(fixture) return new SutProvider<LaunchDarklyFeatureService>(fixture)
.SetDependency(globalSettings) .SetDependency(globalSettings)
.SetDependency(currentContext)
.SetDependency(client)
.Create(); .Create();
} }
@ -30,10 +38,7 @@ public class LaunchDarklyFeatureServiceTests
{ {
var sutProvider = GetSutProvider(new Settings.GlobalSettings { SelfHosted = true }); var sutProvider = GetSutProvider(new Settings.GlobalSettings { SelfHosted = true });
var currentContext = Substitute.For<ICurrentContext>(); Assert.False(sutProvider.Sut.IsEnabled(key));
currentContext.UserId.Returns(Guid.NewGuid());
Assert.False(sutProvider.Sut.IsEnabled(key, currentContext));
} }
[Fact] [Fact]
@ -41,10 +46,7 @@ public class LaunchDarklyFeatureServiceTests
{ {
var sutProvider = GetSutProvider(new Settings.GlobalSettings()); var sutProvider = GetSutProvider(new Settings.GlobalSettings());
var currentContext = Substitute.For<ICurrentContext>(); Assert.False(sutProvider.Sut.IsEnabled(_fakeFeatureKey));
currentContext.UserId.Returns(Guid.NewGuid());
Assert.False(sutProvider.Sut.IsEnabled(_fakeFeatureKey, currentContext));
} }
[Fact(Skip = "For local development")] [Fact(Skip = "For local development")]
@ -54,10 +56,7 @@ public class LaunchDarklyFeatureServiceTests
var sutProvider = GetSutProvider(settings); var sutProvider = GetSutProvider(settings);
var currentContext = Substitute.For<ICurrentContext>(); Assert.False(sutProvider.Sut.IsEnabled(_fakeFeatureKey));
currentContext.UserId.Returns(Guid.NewGuid());
Assert.False(sutProvider.Sut.IsEnabled(_fakeFeatureKey, currentContext));
} }
[Fact(Skip = "For local development")] [Fact(Skip = "For local development")]
@ -67,10 +66,7 @@ public class LaunchDarklyFeatureServiceTests
var sutProvider = GetSutProvider(settings); var sutProvider = GetSutProvider(settings);
var currentContext = Substitute.For<ICurrentContext>(); Assert.Equal(0, sutProvider.Sut.GetIntVariation(_fakeFeatureKey));
currentContext.UserId.Returns(Guid.NewGuid());
Assert.Equal(0, sutProvider.Sut.GetIntVariation(_fakeFeatureKey, currentContext));
} }
[Fact(Skip = "For local development")] [Fact(Skip = "For local development")]
@ -80,10 +76,7 @@ public class LaunchDarklyFeatureServiceTests
var sutProvider = GetSutProvider(settings); var sutProvider = GetSutProvider(settings);
var currentContext = Substitute.For<ICurrentContext>(); Assert.Null(sutProvider.Sut.GetStringVariation(_fakeFeatureKey));
currentContext.UserId.Returns(Guid.NewGuid());
Assert.Null(sutProvider.Sut.GetStringVariation(_fakeFeatureKey, currentContext));
} }
[Fact(Skip = "For local development")] [Fact(Skip = "For local development")]
@ -91,10 +84,7 @@ public class LaunchDarklyFeatureServiceTests
{ {
var sutProvider = GetSutProvider(new Settings.GlobalSettings()); var sutProvider = GetSutProvider(new Settings.GlobalSettings());
var currentContext = Substitute.For<ICurrentContext>(); var results = sutProvider.Sut.GetAll();
currentContext.UserId.Returns(Guid.NewGuid());
var results = sutProvider.Sut.GetAll(currentContext);
Assert.NotNull(results); Assert.NotNull(results);
Assert.NotEmpty(results); Assert.NotEmpty(results);

View File

@ -64,7 +64,7 @@ public class RequireFeatureAttributeTests
var featureService = Substitute.For<IFeatureService>(); var featureService = Substitute.For<IFeatureService>();
var currentContext = Substitute.For<ICurrentContext>(); var currentContext = Substitute.For<ICurrentContext>();
featureService.IsEnabled(_testFeature, Arg.Any<ICurrentContext>()).Returns(enabled); featureService.IsEnabled(_testFeature).Returns(enabled);
services.AddSingleton(featureService); services.AddSingleton(featureService);
services.AddSingleton(currentContext); services.AddSingleton(currentContext);