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

[AC-1707] Restrict provider access to items #3881

Merged
merged 19 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
0eae406
[AC-2274] Introduce CanEditAnyCiphersAsAdminAsync helper to replace E…
shane-melton Mar 7, 2024
f3bfa40
[AC-2274] Add unit tests for CanEditAnyCiphersAsAdmin helper
shane-melton Mar 8, 2024
3a8d909
[AC-2274] Add Jira ticket
shane-melton Mar 8, 2024
a6c3239
[AC-1707] Add feature flag
shane-melton Mar 7, 2024
994013b
[AC-1707] Update CanEditAnyCiphersAsAdmin to fail for providers when …
shane-melton Mar 8, 2024
fb5aa65
[AC-2274] Undo change to purge endpoint
shane-melton Mar 12, 2024
7c5a5a3
Merge branch 'vault/ac-2274/restrict-admin-cipher-endpoints' into vau…
shane-melton Mar 12, 2024
3ba268f
Merge branch 'main' into vault/ac-2274/restrict-admin-cipher-endpoints
shane-melton Mar 26, 2024
0fab3f8
[AC-2274] Update admin checks to account for unassigned ciphers
shane-melton Mar 28, 2024
31ee6ca
Merge branch 'main' into vault/ac-2274/restrict-admin-cipher-endpoints
shane-melton Apr 8, 2024
2ad8cdf
Merge branch 'main' into vault/ac-2274/restrict-admin-cipher-endpoints
shane-melton Apr 10, 2024
ac9ba77
Merge branch 'vault/ac-2274/restrict-admin-cipher-endpoints' into vau…
shane-melton Apr 10, 2024
8c5eea2
[AC-1707] Fix provider auth checks after merge with main
shane-melton Apr 10, 2024
b8ba980
[AC-1707] Fix tests after merge
shane-melton Apr 10, 2024
5923a98
Merge branch 'main' into vault/ac-1707/restrict-provider-access-to-items
shane-melton May 2, 2024
669ce47
Merge branch 'main' into vault/ac-1707/restrict-provider-access-to-items
shane-melton May 6, 2024
f630088
[AC-1707] Adjust CanEditCipherAsAdmin method to properly account for …
shane-melton May 6, 2024
692d10c
[AC-1707] Formatting
shane-melton May 7, 2024
ee76e76
Merge branch 'main' into vault/ac-1707/restrict-provider-access-to-items
shane-melton May 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
42 changes: 31 additions & 11 deletions src/Api/Vault/Controllers/CiphersController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -334,12 +334,32 @@
return await _currentContext.EditAnyCollection(organizationId);
}

if (await CanEditCiphersAsync(organizationId, cipherIds))
var org = _currentContext.GetOrganization(organizationId);

// If we're not an "admin", we don't need to check the ciphers
if (org is not ({ Type: OrganizationUserType.Owner or OrganizationUserType.Admin } or { Permissions.EditAnyCollection: true }))
{
return true;
// Are we a provider user? If so, we need to be sure we're not restricted
// Once the feature flag is removed, this check can be combined with the above
if (await _currentContext.ProviderUserForOrgAsync(organizationId))
{
// Provider is restricted from editing ciphers, so we're not an "admin"
if (_featureService.IsEnabled(FeatureFlagKeys.RestrictProviderAccess))
{
return false;
}

// Provider is unrestricted, so we're an "admin", don't return early
}
else
{
// Not a provider or admin
return false;
}
}

return false;
// We know we're an "admin", now check the ciphers explicitly (in case admins are restricted)
return await CanEditCiphersAsync(organizationId, cipherIds);
}

/// <summary>
Expand All @@ -360,10 +380,10 @@
return true;
}

// Provider users can access all ciphers in V1 (to change later)
// Provider users can only access all ciphers if RestrictProviderAccess is disabled
if (await _currentContext.ProviderUserForOrgAsync(organizationId))
{
return true;
return !_featureService.IsEnabled(FeatureFlagKeys.RestrictProviderAccess);

Check warning on line 386 in src/Api/Vault/Controllers/CiphersController.cs

View check run for this annotation

Codecov / codecov/patch

src/Api/Vault/Controllers/CiphersController.cs#L386

Added line #L386 was not covered by tests
}

return false;
Expand Down Expand Up @@ -399,10 +419,10 @@
return true;
}

// Provider users can edit all ciphers in V1 (to change later)
// Provider users can edit all ciphers if RestrictProviderAccess is disabled
if (await _currentContext.ProviderUserForOrgAsync(organizationId))
{
return true;
return !_featureService.IsEnabled(FeatureFlagKeys.RestrictProviderAccess);
}

return false;
Expand All @@ -422,10 +442,10 @@
return true;
}

// Provider users can still access organization ciphers in V1 (to change later)
// Provider users can only access organization ciphers if RestrictProviderAccess is disabled
if (await _currentContext.ProviderUserForOrgAsync(organizationId))
{
return true;
return !_featureService.IsEnabled(FeatureFlagKeys.RestrictProviderAccess);

Check warning on line 448 in src/Api/Vault/Controllers/CiphersController.cs

View check run for this annotation

Codecov / codecov/patch

src/Api/Vault/Controllers/CiphersController.cs#L448

Added line #L448 was not covered by tests
}

return false;
Expand All @@ -445,10 +465,10 @@
return true;
}

// Provider users can access all ciphers in V1 (to change later)
// Provider users can only access all ciphers if RestrictProviderAccess is disabled
if (await _currentContext.ProviderUserForOrgAsync(organizationId))
{
return true;
return !_featureService.IsEnabled(FeatureFlagKeys.RestrictProviderAccess);

Check warning on line 471 in src/Api/Vault/Controllers/CiphersController.cs

View check run for this annotation

Codecov / codecov/patch

src/Api/Vault/Controllers/CiphersController.cs#L471

Added line #L471 was not covered by tests
}

return false;
Expand Down
1 change: 1 addition & 0 deletions src/Core/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ public static class FeatureFlagKeys
public const string EmailVerification = "email-verification";
public const string AnhFcmv1Migration = "anh-fcmv1-migration";
public const string ExtensionRefresh = "extension-refresh";
public const string RestrictProviderAccess = "restrict-provider-access";

public static List<string> GetAllKeys()
{
Expand Down
47 changes: 20 additions & 27 deletions test/Api.Test/Vault/Controllers/CiphersControllerTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.Security.Claims;
using Bit.Api.Vault.Controllers;
using Bit.Api.Vault.Models;
using Bit.Api.Vault.Models.Request;
using Bit.Core;
using Bit.Core.Context;
Expand All @@ -9,7 +8,6 @@
using Bit.Core.Models.Data.Organizations;
using Bit.Core.Services;
using Bit.Core.Vault.Entities;
using Bit.Core.Vault.Enums;
using Bit.Core.Vault.Models.Data;
using Bit.Core.Vault.Repositories;
using Bit.Core.Vault.Services;
Expand Down Expand Up @@ -62,9 +60,10 @@ public async Task PutPartialShouldReturnCipherWithGivenFolderAndFavoriteValues(G
[BitAutoData(OrganizationUserType.Custom, false, false)]
public async Task CanEditCiphersAsAdminAsync_FlexibleCollections_Success(
OrganizationUserType userType, bool allowAdminsAccessToAllItems, bool shouldSucceed,
CurrentContextOrganization organization, Guid userId, SutProvider<CiphersController> sutProvider
CurrentContextOrganization organization, Guid userId, Cipher cipher, SutProvider<CiphersController> sutProvider
)
{
cipher.OrganizationId = organization.Id;
organization.Type = userType;
if (userType == OrganizationUserType.Custom)
{
Expand All @@ -74,6 +73,9 @@ public async Task PutPartialShouldReturnCipherWithGivenFolderAndFavoriteValues(G
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);

sutProvider.GetDependency<ICipherRepository>().GetByIdAsync(cipher.Id).Returns(cipher);
sutProvider.GetDependency<ICipherRepository>().GetManyByOrganizationIdAsync(organization.Id).Returns(new List<Cipher> { cipher });

sutProvider.GetDependency<IApplicationCacheService>().GetOrganizationAbilityAsync(organization.Id).Returns(new OrganizationAbility
{
Id = organization.Id,
Expand All @@ -82,23 +84,17 @@ public async Task PutPartialShouldReturnCipherWithGivenFolderAndFavoriteValues(G
});
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1).Returns(true);

var requestModel = new CipherCreateRequestModel
{
Cipher = new CipherRequestModel { OrganizationId = organization.Id.ToString(), Type = CipherType.Login, Login = new CipherLoginModel() },
CollectionIds = new List<Guid>()
};

if (shouldSucceed)
{
await sutProvider.Sut.PostAdmin(requestModel);
await sutProvider.Sut.DeleteAdmin(cipher.Id.ToString());
await sutProvider.GetDependency<ICipherService>().ReceivedWithAnyArgs()
.SaveAsync(default, default, default);
.DeleteAsync(default, default);
}
else
{
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.PostAdmin(requestModel));
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteAdmin(cipher.Id.ToString()));
await sutProvider.GetDependency<ICipherService>().DidNotReceiveWithAnyArgs()
.SaveAsync(default, default, default);
.DeleteAsync(default, default);
}
}

Expand Down Expand Up @@ -144,24 +140,19 @@ await sutProvider.GetDependency<ICipherService>().DidNotReceiveWithAnyArgs()
}

[Theory]
[BitAutoData(false, true)]
[BitAutoData(true, true)]
[BitAutoData(false, false)]
[BitAutoData(true, false)]
[BitAutoData(true, true)]
public async Task CanEditCiphersAsAdminAsync_Providers(
bool fcV1Enabled, bool shouldSucceed, Cipher cipher, CurrentContextOrganization organization, Guid userId, SutProvider<CiphersController> sutProvider
bool fcV1Enabled, bool restrictProviders, Cipher cipher, CurrentContextOrganization organization, Guid userId, SutProvider<CiphersController> sutProvider
)
{
cipher.OrganizationId = organization.Id;
if (fcV1Enabled)
{
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(organization.Id).Returns(shouldSucceed);
}
else
{
sutProvider.GetDependency<ICurrentContext>().EditAnyCollection(organization.Id).Returns(shouldSucceed);
}
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);

// Simulate that the user is a provider for the organization
sutProvider.GetDependency<ICurrentContext>().EditAnyCollection(organization.Id).Returns(true);
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(organization.Id).Returns(true);

sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);

sutProvider.GetDependency<ICipherRepository>().GetByIdAsync(cipher.Id).Returns(cipher);
Expand All @@ -174,14 +165,16 @@ await sutProvider.GetDependency<ICipherService>().DidNotReceiveWithAnyArgs()
AllowAdminAccessToAllCollectionItems = false
});
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1).Returns(fcV1Enabled);
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.RestrictProviderAccess).Returns(restrictProviders);

if (shouldSucceed)
// Non V1 FC or non restricted providers should succeed
if (!fcV1Enabled || !restrictProviders)
{
await sutProvider.Sut.DeleteAdmin(cipher.Id.ToString());
await sutProvider.GetDependency<ICipherService>().ReceivedWithAnyArgs()
.DeleteAsync(default, default);
}
else
else // Otherwise, they should fail
{
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteAdmin(cipher.Id.ToString()));
await sutProvider.GetDependency<ICipherService>().DidNotReceiveWithAnyArgs()
Expand Down