1
0
mirror of https://github.com/bitwarden/server.git synced 2025-05-18 01:55:37 -05:00

[PM-20543] - remove restrict-provider-access feature flag (#5700)

* remove restrict-provider-access feature flag

* remove feature flag

* re-add flag

* remove unnecessary tests

* fix bad merge

* fix bad merge

* remove RestrictProviderAccess key
This commit is contained in:
Jordan Aasen 2025-05-15 14:00:48 -07:00 committed by GitHub
parent 07de9aa8bc
commit 97fbf21977
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 18 additions and 248 deletions

View File

@ -315,28 +315,12 @@ public class CiphersController : Controller
{ {
var org = _currentContext.GetOrganization(organizationId); var org = _currentContext.GetOrganization(organizationId);
// If we're not an "admin", we don't need to check the ciphers // If we're not an "admin" or if we're not a provider user we don't need to check the ciphers
if (org is not ({ Type: OrganizationUserType.Owner or OrganizationUserType.Admin } or { Permissions.EditAnyCollection: true })) if (org is not ({ Type: OrganizationUserType.Owner or OrganizationUserType.Admin } or { Permissions.EditAnyCollection: true }) || await _currentContext.ProviderUserForOrgAsync(organizationId))
{
// 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; return false;
} }
// Provider is unrestricted, so we're an "admin", don't return early
}
else
{
// Not a provider or admin
return false;
}
}
// We know we're an "admin", now check the ciphers explicitly (in case admins are restricted) // We know we're an "admin", now check the ciphers explicitly (in case admins are restricted)
return await CanEditCiphersAsync(organizationId, cipherIds); return await CanEditCiphersAsync(organizationId, cipherIds);
} }
@ -350,28 +334,12 @@ public class CiphersController : Controller
var org = _currentContext.GetOrganization(organizationId); var org = _currentContext.GetOrganization(organizationId);
// If we're not an "admin", we don't need to check the ciphers // If we're not an "admin" or if we're a provider user we don't need to check the ciphers
if (org is not ({ Type: OrganizationUserType.Owner or OrganizationUserType.Admin } or { Permissions.EditAnyCollection: true })) if (org is not ({ Type: OrganizationUserType.Owner or OrganizationUserType.Admin } or { Permissions.EditAnyCollection: true }) || await _currentContext.ProviderUserForOrgAsync(organizationId))
{
// 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; return false;
} }
// Provider is unrestricted, so we're an "admin", don't return early
}
else
{
// Not a provider or admin
return false;
}
}
// If the user can edit all ciphers for the organization, just check they all belong to the org // If the user can edit all ciphers for the organization, just check they all belong to the org
if (await CanEditAllCiphersAsync(organizationId)) if (await CanEditAllCiphersAsync(organizationId))
{ {
@ -462,10 +430,10 @@ public class CiphersController : Controller
return true; return true;
} }
// Provider users can edit all ciphers if RestrictProviderAccess is disabled // Provider users cannot edit ciphers
if (await _currentContext.ProviderUserForOrgAsync(organizationId)) if (await _currentContext.ProviderUserForOrgAsync(organizationId))
{ {
return !_featureService.IsEnabled(FeatureFlagKeys.RestrictProviderAccess); return false;
} }
return false; return false;
@ -485,10 +453,10 @@ public class CiphersController : Controller
return true; return true;
} }
// Provider users can only access organization ciphers if RestrictProviderAccess is disabled // Provider users cannot access organization ciphers
if (await _currentContext.ProviderUserForOrgAsync(organizationId)) if (await _currentContext.ProviderUserForOrgAsync(organizationId))
{ {
return !_featureService.IsEnabled(FeatureFlagKeys.RestrictProviderAccess); return false;
} }
return false; return false;
@ -508,10 +476,10 @@ public class CiphersController : Controller
return true; return true;
} }
// Provider users can only access all ciphers if RestrictProviderAccess is disabled // Provider users cannot access ciphers
if (await _currentContext.ProviderUserForOrgAsync(organizationId)) if (await _currentContext.ProviderUserForOrgAsync(organizationId))
{ {
return !_featureService.IsEnabled(FeatureFlagKeys.RestrictProviderAccess); return false;
} }
return false; return false;

View File

@ -193,7 +193,6 @@ public static class FeatureFlagKeys
/* Vault Team */ /* Vault Team */
public const string PM8851_BrowserOnboardingNudge = "pm-8851-browser-onboarding-nudge"; public const string PM8851_BrowserOnboardingNudge = "pm-8851-browser-onboarding-nudge";
public const string PM9111ExtensionPersistAddEditForm = "pm-9111-extension-persist-add-edit-form"; public const string PM9111ExtensionPersistAddEditForm = "pm-9111-extension-persist-add-edit-form";
public const string RestrictProviderAccess = "restrict-provider-access";
public const string SecurityTasks = "security-tasks"; public const string SecurityTasks = "security-tasks";
public const string CipherKeyEncryption = "cipher-key-encryption"; public const string CipherKeyEncryption = "cipher-key-encryption";
public const string DesktopCipherForms = "pm-18520-desktop-cipher-forms"; public const string DesktopCipherForms = "pm-18520-desktop-cipher-forms";

View File

@ -193,49 +193,6 @@ public class CiphersControllerTests
} }
} }
[Theory]
[BitAutoData(false)]
[BitAutoData(false)]
[BitAutoData(true)]
public async Task CanEditCiphersAsAdminAsync_Providers(
bool restrictProviders, CipherDetails cipherDetails, CurrentContextOrganization organization, Guid userId, SutProvider<CiphersController> sutProvider
)
{
cipherDetails.OrganizationId = organization.Id;
// 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(cipherDetails.Id, userId).Returns(cipherDetails);
sutProvider.GetDependency<ICipherRepository>().GetManyByOrganizationIdAsync(organization.Id).Returns(new List<Cipher> { cipherDetails });
sutProvider.GetDependency<IApplicationCacheService>().GetOrganizationAbilityAsync(organization.Id).Returns(new OrganizationAbility
{
Id = organization.Id,
AllowAdminAccessToAllCollectionItems = false
});
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.RestrictProviderAccess).Returns(restrictProviders);
// Non restricted providers should succeed
if (!restrictProviders)
{
await sutProvider.Sut.DeleteAdmin(cipherDetails.Id);
await sutProvider.GetDependency<ICipherService>().ReceivedWithAnyArgs()
.DeleteAsync(default, default);
}
else // Otherwise, they should fail
{
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteAdmin(cipherDetails.Id));
await sutProvider.GetDependency<ICipherService>().DidNotReceiveWithAnyArgs()
.DeleteAsync(default, default);
}
await sutProvider.GetDependency<ICurrentContext>().Received().ProviderUserForOrgAsync(organization.Id);
}
[Theory] [Theory]
[BitAutoData(OrganizationUserType.Owner)] [BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Admin)] [BitAutoData(OrganizationUserType.Admin)]
@ -456,24 +413,7 @@ public class CiphersControllerTests
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async Task DeleteAdmin_WithProviderUser_DeletesCipher( public async Task DeleteAdmin_WithProviderUser_ThrowsNotFoundException(
CipherDetails cipherDetails, Guid userId, SutProvider<CiphersController> sutProvider)
{
cipherDetails.OrganizationId = Guid.NewGuid();
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(cipherDetails.OrganizationId.Value).Returns(true);
sutProvider.GetDependency<ICipherRepository>().GetByIdAsync(cipherDetails.Id, userId).Returns(cipherDetails);
sutProvider.GetDependency<ICipherRepository>().GetManyByOrganizationIdAsync(cipherDetails.OrganizationId.Value).Returns(new List<Cipher> { cipherDetails });
await sutProvider.Sut.DeleteAdmin(cipherDetails.Id);
await sutProvider.GetDependency<ICipherService>().Received(1).DeleteAsync(cipherDetails, userId, true);
}
[Theory]
[BitAutoData]
public async Task DeleteAdmin_WithProviderUser_WithRestrictProviderAccessTrue_ThrowsNotFoundException(
Cipher cipher, Guid userId, SutProvider<CiphersController> sutProvider) Cipher cipher, Guid userId, SutProvider<CiphersController> sutProvider)
{ {
cipher.OrganizationId = Guid.NewGuid(); cipher.OrganizationId = Guid.NewGuid();
@ -481,7 +421,6 @@ public class CiphersControllerTests
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId); sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(cipher.OrganizationId.Value).Returns(true); sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(cipher.OrganizationId.Value).Returns(true);
sutProvider.GetDependency<ICipherRepository>().GetByIdAsync(cipher.Id).Returns(cipher); sutProvider.GetDependency<ICipherRepository>().GetByIdAsync(cipher.Id).Returns(cipher);
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.RestrictProviderAccess).Returns(true);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteAdmin(cipher.Id)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteAdmin(cipher.Id));
} }
@ -737,43 +676,13 @@ public class CiphersControllerTests
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async Task DeleteManyAdmin_WithProviderUser_DeletesCiphers( public async Task DeleteManyAdmin_WithProviderUser_ThrowsNotFoundException(
CipherBulkDeleteRequestModel model, Guid userId,
List<Cipher> ciphers, SutProvider<CiphersController> sutProvider)
{
var organizationId = Guid.NewGuid();
model.OrganizationId = organizationId.ToString();
model.Ids = ciphers.Select(c => c.Id.ToString()).ToList();
foreach (var cipher in ciphers)
{
cipher.OrganizationId = organizationId;
}
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(organizationId).Returns(true);
sutProvider.GetDependency<ICipherRepository>().GetManyByOrganizationIdAsync(organizationId).Returns(ciphers);
await sutProvider.Sut.DeleteManyAdmin(model);
await sutProvider.GetDependency<ICipherService>()
.Received(1)
.DeleteManyAsync(
Arg.Is<IEnumerable<Guid>>(ids =>
ids.All(id => model.Ids.Contains(id.ToString())) && ids.Count() == model.Ids.Count()),
userId, organizationId, true);
}
[Theory]
[BitAutoData]
public async Task DeleteManyAdmin_WithProviderUser_WithRestrictProviderAccessTrue_ThrowsNotFoundException(
CipherBulkDeleteRequestModel model, SutProvider<CiphersController> sutProvider) CipherBulkDeleteRequestModel model, SutProvider<CiphersController> sutProvider)
{ {
var organizationId = Guid.NewGuid(); var organizationId = Guid.NewGuid();
model.OrganizationId = organizationId.ToString(); model.OrganizationId = organizationId.ToString();
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(organizationId).Returns(true); sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(organizationId).Returns(true);
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.RestrictProviderAccess).Returns(true);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteManyAdmin(model)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteManyAdmin(model));
} }
@ -1000,24 +909,7 @@ public class CiphersControllerTests
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async Task PutDeleteAdmin_WithProviderUser_SoftDeletesCipher( public async Task PutDeleteAdmin_WithProviderUser_ThrowsNotFoundException(
CipherDetails cipherDetails, Guid userId, SutProvider<CiphersController> sutProvider)
{
cipherDetails.OrganizationId = Guid.NewGuid();
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(cipherDetails.OrganizationId.Value).Returns(true);
sutProvider.GetDependency<ICipherRepository>().GetByIdAsync(cipherDetails.Id, userId).Returns(cipherDetails);
sutProvider.GetDependency<ICipherRepository>().GetManyByOrganizationIdAsync(cipherDetails.OrganizationId.Value).Returns(new List<Cipher> { cipherDetails });
await sutProvider.Sut.PutDeleteAdmin(cipherDetails.Id);
await sutProvider.GetDependency<ICipherService>().Received(1).SoftDeleteAsync(cipherDetails, userId, true);
}
[Theory]
[BitAutoData]
public async Task PutDeleteAdmin_WithProviderUser_WithRestrictProviderAccessTrue_ThrowsNotFoundException(
Cipher cipher, Guid userId, SutProvider<CiphersController> sutProvider) Cipher cipher, Guid userId, SutProvider<CiphersController> sutProvider)
{ {
cipher.OrganizationId = Guid.NewGuid(); cipher.OrganizationId = Guid.NewGuid();
@ -1025,7 +917,6 @@ public class CiphersControllerTests
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId); sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(cipher.OrganizationId.Value).Returns(true); sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(cipher.OrganizationId.Value).Returns(true);
sutProvider.GetDependency<ICipherRepository>().GetByIdAsync(cipher.Id).Returns(cipher); sutProvider.GetDependency<ICipherRepository>().GetByIdAsync(cipher.Id).Returns(cipher);
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.RestrictProviderAccess).Returns(true);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.PutDeleteAdmin(cipher.Id)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.PutDeleteAdmin(cipher.Id));
} }
@ -1272,43 +1163,13 @@ public class CiphersControllerTests
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async Task PutDeleteManyAdmin_WithProviderUser_SoftDeletesCiphers( public async Task PutDeleteManyAdmin_WithProviderUser_ThrowsNotFoundException(
CipherBulkDeleteRequestModel model, Guid userId,
List<Cipher> ciphers, SutProvider<CiphersController> sutProvider)
{
var organizationId = Guid.NewGuid();
model.OrganizationId = organizationId.ToString();
model.Ids = ciphers.Select(c => c.Id.ToString()).ToList();
foreach (var cipher in ciphers)
{
cipher.OrganizationId = organizationId;
}
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(organizationId).Returns(true);
sutProvider.GetDependency<ICipherRepository>().GetManyByOrganizationIdAsync(organizationId).Returns(ciphers);
await sutProvider.Sut.PutDeleteManyAdmin(model);
await sutProvider.GetDependency<ICipherService>()
.Received(1)
.SoftDeleteManyAsync(
Arg.Is<IEnumerable<Guid>>(ids =>
ids.All(id => model.Ids.Contains(id.ToString())) && ids.Count() == model.Ids.Count()),
userId, organizationId, true);
}
[Theory]
[BitAutoData]
public async Task PutDeleteManyAdmin_WithProviderUser_WithRestrictProviderAccessTrue_ThrowsNotFoundException(
CipherBulkDeleteRequestModel model, SutProvider<CiphersController> sutProvider) CipherBulkDeleteRequestModel model, SutProvider<CiphersController> sutProvider)
{ {
var organizationId = Guid.NewGuid(); var organizationId = Guid.NewGuid();
model.OrganizationId = organizationId.ToString(); model.OrganizationId = organizationId.ToString();
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(organizationId).Returns(true); sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(organizationId).Returns(true);
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.RestrictProviderAccess).Returns(true);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.PutDeleteManyAdmin(model)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.PutDeleteManyAdmin(model));
} }
@ -1546,27 +1407,7 @@ public class CiphersControllerTests
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async Task PutRestoreAdmin_WithProviderUser_RestoresCipher( public async Task PutRestoreAdmin_WithProviderUser_ThrowsNotFoundException(
CipherDetails cipherDetails, Guid userId, SutProvider<CiphersController> sutProvider)
{
cipherDetails.OrganizationId = Guid.NewGuid();
cipherDetails.Type = CipherType.Login;
cipherDetails.Data = JsonSerializer.Serialize(new CipherLoginData());
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(cipherDetails.OrganizationId.Value).Returns(true);
sutProvider.GetDependency<ICipherRepository>().GetByIdAsync(cipherDetails.Id, userId).Returns(cipherDetails);
sutProvider.GetDependency<ICipherRepository>().GetManyByOrganizationIdAsync(cipherDetails.OrganizationId.Value).Returns(new List<Cipher> { cipherDetails });
var result = await sutProvider.Sut.PutRestoreAdmin(cipherDetails.Id);
Assert.IsType<CipherMiniResponseModel>(result);
await sutProvider.GetDependency<ICipherService>().Received(1).RestoreAsync(cipherDetails, userId, true);
}
[Theory]
[BitAutoData]
public async Task PutRestoreAdmin_WithProviderUser_WithRestrictProviderAccessTrue_ThrowsNotFoundException(
CipherDetails cipherDetails, Guid userId, SutProvider<CiphersController> sutProvider) CipherDetails cipherDetails, Guid userId, SutProvider<CiphersController> sutProvider)
{ {
cipherDetails.OrganizationId = Guid.NewGuid(); cipherDetails.OrganizationId = Guid.NewGuid();
@ -1574,7 +1415,6 @@ public class CiphersControllerTests
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId); sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(cipherDetails.OrganizationId.Value).Returns(true); sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(cipherDetails.OrganizationId.Value).Returns(true);
sutProvider.GetDependency<ICipherRepository>().GetOrganizationDetailsByIdAsync(cipherDetails.Id).Returns(cipherDetails); sutProvider.GetDependency<ICipherRepository>().GetOrganizationDetailsByIdAsync(cipherDetails.Id).Returns(cipherDetails);
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.RestrictProviderAccess).Returns(true);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.PutRestoreAdmin(cipherDetails.Id)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.PutRestoreAdmin(cipherDetails.Id));
} }
@ -1896,49 +1736,12 @@ public class CiphersControllerTests
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async Task PutRestoreManyAdmin_WithProviderUser_RestoresCiphers( public async Task PutRestoreManyAdmin_WithProviderUser_ThrowsNotFoundException(
CipherBulkRestoreRequestModel model, Guid userId,
List<Cipher> ciphers, SutProvider<CiphersController> sutProvider)
{
model.OrganizationId = Guid.NewGuid();
model.Ids = ciphers.Select(c => c.Id.ToString()).ToList();
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(model.OrganizationId).Returns(true);
sutProvider.GetDependency<ICipherRepository>().GetManyByOrganizationIdAsync(model.OrganizationId).Returns(ciphers);
var cipherOrgDetails = ciphers.Select(c => new CipherOrganizationDetails
{
Id = c.Id,
OrganizationId = model.OrganizationId
}).ToList();
sutProvider.GetDependency<ICipherService>()
.RestoreManyAsync(
Arg.Any<HashSet<Guid>>(),
userId, model.OrganizationId, true)
.Returns(cipherOrgDetails);
var result = await sutProvider.Sut.PutRestoreManyAdmin(model);
Assert.NotNull(result);
await sutProvider.GetDependency<ICipherService>()
.Received(1)
.RestoreManyAsync(
Arg.Is<HashSet<Guid>>(ids =>
ids.All(id => model.Ids.Contains(id.ToString())) && ids.Count == model.Ids.Count()),
userId, model.OrganizationId, true);
}
[Theory]
[BitAutoData]
public async Task PutRestoreManyAdmin_WithProviderUser_WithRestrictProviderAccessTrue_ThrowsNotFoundException(
CipherBulkRestoreRequestModel model, SutProvider<CiphersController> sutProvider) CipherBulkRestoreRequestModel model, SutProvider<CiphersController> sutProvider)
{ {
model.OrganizationId = Guid.NewGuid(); model.OrganizationId = Guid.NewGuid();
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(model.OrganizationId).Returns(true); sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(model.OrganizationId).Returns(true);
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.RestrictProviderAccess).Returns(true);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.PutRestoreManyAdmin(model)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.PutRestoreManyAdmin(model));
} }