mirror of
https://github.com/bitwarden/server.git
synced 2025-07-01 16:12:49 -05:00
Merge branch 'master' into feature/flexible-collections
This commit is contained in:
@ -1,6 +1,4 @@
|
|||||||
using Bit.Core.OrganizationFeatures.OrganizationUsers;
|
using Bit.Scim.Groups;
|
||||||
using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
|
|
||||||
using Bit.Scim.Groups;
|
|
||||||
using Bit.Scim.Groups.Interfaces;
|
using Bit.Scim.Groups.Interfaces;
|
||||||
using Bit.Scim.Users;
|
using Bit.Scim.Users;
|
||||||
using Bit.Scim.Users.Interfaces;
|
using Bit.Scim.Users.Interfaces;
|
||||||
@ -23,7 +21,6 @@ public static class ScimServiceCollectionExtensions
|
|||||||
|
|
||||||
public static void AddScimUserCommands(this IServiceCollection services)
|
public static void AddScimUserCommands(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddScoped<IDeleteOrganizationUserCommand, DeleteOrganizationUserCommand>();
|
|
||||||
services.AddScoped<IPatchUserCommand, PatchUserCommand>();
|
services.AddScoped<IPatchUserCommand, PatchUserCommand>();
|
||||||
services.AddScoped<IPostUserCommand, PostUserCommand>();
|
services.AddScoped<IPostUserCommand, PostUserCommand>();
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@
|
|||||||
<div class="float-right">
|
<div class="float-right">
|
||||||
@if (org.Status == OrganizationStatusType.Pending)
|
@if (org.Status == OrganizationStatusType.Pending)
|
||||||
{
|
{
|
||||||
<a href="#" class="float-right" onclick="return resendOwnerInvite('@org.OrganizationId', '@org.OrganizationName');">
|
<a href="#" class="float-right" onclick="return resendOwnerInvite('@org.OrganizationId');">
|
||||||
<i class="fa fa-envelope-o fa-lg" title="Resend Setup Invite"></i>
|
<i class="fa fa-envelope-o fa-lg" title="Resend Setup Invite"></i>
|
||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
<script>
|
<script>
|
||||||
function resendOwnerInvite(orgId, orgName) {
|
function resendOwnerInvite(orgId) {
|
||||||
if (confirm('Resend invite to "' + orgName + '"?')) {
|
if (confirm('Resend invite to organization?')) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "POST",
|
type: "POST",
|
||||||
url: '@Url.Action("ResendOwnerInvite", "Organizations")' + '?id=' + orgId,
|
url: '@Url.Action("ResendOwnerInvite", "Organizations")' + '?id=' + orgId,
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
contentType: false,
|
contentType: false,
|
||||||
processData: false,
|
processData: false,
|
||||||
success: function (response) {
|
success: function (response) {
|
||||||
alert('Invitation has been resent!');
|
alert('Invitation has been resent!');
|
||||||
},
|
},
|
||||||
error: function (response) {
|
error: function (response) {
|
||||||
alert("Error!");
|
alert("Error!");
|
||||||
@ -17,4 +17,4 @@
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -236,7 +236,7 @@ public class TwoFactorController : Controller
|
|||||||
[HttpPost("get-webauthn")]
|
[HttpPost("get-webauthn")]
|
||||||
public async Task<TwoFactorWebAuthnResponseModel> GetWebAuthn([FromBody] SecretVerificationRequestModel model)
|
public async Task<TwoFactorWebAuthnResponseModel> GetWebAuthn([FromBody] SecretVerificationRequestModel model)
|
||||||
{
|
{
|
||||||
var user = await CheckAsync(model, true);
|
var user = await CheckAsync(model, false);
|
||||||
var response = new TwoFactorWebAuthnResponseModel(user);
|
var response = new TwoFactorWebAuthnResponseModel(user);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
@ -245,7 +245,7 @@ public class TwoFactorController : Controller
|
|||||||
[ApiExplorerSettings(IgnoreApi = true)] // Disable Swagger due to CredentialCreateOptions not converting properly
|
[ApiExplorerSettings(IgnoreApi = true)] // Disable Swagger due to CredentialCreateOptions not converting properly
|
||||||
public async Task<CredentialCreateOptions> GetWebAuthnChallenge([FromBody] SecretVerificationRequestModel model)
|
public async Task<CredentialCreateOptions> GetWebAuthnChallenge([FromBody] SecretVerificationRequestModel model)
|
||||||
{
|
{
|
||||||
var user = await CheckAsync(model, true);
|
var user = await CheckAsync(model, false);
|
||||||
var reg = await _userService.StartWebAuthnRegistrationAsync(user);
|
var reg = await _userService.StartWebAuthnRegistrationAsync(user);
|
||||||
return reg;
|
return reg;
|
||||||
}
|
}
|
||||||
@ -254,7 +254,7 @@ public class TwoFactorController : Controller
|
|||||||
[HttpPost("webauthn")]
|
[HttpPost("webauthn")]
|
||||||
public async Task<TwoFactorWebAuthnResponseModel> PutWebAuthn([FromBody] TwoFactorWebAuthnRequestModel model)
|
public async Task<TwoFactorWebAuthnResponseModel> PutWebAuthn([FromBody] TwoFactorWebAuthnRequestModel model)
|
||||||
{
|
{
|
||||||
var user = await CheckAsync(model, true);
|
var user = await CheckAsync(model, false);
|
||||||
|
|
||||||
var success = await _userService.CompleteWebAuthRegistrationAsync(
|
var success = await _userService.CompleteWebAuthRegistrationAsync(
|
||||||
user, model.Id.Value, model.Name, model.DeviceResponse);
|
user, model.Id.Value, model.Name, model.DeviceResponse);
|
||||||
@ -271,7 +271,7 @@ public class TwoFactorController : Controller
|
|||||||
public async Task<TwoFactorWebAuthnResponseModel> DeleteWebAuthn(
|
public async Task<TwoFactorWebAuthnResponseModel> DeleteWebAuthn(
|
||||||
[FromBody] TwoFactorWebAuthnDeleteRequestModel model)
|
[FromBody] TwoFactorWebAuthnDeleteRequestModel model)
|
||||||
{
|
{
|
||||||
var user = await CheckAsync(model, true);
|
var user = await CheckAsync(model, false);
|
||||||
await _userService.DeleteWebAuthnKeyAsync(user, model.Id.Value);
|
await _userService.DeleteWebAuthnKeyAsync(user, model.Id.Value);
|
||||||
var response = new TwoFactorWebAuthnResponseModel(user);
|
var response = new TwoFactorWebAuthnResponseModel(user);
|
||||||
return response;
|
return response;
|
||||||
|
@ -30,6 +30,7 @@ public class OrganizationUsersController : Controller
|
|||||||
private readonly ICurrentContext _currentContext;
|
private readonly ICurrentContext _currentContext;
|
||||||
private readonly ICountNewSmSeatsRequiredQuery _countNewSmSeatsRequiredQuery;
|
private readonly ICountNewSmSeatsRequiredQuery _countNewSmSeatsRequiredQuery;
|
||||||
private readonly IUpdateSecretsManagerSubscriptionCommand _updateSecretsManagerSubscriptionCommand;
|
private readonly IUpdateSecretsManagerSubscriptionCommand _updateSecretsManagerSubscriptionCommand;
|
||||||
|
private readonly IUpdateOrganizationUserGroupsCommand _updateOrganizationUserGroupsCommand;
|
||||||
|
|
||||||
public OrganizationUsersController(
|
public OrganizationUsersController(
|
||||||
IOrganizationRepository organizationRepository,
|
IOrganizationRepository organizationRepository,
|
||||||
@ -41,7 +42,8 @@ public class OrganizationUsersController : Controller
|
|||||||
IPolicyRepository policyRepository,
|
IPolicyRepository policyRepository,
|
||||||
ICurrentContext currentContext,
|
ICurrentContext currentContext,
|
||||||
ICountNewSmSeatsRequiredQuery countNewSmSeatsRequiredQuery,
|
ICountNewSmSeatsRequiredQuery countNewSmSeatsRequiredQuery,
|
||||||
IUpdateSecretsManagerSubscriptionCommand updateSecretsManagerSubscriptionCommand)
|
IUpdateSecretsManagerSubscriptionCommand updateSecretsManagerSubscriptionCommand,
|
||||||
|
IUpdateOrganizationUserGroupsCommand updateOrganizationUserGroupsCommand)
|
||||||
{
|
{
|
||||||
_organizationRepository = organizationRepository;
|
_organizationRepository = organizationRepository;
|
||||||
_organizationUserRepository = organizationUserRepository;
|
_organizationUserRepository = organizationUserRepository;
|
||||||
@ -53,6 +55,7 @@ public class OrganizationUsersController : Controller
|
|||||||
_currentContext = currentContext;
|
_currentContext = currentContext;
|
||||||
_countNewSmSeatsRequiredQuery = countNewSmSeatsRequiredQuery;
|
_countNewSmSeatsRequiredQuery = countNewSmSeatsRequiredQuery;
|
||||||
_updateSecretsManagerSubscriptionCommand = updateSecretsManagerSubscriptionCommand;
|
_updateSecretsManagerSubscriptionCommand = updateSecretsManagerSubscriptionCommand;
|
||||||
|
_updateOrganizationUserGroupsCommand = updateOrganizationUserGroupsCommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{id}")]
|
[HttpGet("{id}")]
|
||||||
@ -308,7 +311,7 @@ public class OrganizationUsersController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
var loggedInUserId = _userService.GetProperUserId(User);
|
var loggedInUserId = _userService.GetProperUserId(User);
|
||||||
await _organizationService.UpdateUserGroupsAsync(organizationUser, model.GroupIds.Select(g => new Guid(g)), loggedInUserId);
|
await _updateOrganizationUserGroupsCommand.UpdateUserGroupsAsync(organizationUser, model.GroupIds.Select(g => new Guid(g)), loggedInUserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPut("{userId}/reset-password-enrollment")]
|
[HttpPut("{userId}/reset-password-enrollment")]
|
||||||
|
@ -3,6 +3,7 @@ using Bit.Api.Models.Public.Request;
|
|||||||
using Bit.Api.Models.Public.Response;
|
using Bit.Api.Models.Public.Response;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Models.Business;
|
using Bit.Core.Models.Business;
|
||||||
|
using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
@ -19,19 +20,22 @@ public class MembersController : Controller
|
|||||||
private readonly IOrganizationService _organizationService;
|
private readonly IOrganizationService _organizationService;
|
||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
private readonly ICurrentContext _currentContext;
|
private readonly ICurrentContext _currentContext;
|
||||||
|
private readonly IUpdateOrganizationUserGroupsCommand _updateOrganizationUserGroupsCommand;
|
||||||
|
|
||||||
public MembersController(
|
public MembersController(
|
||||||
IOrganizationUserRepository organizationUserRepository,
|
IOrganizationUserRepository organizationUserRepository,
|
||||||
IGroupRepository groupRepository,
|
IGroupRepository groupRepository,
|
||||||
IOrganizationService organizationService,
|
IOrganizationService organizationService,
|
||||||
IUserService userService,
|
IUserService userService,
|
||||||
ICurrentContext currentContext)
|
ICurrentContext currentContext,
|
||||||
|
IUpdateOrganizationUserGroupsCommand updateOrganizationUserGroupsCommand)
|
||||||
{
|
{
|
||||||
_organizationUserRepository = organizationUserRepository;
|
_organizationUserRepository = organizationUserRepository;
|
||||||
_groupRepository = groupRepository;
|
_groupRepository = groupRepository;
|
||||||
_organizationService = organizationService;
|
_organizationService = organizationService;
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
_currentContext = currentContext;
|
_currentContext = currentContext;
|
||||||
|
_updateOrganizationUserGroupsCommand = updateOrganizationUserGroupsCommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -183,7 +187,7 @@ public class MembersController : Controller
|
|||||||
{
|
{
|
||||||
return new NotFoundResult();
|
return new NotFoundResult();
|
||||||
}
|
}
|
||||||
await _organizationService.UpdateUserGroupsAsync(existingUser, model.GroupIds, null);
|
await _updateOrganizationUserGroupsCommand.UpdateUserGroupsAsync(existingUser, model.GroupIds, null);
|
||||||
return new OkResult();
|
return new OkResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,10 +28,6 @@ public class WebAuthnTokenProvider : IUserTwoFactorTokenProvider<User>
|
|||||||
public async Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<User> manager, User user)
|
public async Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<User> manager, User user)
|
||||||
{
|
{
|
||||||
var userService = _serviceProvider.GetRequiredService<IUserService>();
|
var userService = _serviceProvider.GetRequiredService<IUserService>();
|
||||||
if (!(await userService.CanAccessPremium(user)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var webAuthnProvider = user.GetTwoFactorProvider(TwoFactorProviderType.WebAuthn);
|
var webAuthnProvider = user.GetTwoFactorProvider(TwoFactorProviderType.WebAuthn);
|
||||||
if (!HasProperMetaData(webAuthnProvider))
|
if (!HasProperMetaData(webAuthnProvider))
|
||||||
@ -45,10 +41,6 @@ public class WebAuthnTokenProvider : IUserTwoFactorTokenProvider<User>
|
|||||||
public async Task<string> GenerateAsync(string purpose, UserManager<User> manager, User user)
|
public async Task<string> GenerateAsync(string purpose, UserManager<User> manager, User user)
|
||||||
{
|
{
|
||||||
var userService = _serviceProvider.GetRequiredService<IUserService>();
|
var userService = _serviceProvider.GetRequiredService<IUserService>();
|
||||||
if (!(await userService.CanAccessPremium(user)))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.WebAuthn);
|
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.WebAuthn);
|
||||||
var keys = LoadKeys(provider);
|
var keys = LoadKeys(provider);
|
||||||
@ -81,7 +73,7 @@ public class WebAuthnTokenProvider : IUserTwoFactorTokenProvider<User>
|
|||||||
public async Task<bool> ValidateAsync(string purpose, string token, UserManager<User> manager, User user)
|
public async Task<bool> ValidateAsync(string purpose, string token, UserManager<User> manager, User user)
|
||||||
{
|
{
|
||||||
var userService = _serviceProvider.GetRequiredService<IUserService>();
|
var userService = _serviceProvider.GetRequiredService<IUserService>();
|
||||||
if (!(await userService.CanAccessPremium(user)) || string.IsNullOrWhiteSpace(token))
|
if (string.IsNullOrWhiteSpace(token))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,6 @@ public class TwoFactorProvider
|
|||||||
case TwoFactorProviderType.Duo:
|
case TwoFactorProviderType.Duo:
|
||||||
case TwoFactorProviderType.YubiKey:
|
case TwoFactorProviderType.YubiKey:
|
||||||
case TwoFactorProviderType.U2f: // Keep to ensure old U2f keys are considered premium
|
case TwoFactorProviderType.U2f: // Keep to ensure old U2f keys are considered premium
|
||||||
case TwoFactorProviderType.WebAuthn:
|
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
|
@ -46,4 +46,13 @@ public static class FeatureFlagKeys
|
|||||||
.Select(x => (string)x.GetRawConstantValue())
|
.Select(x => (string)x.GetRawConstantValue())
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Dictionary<string, string> GetLocalOverrideFlagValues()
|
||||||
|
{
|
||||||
|
// place overriding values when needed locally (offline), or return null
|
||||||
|
return new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{ TrustedDeviceEncryption, "true" }
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,7 @@ public static class OrganizationServiceCollectionExtensions
|
|||||||
services.AddOrganizationLicenseCommandsQueries();
|
services.AddOrganizationLicenseCommandsQueries();
|
||||||
services.AddOrganizationDomainCommandsQueries();
|
services.AddOrganizationDomainCommandsQueries();
|
||||||
services.AddOrganizationAuthCommands();
|
services.AddOrganizationAuthCommands();
|
||||||
|
services.AddOrganizationUserCommands();
|
||||||
services.AddOrganizationUserCommandsQueries();
|
services.AddOrganizationUserCommandsQueries();
|
||||||
services.AddBaseOrganizationSubscriptionCommandsQueries();
|
services.AddBaseOrganizationSubscriptionCommandsQueries();
|
||||||
}
|
}
|
||||||
@ -81,6 +82,12 @@ public static class OrganizationServiceCollectionExtensions
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void AddOrganizationUserCommands(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddScoped<IDeleteOrganizationUserCommand, DeleteOrganizationUserCommand>();
|
||||||
|
services.AddScoped<IUpdateOrganizationUserGroupsCommand, UpdateOrganizationUserGroupsCommand>();
|
||||||
|
}
|
||||||
|
|
||||||
private static void AddOrganizationApiKeyCommandsQueries(this IServiceCollection services)
|
private static void AddOrganizationApiKeyCommandsQueries(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddScoped<IGetOrganizationApiKeyQuery, GetOrganizationApiKeyQuery>();
|
services.AddScoped<IGetOrganizationApiKeyQuery, GetOrganizationApiKeyQuery>();
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
using Bit.Core.Entities;
|
||||||
|
|
||||||
|
namespace Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||||
|
|
||||||
|
public interface IUpdateOrganizationUserGroupsCommand
|
||||||
|
{
|
||||||
|
Task UpdateUserGroupsAsync(OrganizationUser organizationUser, IEnumerable<Guid> groupIds, Guid? loggedInUserId);
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
using Bit.Core.Entities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||||
|
using Bit.Core.Repositories;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
|
||||||
|
namespace Bit.Core.OrganizationFeatures.OrganizationUsers;
|
||||||
|
|
||||||
|
public class UpdateOrganizationUserGroupsCommand : IUpdateOrganizationUserGroupsCommand
|
||||||
|
{
|
||||||
|
private readonly IEventService _eventService;
|
||||||
|
private readonly IOrganizationService _organizationService;
|
||||||
|
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||||
|
|
||||||
|
public UpdateOrganizationUserGroupsCommand(
|
||||||
|
IEventService eventService,
|
||||||
|
IOrganizationService organizationService,
|
||||||
|
IOrganizationUserRepository organizationUserRepository)
|
||||||
|
{
|
||||||
|
_eventService = eventService;
|
||||||
|
_organizationService = organizationService;
|
||||||
|
_organizationUserRepository = organizationUserRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateUserGroupsAsync(OrganizationUser organizationUser, IEnumerable<Guid> groupIds, Guid? loggedInUserId)
|
||||||
|
{
|
||||||
|
if (loggedInUserId.HasValue)
|
||||||
|
{
|
||||||
|
await _organizationService.ValidateOrganizationUserUpdatePermissions(organizationUser.OrganizationId, organizationUser.Type, null, organizationUser.GetPermissions());
|
||||||
|
}
|
||||||
|
await _organizationUserRepository.UpdateGroupsAsync(organizationUser.Id, groupIds);
|
||||||
|
await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_UpdatedGroups);
|
||||||
|
}
|
||||||
|
}
|
@ -54,7 +54,6 @@ public interface IOrganizationService
|
|||||||
Task DeleteUserAsync(Guid organizationId, Guid userId);
|
Task DeleteUserAsync(Guid organizationId, Guid userId);
|
||||||
Task<List<Tuple<OrganizationUser, string>>> DeleteUsersAsync(Guid organizationId,
|
Task<List<Tuple<OrganizationUser, string>>> DeleteUsersAsync(Guid organizationId,
|
||||||
IEnumerable<Guid> organizationUserIds, Guid? deletingUserId);
|
IEnumerable<Guid> organizationUserIds, Guid? deletingUserId);
|
||||||
Task UpdateUserGroupsAsync(OrganizationUser organizationUser, IEnumerable<Guid> groupIds, Guid? loggedInUserId);
|
|
||||||
Task UpdateUserResetPasswordEnrollmentAsync(Guid organizationId, Guid userId, string resetPasswordKey, Guid? callingUserId);
|
Task UpdateUserResetPasswordEnrollmentAsync(Guid organizationId, Guid userId, string resetPasswordKey, Guid? callingUserId);
|
||||||
Task ImportAsync(Guid organizationId, Guid? importingUserId, IEnumerable<ImportedGroup> groups,
|
Task ImportAsync(Guid organizationId, Guid? importingUserId, IEnumerable<ImportedGroup> groups,
|
||||||
IEnumerable<ImportedOrganizationUser> newUsers, IEnumerable<string> removeUserExternalIds,
|
IEnumerable<ImportedOrganizationUser> newUsers, IEnumerable<string> removeUserExternalIds,
|
||||||
@ -82,4 +81,6 @@ public interface IOrganizationService
|
|||||||
|
|
||||||
void ValidatePasswordManagerPlan(Models.StaticStore.Plan plan, OrganizationUpgrade upgrade);
|
void ValidatePasswordManagerPlan(Models.StaticStore.Plan plan, OrganizationUpgrade upgrade);
|
||||||
void ValidateSecretsManagerPlan(Models.StaticStore.Plan plan, OrganizationUpgrade upgrade);
|
void ValidateSecretsManagerPlan(Models.StaticStore.Plan plan, OrganizationUpgrade upgrade);
|
||||||
|
Task ValidateOrganizationUserUpdatePermissions(Guid organizationId, OrganizationUserType newType,
|
||||||
|
OrganizationUserType? oldType, Permissions permissions);
|
||||||
}
|
}
|
||||||
|
@ -29,24 +29,12 @@ public class LaunchDarklyFeatureService : IFeatureService, IDisposable
|
|||||||
// support configuration directly from settings
|
// support configuration directly from settings
|
||||||
else if (globalSettings.LaunchDarkly?.FlagValues?.Any() is true)
|
else if (globalSettings.LaunchDarkly?.FlagValues?.Any() is true)
|
||||||
{
|
{
|
||||||
var source = TestData.DataSource();
|
ldConfig.DataSource(BuildDataSource(globalSettings.LaunchDarkly.FlagValues));
|
||||||
foreach (var kvp in globalSettings.LaunchDarkly.FlagValues)
|
}
|
||||||
{
|
// support local overrides
|
||||||
if (bool.TryParse(kvp.Value, out bool boolValue))
|
else if (FeatureFlagKeys.GetLocalOverrideFlagValues()?.Any() is true)
|
||||||
{
|
{
|
||||||
source.Update(source.Flag(kvp.Key).ValueForAll(LaunchDarkly.Sdk.LdValue.Of(boolValue)));
|
ldConfig.DataSource(BuildDataSource(FeatureFlagKeys.GetLocalOverrideFlagValues()));
|
||||||
}
|
|
||||||
else if (int.TryParse(kvp.Value, out int intValue))
|
|
||||||
{
|
|
||||||
source.Update(source.Flag(kvp.Key).ValueForAll(LaunchDarkly.Sdk.LdValue.Of(intValue)));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
source.Update(source.Flag(kvp.Key).ValueForAll(LaunchDarkly.Sdk.LdValue.Of(kvp.Value)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ldConfig.DataSource(source);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -187,4 +175,26 @@ public class LaunchDarklyFeatureService : IFeatureService, IDisposable
|
|||||||
|
|
||||||
return builder.Build();
|
return builder.Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private TestData BuildDataSource(Dictionary<string, string> values)
|
||||||
|
{
|
||||||
|
var source = TestData.DataSource();
|
||||||
|
foreach (var kvp in values)
|
||||||
|
{
|
||||||
|
if (bool.TryParse(kvp.Value, out bool boolValue))
|
||||||
|
{
|
||||||
|
source.Update(source.Flag(kvp.Key).ValueForAll(LaunchDarkly.Sdk.LdValue.Of(boolValue)));
|
||||||
|
}
|
||||||
|
else if (int.TryParse(kvp.Value, out int intValue))
|
||||||
|
{
|
||||||
|
source.Update(source.Flag(kvp.Key).ValueForAll(LaunchDarkly.Sdk.LdValue.Of(intValue)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
source.Update(source.Flag(kvp.Key).ValueForAll(LaunchDarkly.Sdk.LdValue.Of(kvp.Value)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return source;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1582,17 +1582,6 @@ public class OrganizationService : IOrganizationService
|
|||||||
return hasOtherOwner;
|
return hasOtherOwner;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateUserGroupsAsync(OrganizationUser organizationUser, IEnumerable<Guid> groupIds, Guid? loggedInUserId)
|
|
||||||
{
|
|
||||||
if (loggedInUserId.HasValue)
|
|
||||||
{
|
|
||||||
await ValidateOrganizationUserUpdatePermissions(organizationUser.OrganizationId, organizationUser.Type, null, organizationUser.GetPermissions());
|
|
||||||
}
|
|
||||||
await _organizationUserRepository.UpdateGroupsAsync(organizationUser.Id, groupIds);
|
|
||||||
await _eventService.LogOrganizationUserEventAsync(organizationUser,
|
|
||||||
EventType.OrganizationUser_UpdatedGroups);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task UpdateUserResetPasswordEnrollmentAsync(Guid organizationId, Guid userId, string resetPasswordKey, Guid? callingUserId)
|
public async Task UpdateUserResetPasswordEnrollmentAsync(Guid organizationId, Guid userId, string resetPasswordKey, Guid? callingUserId)
|
||||||
{
|
{
|
||||||
// Org User must be the same as the calling user and the organization ID associated with the user must match passed org ID
|
// Org User must be the same as the calling user and the organization ID associated with the user must match passed org ID
|
||||||
@ -2032,7 +2021,7 @@ public class OrganizationService : IOrganizationService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ValidateOrganizationUserUpdatePermissions(Guid organizationId, OrganizationUserType newType, OrganizationUserType? oldType, Permissions permissions)
|
public async Task ValidateOrganizationUserUpdatePermissions(Guid organizationId, OrganizationUserType newType, OrganizationUserType? oldType, Permissions permissions)
|
||||||
{
|
{
|
||||||
if (await _currentContext.OrganizationOwner(organizationId))
|
if (await _currentContext.OrganizationOwner(organizationId))
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
using Bit.Core.Entities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.OrganizationFeatures.OrganizationUsers;
|
||||||
|
using Bit.Core.Repositories;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
using Bit.Test.Common.AutoFixture;
|
||||||
|
using Bit.Test.Common.AutoFixture.Attributes;
|
||||||
|
using NSubstitute;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Bit.Core.Test.OrganizationFeatures.OrganizationUsers;
|
||||||
|
|
||||||
|
[SutProviderCustomize]
|
||||||
|
public class UpdateOrganizationUserGroupsCommandTests
|
||||||
|
{
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task UpdateUserGroups_Passes(
|
||||||
|
OrganizationUser organizationUser,
|
||||||
|
IEnumerable<Guid> groupIds,
|
||||||
|
SutProvider<UpdateOrganizationUserGroupsCommand> sutProvider)
|
||||||
|
{
|
||||||
|
await sutProvider.Sut.UpdateUserGroupsAsync(organizationUser, groupIds, null);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IOrganizationService>().DidNotReceiveWithAnyArgs()
|
||||||
|
.ValidateOrganizationUserUpdatePermissions(default, default, default, default);
|
||||||
|
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1)
|
||||||
|
.UpdateGroupsAsync(organizationUser.Id, groupIds);
|
||||||
|
await sutProvider.GetDependency<IEventService>().Received(1)
|
||||||
|
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_UpdatedGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task UpdateUserGroups_WithSavingUserId_Passes(
|
||||||
|
OrganizationUser organizationUser,
|
||||||
|
IEnumerable<Guid> groupIds,
|
||||||
|
Guid savingUserId,
|
||||||
|
SutProvider<UpdateOrganizationUserGroupsCommand> sutProvider)
|
||||||
|
{
|
||||||
|
organizationUser.Permissions = null;
|
||||||
|
|
||||||
|
await sutProvider.Sut.UpdateUserGroupsAsync(organizationUser, groupIds, savingUserId);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IOrganizationService>().Received(1)
|
||||||
|
.ValidateOrganizationUserUpdatePermissions(organizationUser.OrganizationId, organizationUser.Type, null, organizationUser.GetPermissions());
|
||||||
|
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1)
|
||||||
|
.UpdateGroupsAsync(organizationUser.Id, groupIds);
|
||||||
|
await sutProvider.GetDependency<IEventService>().Received(1)
|
||||||
|
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_UpdatedGroups);
|
||||||
|
}
|
||||||
|
}
|
@ -20,14 +20,6 @@ public class LaunchDarklyFeatureServiceTests
|
|||||||
.Create();
|
.Create();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Offline_WhenSelfHost()
|
|
||||||
{
|
|
||||||
var sutProvider = GetSutProvider(new Core.Settings.GlobalSettings() { SelfHosted = true });
|
|
||||||
|
|
||||||
Assert.False(sutProvider.Sut.IsOnline());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public void DefaultFeatureValue_WhenSelfHost(string key)
|
public void DefaultFeatureValue_WhenSelfHost(string key)
|
||||||
{
|
{
|
||||||
|
@ -1832,4 +1832,74 @@ public class OrganizationServiceTests
|
|||||||
|
|
||||||
sutProvider.Sut.ValidateSecretsManagerPlan(plan, signup);
|
sutProvider.Sut.ValidateSecretsManagerPlan(plan, signup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[OrganizationInviteCustomize(
|
||||||
|
InviteeUserType = OrganizationUserType.Owner,
|
||||||
|
InvitorUserType = OrganizationUserType.Admin
|
||||||
|
), BitAutoData]
|
||||||
|
public async Task ValidateOrganizationUserUpdatePermissions_WithAdminAddingOwner_Throws(
|
||||||
|
Guid organizationId,
|
||||||
|
OrganizationUserInvite organizationUserInvite,
|
||||||
|
SutProvider<OrganizationService> sutProvider)
|
||||||
|
{
|
||||||
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
|
() => sutProvider.Sut.ValidateOrganizationUserUpdatePermissions(organizationId, organizationUserInvite.Type.Value, null, organizationUserInvite.Permissions));
|
||||||
|
|
||||||
|
Assert.Contains("only an owner can configure another owner's account.", exception.Message.ToLowerInvariant());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[OrganizationInviteCustomize(
|
||||||
|
InviteeUserType = OrganizationUserType.Admin,
|
||||||
|
InvitorUserType = OrganizationUserType.Owner
|
||||||
|
), BitAutoData]
|
||||||
|
public async Task ValidateOrganizationUserUpdatePermissions_WithoutManageUsersPermission_Throws(
|
||||||
|
Guid organizationId,
|
||||||
|
OrganizationUserInvite organizationUserInvite,
|
||||||
|
SutProvider<OrganizationService> sutProvider)
|
||||||
|
{
|
||||||
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
|
() => sutProvider.Sut.ValidateOrganizationUserUpdatePermissions(organizationId, organizationUserInvite.Type.Value, null, organizationUserInvite.Permissions));
|
||||||
|
|
||||||
|
Assert.Contains("your account does not have permission to manage users.", exception.Message.ToLowerInvariant());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[OrganizationInviteCustomize(
|
||||||
|
InviteeUserType = OrganizationUserType.Admin,
|
||||||
|
InvitorUserType = OrganizationUserType.Custom
|
||||||
|
), BitAutoData]
|
||||||
|
public async Task ValidateOrganizationUserUpdatePermissions_WithCustomAddingAdmin_Throws(
|
||||||
|
Guid organizationId,
|
||||||
|
OrganizationUserInvite organizationUserInvite,
|
||||||
|
SutProvider<OrganizationService> sutProvider)
|
||||||
|
{
|
||||||
|
sutProvider.GetDependency<ICurrentContext>().ManageUsers(organizationId).Returns(true);
|
||||||
|
|
||||||
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
|
() => sutProvider.Sut.ValidateOrganizationUserUpdatePermissions(organizationId, organizationUserInvite.Type.Value, null, organizationUserInvite.Permissions));
|
||||||
|
|
||||||
|
Assert.Contains("custom users can not manage admins or owners.", exception.Message.ToLowerInvariant());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[OrganizationInviteCustomize(
|
||||||
|
InviteeUserType = OrganizationUserType.Custom,
|
||||||
|
InvitorUserType = OrganizationUserType.Custom
|
||||||
|
), BitAutoData]
|
||||||
|
public async Task ValidateOrganizationUserUpdatePermissions_WithCustomAddingUser_WithoutPermissions_Throws(
|
||||||
|
Guid organizationId,
|
||||||
|
OrganizationUserInvite organizationUserInvite,
|
||||||
|
SutProvider<OrganizationService> sutProvider)
|
||||||
|
{
|
||||||
|
var invitePermissions = new Permissions { AccessReports = true };
|
||||||
|
sutProvider.GetDependency<ICurrentContext>().ManageUsers(organizationId).Returns(true);
|
||||||
|
sutProvider.GetDependency<ICurrentContext>().AccessReports(organizationId).Returns(false);
|
||||||
|
|
||||||
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
|
() => sutProvider.Sut.ValidateOrganizationUserUpdatePermissions(organizationId, organizationUserInvite.Type.Value, null, invitePermissions));
|
||||||
|
|
||||||
|
Assert.Contains("custom users can only grant the same custom permissions that they have.", exception.Message.ToLowerInvariant());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user