mirror of
https://github.com/bitwarden/server
synced 2025-12-15 15:53:59 +00:00
[PM-26772] Importing Archived Ciphers (#6452)
* persist archive date for importing ciphers * throw error if a user imports archived ciphers into an organization * remove extra semi-colon * set archive date for initial tests to avoid error thrown * refactor ArchivedDate query * add test for throwing for importing archived ciphers into a organization * remove folder and organization id from test * remove unneeded org id and null out the folderid
This commit is contained in:
@@ -74,10 +74,14 @@ public class ImportCiphersController : Controller
|
|||||||
throw new BadRequestException("You cannot import this much data at once.");
|
throw new BadRequestException("You cannot import this much data at once.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (model.Ciphers.Any(c => c.ArchivedDate.HasValue))
|
||||||
|
{
|
||||||
|
throw new BadRequestException("You cannot import archived items into an organization.");
|
||||||
|
}
|
||||||
|
|
||||||
var orgId = new Guid(organizationId);
|
var orgId = new Guid(organizationId);
|
||||||
var collections = model.Collections.Select(c => c.ToCollection(orgId)).ToList();
|
var collections = model.Collections.Select(c => c.ToCollection(orgId)).ToList();
|
||||||
|
|
||||||
|
|
||||||
//An User is allowed to import if CanCreate Collections or has AccessToImportExport
|
//An User is allowed to import if CanCreate Collections or has AccessToImportExport
|
||||||
var authorized = await CheckOrgImportPermission(collections, orgId);
|
var authorized = await CheckOrgImportPermission(collections, orgId);
|
||||||
if (!authorized)
|
if (!authorized)
|
||||||
@@ -156,7 +160,7 @@ public class ImportCiphersController : Controller
|
|||||||
if (existingCollections.Any() && (await _authorizationService.AuthorizeAsync(User, existingCollections, BulkCollectionOperations.ImportCiphers)).Succeeded)
|
if (existingCollections.Any() && (await _authorizationService.AuthorizeAsync(User, existingCollections, BulkCollectionOperations.ImportCiphers)).Succeeded)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
};
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -218,6 +218,8 @@ public static class BulkResourceCreationService
|
|||||||
ciphersTable.Columns.Add(revisionDateColumn);
|
ciphersTable.Columns.Add(revisionDateColumn);
|
||||||
var deletedDateColumn = new DataColumn(nameof(c.DeletedDate), typeof(DateTime));
|
var deletedDateColumn = new DataColumn(nameof(c.DeletedDate), typeof(DateTime));
|
||||||
ciphersTable.Columns.Add(deletedDateColumn);
|
ciphersTable.Columns.Add(deletedDateColumn);
|
||||||
|
var archivedDateColumn = new DataColumn(nameof(c.ArchivedDate), typeof(DateTime));
|
||||||
|
ciphersTable.Columns.Add(archivedDateColumn);
|
||||||
var repromptColumn = new DataColumn(nameof(c.Reprompt), typeof(short));
|
var repromptColumn = new DataColumn(nameof(c.Reprompt), typeof(short));
|
||||||
ciphersTable.Columns.Add(repromptColumn);
|
ciphersTable.Columns.Add(repromptColumn);
|
||||||
var keyColummn = new DataColumn(nameof(c.Key), typeof(string));
|
var keyColummn = new DataColumn(nameof(c.Key), typeof(string));
|
||||||
@@ -247,6 +249,7 @@ public static class BulkResourceCreationService
|
|||||||
row[creationDateColumn] = cipher.CreationDate;
|
row[creationDateColumn] = cipher.CreationDate;
|
||||||
row[revisionDateColumn] = cipher.RevisionDate;
|
row[revisionDateColumn] = cipher.RevisionDate;
|
||||||
row[deletedDateColumn] = cipher.DeletedDate.HasValue ? (object)cipher.DeletedDate : DBNull.Value;
|
row[deletedDateColumn] = cipher.DeletedDate.HasValue ? (object)cipher.DeletedDate : DBNull.Value;
|
||||||
|
row[archivedDateColumn] = cipher.ArchivedDate.HasValue ? cipher.ArchivedDate : DBNull.Value;
|
||||||
row[repromptColumn] = cipher.Reprompt.HasValue ? cipher.Reprompt.Value : DBNull.Value;
|
row[repromptColumn] = cipher.Reprompt.HasValue ? cipher.Reprompt.Value : DBNull.Value;
|
||||||
row[keyColummn] = cipher.Key;
|
row[keyColummn] = cipher.Key;
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ public class ImportCiphersControllerTests
|
|||||||
.With(x => x.Ciphers, fixture.Build<CipherRequestModel>()
|
.With(x => x.Ciphers, fixture.Build<CipherRequestModel>()
|
||||||
.With(c => c.OrganizationId, Guid.NewGuid().ToString())
|
.With(c => c.OrganizationId, Guid.NewGuid().ToString())
|
||||||
.With(c => c.FolderId, Guid.NewGuid().ToString())
|
.With(c => c.FolderId, Guid.NewGuid().ToString())
|
||||||
|
.With(c => c.ArchivedDate, (DateTime?)null)
|
||||||
.CreateMany(1).ToArray())
|
.CreateMany(1).ToArray())
|
||||||
.Create();
|
.Create();
|
||||||
|
|
||||||
@@ -92,6 +93,37 @@ public class ImportCiphersControllerTests
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task PostImportIndividual_WithArchivedDate_SavesArchivedDate(User user,
|
||||||
|
IFixture fixture, SutProvider<ImportCiphersController> sutProvider)
|
||||||
|
{
|
||||||
|
var archivedDate = DateTime.UtcNow;
|
||||||
|
sutProvider.GetDependency<GlobalSettings>()
|
||||||
|
.SelfHosted = false;
|
||||||
|
|
||||||
|
sutProvider.GetDependency<Core.Services.IUserService>()
|
||||||
|
.GetProperUserId(Arg.Any<ClaimsPrincipal>())
|
||||||
|
.Returns(user.Id);
|
||||||
|
|
||||||
|
var request = fixture.Build<ImportCiphersRequestModel>()
|
||||||
|
.With(x => x.Ciphers, fixture.Build<CipherRequestModel>()
|
||||||
|
.With(c => c.ArchivedDate, archivedDate)
|
||||||
|
.With(c => c.FolderId, (string)null)
|
||||||
|
.CreateMany(1).ToArray())
|
||||||
|
.Create();
|
||||||
|
|
||||||
|
await sutProvider.Sut.PostImport(request);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IImportCiphersCommand>()
|
||||||
|
.Received()
|
||||||
|
.ImportIntoIndividualVaultAsync(
|
||||||
|
Arg.Any<List<Folder>>(),
|
||||||
|
Arg.Is<List<CipherDetails>>(ciphers => ciphers.First().ArchivedDate == archivedDate),
|
||||||
|
Arg.Any<IEnumerable<KeyValuePair<int, int>>>(),
|
||||||
|
user.Id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/****************************
|
/****************************
|
||||||
* PostImport - Organization
|
* PostImport - Organization
|
||||||
****************************/
|
****************************/
|
||||||
@@ -156,6 +188,7 @@ public class ImportCiphersControllerTests
|
|||||||
.With(x => x.Ciphers, fixture.Build<CipherRequestModel>()
|
.With(x => x.Ciphers, fixture.Build<CipherRequestModel>()
|
||||||
.With(c => c.OrganizationId, Guid.NewGuid().ToString())
|
.With(c => c.OrganizationId, Guid.NewGuid().ToString())
|
||||||
.With(c => c.FolderId, Guid.NewGuid().ToString())
|
.With(c => c.FolderId, Guid.NewGuid().ToString())
|
||||||
|
.With(c => c.ArchivedDate, (DateTime?)null)
|
||||||
.CreateMany(1).ToArray())
|
.CreateMany(1).ToArray())
|
||||||
.With(y => y.Collections, fixture.Build<CollectionWithIdRequestModel>()
|
.With(y => y.Collections, fixture.Build<CollectionWithIdRequestModel>()
|
||||||
.With(c => c.Id, orgIdGuid)
|
.With(c => c.Id, orgIdGuid)
|
||||||
@@ -227,6 +260,7 @@ public class ImportCiphersControllerTests
|
|||||||
.With(x => x.Ciphers, fixture.Build<CipherRequestModel>()
|
.With(x => x.Ciphers, fixture.Build<CipherRequestModel>()
|
||||||
.With(c => c.OrganizationId, Guid.NewGuid().ToString())
|
.With(c => c.OrganizationId, Guid.NewGuid().ToString())
|
||||||
.With(c => c.FolderId, Guid.NewGuid().ToString())
|
.With(c => c.FolderId, Guid.NewGuid().ToString())
|
||||||
|
.With(c => c.ArchivedDate, (DateTime?)null)
|
||||||
.CreateMany(1).ToArray())
|
.CreateMany(1).ToArray())
|
||||||
.With(y => y.Collections, fixture.Build<CollectionWithIdRequestModel>()
|
.With(y => y.Collections, fixture.Build<CollectionWithIdRequestModel>()
|
||||||
.With(c => c.Id, orgIdGuid)
|
.With(c => c.Id, orgIdGuid)
|
||||||
@@ -291,6 +325,7 @@ public class ImportCiphersControllerTests
|
|||||||
.With(x => x.Ciphers, fixture.Build<CipherRequestModel>()
|
.With(x => x.Ciphers, fixture.Build<CipherRequestModel>()
|
||||||
.With(c => c.OrganizationId, Guid.NewGuid().ToString())
|
.With(c => c.OrganizationId, Guid.NewGuid().ToString())
|
||||||
.With(c => c.FolderId, Guid.NewGuid().ToString())
|
.With(c => c.FolderId, Guid.NewGuid().ToString())
|
||||||
|
.With(c => c.ArchivedDate, (DateTime?)null)
|
||||||
.CreateMany(1).ToArray())
|
.CreateMany(1).ToArray())
|
||||||
.With(y => y.Collections, fixture.Build<CollectionWithIdRequestModel>()
|
.With(y => y.Collections, fixture.Build<CollectionWithIdRequestModel>()
|
||||||
.With(c => c.Id, orgIdGuid)
|
.With(c => c.Id, orgIdGuid)
|
||||||
@@ -354,6 +389,7 @@ public class ImportCiphersControllerTests
|
|||||||
.With(x => x.Ciphers, fixture.Build<CipherRequestModel>()
|
.With(x => x.Ciphers, fixture.Build<CipherRequestModel>()
|
||||||
.With(c => c.OrganizationId, Guid.NewGuid().ToString())
|
.With(c => c.OrganizationId, Guid.NewGuid().ToString())
|
||||||
.With(c => c.FolderId, Guid.NewGuid().ToString())
|
.With(c => c.FolderId, Guid.NewGuid().ToString())
|
||||||
|
.With(c => c.ArchivedDate, (DateTime?)null)
|
||||||
.CreateMany(1).ToArray())
|
.CreateMany(1).ToArray())
|
||||||
.With(y => y.Collections, fixture.Build<CollectionWithIdRequestModel>()
|
.With(y => y.Collections, fixture.Build<CollectionWithIdRequestModel>()
|
||||||
.With(c => c.Id, orgIdGuid)
|
.With(c => c.Id, orgIdGuid)
|
||||||
@@ -423,6 +459,7 @@ public class ImportCiphersControllerTests
|
|||||||
Ciphers = fixture.Build<CipherRequestModel>()
|
Ciphers = fixture.Build<CipherRequestModel>()
|
||||||
.With(_ => _.OrganizationId, orgId.ToString())
|
.With(_ => _.OrganizationId, orgId.ToString())
|
||||||
.With(_ => _.FolderId, Guid.NewGuid().ToString())
|
.With(_ => _.FolderId, Guid.NewGuid().ToString())
|
||||||
|
.With(_ => _.ArchivedDate, (DateTime?)null)
|
||||||
.CreateMany(2).ToArray(),
|
.CreateMany(2).ToArray(),
|
||||||
CollectionRelationships = new List<KeyValuePair<int, int>>().ToArray(),
|
CollectionRelationships = new List<KeyValuePair<int, int>>().ToArray(),
|
||||||
};
|
};
|
||||||
@@ -499,6 +536,7 @@ public class ImportCiphersControllerTests
|
|||||||
Ciphers = fixture.Build<CipherRequestModel>()
|
Ciphers = fixture.Build<CipherRequestModel>()
|
||||||
.With(_ => _.OrganizationId, orgId.ToString())
|
.With(_ => _.OrganizationId, orgId.ToString())
|
||||||
.With(_ => _.FolderId, Guid.NewGuid().ToString())
|
.With(_ => _.FolderId, Guid.NewGuid().ToString())
|
||||||
|
.With(_ => _.ArchivedDate, (DateTime?)null)
|
||||||
.CreateMany(2).ToArray(),
|
.CreateMany(2).ToArray(),
|
||||||
CollectionRelationships = new List<KeyValuePair<int, int>>().ToArray(),
|
CollectionRelationships = new List<KeyValuePair<int, int>>().ToArray(),
|
||||||
};
|
};
|
||||||
@@ -578,6 +616,7 @@ public class ImportCiphersControllerTests
|
|||||||
Ciphers = fixture.Build<CipherRequestModel>()
|
Ciphers = fixture.Build<CipherRequestModel>()
|
||||||
.With(_ => _.OrganizationId, orgId.ToString())
|
.With(_ => _.OrganizationId, orgId.ToString())
|
||||||
.With(_ => _.FolderId, Guid.NewGuid().ToString())
|
.With(_ => _.FolderId, Guid.NewGuid().ToString())
|
||||||
|
.With(_ => _.ArchivedDate, (DateTime?)null)
|
||||||
.CreateMany(2).ToArray(),
|
.CreateMany(2).ToArray(),
|
||||||
CollectionRelationships = new List<KeyValuePair<int, int>>().ToArray(),
|
CollectionRelationships = new List<KeyValuePair<int, int>>().ToArray(),
|
||||||
};
|
};
|
||||||
@@ -651,6 +690,7 @@ public class ImportCiphersControllerTests
|
|||||||
Ciphers = fixture.Build<CipherRequestModel>()
|
Ciphers = fixture.Build<CipherRequestModel>()
|
||||||
.With(_ => _.OrganizationId, orgId.ToString())
|
.With(_ => _.OrganizationId, orgId.ToString())
|
||||||
.With(_ => _.FolderId, Guid.NewGuid().ToString())
|
.With(_ => _.FolderId, Guid.NewGuid().ToString())
|
||||||
|
.With(_ => _.ArchivedDate, (DateTime?)null)
|
||||||
.CreateMany(2).ToArray(),
|
.CreateMany(2).ToArray(),
|
||||||
CollectionRelationships = new List<KeyValuePair<int, int>>().ToArray(),
|
CollectionRelationships = new List<KeyValuePair<int, int>>().ToArray(),
|
||||||
};
|
};
|
||||||
@@ -720,6 +760,7 @@ public class ImportCiphersControllerTests
|
|||||||
Ciphers = fixture.Build<CipherRequestModel>()
|
Ciphers = fixture.Build<CipherRequestModel>()
|
||||||
.With(_ => _.OrganizationId, orgId.ToString())
|
.With(_ => _.OrganizationId, orgId.ToString())
|
||||||
.With(_ => _.FolderId, Guid.NewGuid().ToString())
|
.With(_ => _.FolderId, Guid.NewGuid().ToString())
|
||||||
|
.With(_ => _.ArchivedDate, (DateTime?)null)
|
||||||
.CreateMany(2).ToArray(),
|
.CreateMany(2).ToArray(),
|
||||||
CollectionRelationships = new List<KeyValuePair<int, int>>().ToArray(),
|
CollectionRelationships = new List<KeyValuePair<int, int>>().ToArray(),
|
||||||
};
|
};
|
||||||
@@ -765,6 +806,63 @@ public class ImportCiphersControllerTests
|
|||||||
Arg.Any<Guid>());
|
Arg.Any<Guid>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task PostImportOrganization_ThrowsException_WhenAnyCipherIsArchived(
|
||||||
|
SutProvider<ImportCiphersController> sutProvider,
|
||||||
|
IFixture fixture,
|
||||||
|
User user
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var orgId = Guid.NewGuid();
|
||||||
|
|
||||||
|
sutProvider.GetDependency<GlobalSettings>()
|
||||||
|
.SelfHosted = false;
|
||||||
|
sutProvider.GetDependency<GlobalSettings>()
|
||||||
|
.ImportCiphersLimitation = _organizationCiphersLimitations;
|
||||||
|
|
||||||
|
SetupUserService(sutProvider, user);
|
||||||
|
|
||||||
|
var ciphers = fixture.Build<CipherRequestModel>()
|
||||||
|
.With(_ => _.ArchivedDate, DateTime.UtcNow)
|
||||||
|
.CreateMany(2).ToArray();
|
||||||
|
|
||||||
|
var request = new ImportOrganizationCiphersRequestModel
|
||||||
|
{
|
||||||
|
Collections = new List<CollectionWithIdRequestModel>().ToArray(),
|
||||||
|
Ciphers = ciphers,
|
||||||
|
CollectionRelationships = new List<KeyValuePair<int, int>>().ToArray(),
|
||||||
|
};
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ICurrentContext>()
|
||||||
|
.AccessImportExport(Arg.Any<Guid>())
|
||||||
|
.Returns(false);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IAuthorizationService>()
|
||||||
|
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(),
|
||||||
|
Arg.Any<IEnumerable<Collection>>(),
|
||||||
|
Arg.Is<IEnumerable<IAuthorizationRequirement>>(reqs =>
|
||||||
|
reqs.Contains(BulkCollectionOperations.ImportCiphers)))
|
||||||
|
.Returns(AuthorizationResult.Failed());
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IAuthorizationService>()
|
||||||
|
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(),
|
||||||
|
Arg.Any<IEnumerable<Collection>>(),
|
||||||
|
Arg.Is<IEnumerable<IAuthorizationRequirement>>(reqs =>
|
||||||
|
reqs.Contains(BulkCollectionOperations.Create)))
|
||||||
|
.Returns(AuthorizationResult.Success());
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ICollectionRepository>()
|
||||||
|
.GetManyByOrganizationIdAsync(orgId)
|
||||||
|
.Returns(new List<Collection>());
|
||||||
|
|
||||||
|
var exception = await Assert.ThrowsAsync<BadRequestException>(async () =>
|
||||||
|
{
|
||||||
|
await sutProvider.Sut.PostImportOrganization(orgId.ToString(), request);
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.Equal("You cannot import archived items into an organization.", exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
private static void SetupUserService(SutProvider<ImportCiphersController> sutProvider, User user)
|
private static void SetupUserService(SutProvider<ImportCiphersController> sutProvider, User user)
|
||||||
{
|
{
|
||||||
// This is a workaround for the NSubstitute issue with ambiguous arguments
|
// This is a workaround for the NSubstitute issue with ambiguous arguments
|
||||||
|
|||||||
Reference in New Issue
Block a user