mirror of
https://github.com/bitwarden/server.git
synced 2024-11-23 12:25:16 +01:00
organization cipher import with collections
This commit is contained in:
parent
e7aa6980d5
commit
95181aef89
@ -112,7 +112,7 @@ namespace Bit.Api.Controllers
|
||||
}
|
||||
|
||||
[HttpPost("import")]
|
||||
public async Task PostImport([FromBody]ImportPasswordsRequestModel model)
|
||||
public async Task PostImport([FromBody]ImportCiphersRequestModel model)
|
||||
{
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var folders = model.Folders.Select(f => f.ToFolder(userId)).ToList();
|
||||
@ -120,6 +120,21 @@ namespace Bit.Api.Controllers
|
||||
await _cipherService.ImportCiphersAsync(folders, ciphers, model.FolderRelationships);
|
||||
}
|
||||
|
||||
[HttpPost("{orgId}/import")]
|
||||
public async Task PostImport(string orgId, [FromBody]ImportOrganizationCiphersRequestModel model)
|
||||
{
|
||||
var organizationId = new Guid(orgId);
|
||||
if(!_currentContext.OrganizationAdmin(organizationId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var collections = model.Collections.Select(c => c.ToCollection(organizationId)).ToList();
|
||||
var ciphers = model.Logins.Select(l => l.ToOrganizationCipherDetails(organizationId)).ToList();
|
||||
await _cipherService.ImportCiphersAsync(collections, ciphers, model.CollectionRelationships, userId);
|
||||
}
|
||||
|
||||
[HttpPut("{id}/partial")]
|
||||
[HttpPost("{id}/partial")]
|
||||
public async Task PutPartial(string id, [FromBody]CipherPartialRequestModel model)
|
||||
|
@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Bit.Core.Models.Api
|
||||
{
|
||||
public class ImportCiphersRequestModel
|
||||
{
|
||||
public FolderRequestModel[] Folders { get; set; }
|
||||
public LoginRequestModel[] Logins { get; set; }
|
||||
public KeyValuePair<int, int>[] FolderRelationships { get; set; }
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Bit.Core.Models.Api
|
||||
{
|
||||
public class ImportPasswordsRequestModel
|
||||
{
|
||||
private LoginRequestModel[] _logins;
|
||||
|
||||
public FolderRequestModel[] Folders { get; set; }
|
||||
[Obsolete]
|
||||
public LoginRequestModel[] Sites
|
||||
{
|
||||
get { return _logins; }
|
||||
set { _logins = value; }
|
||||
}
|
||||
public LoginRequestModel[] Logins
|
||||
{
|
||||
get { return _logins; }
|
||||
set { _logins = value; }
|
||||
}
|
||||
public KeyValuePair<int, int>[] FolderRelationships { get; set; }
|
||||
}
|
||||
}
|
@ -44,6 +44,15 @@ namespace Bit.Core.Models.Api
|
||||
});
|
||||
}
|
||||
|
||||
public CipherDetails ToOrganizationCipherDetails(Guid orgId)
|
||||
{
|
||||
return ToCipherDetails(new CipherDetails
|
||||
{
|
||||
OrganizationId = orgId,
|
||||
Edit = true
|
||||
});
|
||||
}
|
||||
|
||||
public Cipher ToOrganizationCipher()
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(OrganizationId))
|
||||
|
@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Bit.Core.Models.Api
|
||||
{
|
||||
public class ImportOrganizationCiphersRequestModel
|
||||
{
|
||||
public CollectionRequestModel[] Collections { get; set; }
|
||||
public LoginRequestModel[] Logins { get; set; }
|
||||
public KeyValuePair<int, int>[] CollectionRelationships { get; set; }
|
||||
}
|
||||
}
|
@ -26,5 +26,7 @@ namespace Bit.Core.Repositories
|
||||
Task MoveAsync(IEnumerable<Guid> ids, Guid? folderId, Guid userId);
|
||||
Task UpdateUserKeysAndCiphersAsync(User user, IEnumerable<Cipher> ciphers, IEnumerable<Folder> folders);
|
||||
Task CreateAsync(IEnumerable<Cipher> ciphers, IEnumerable<Folder> folders);
|
||||
Task CreateAsync(IEnumerable<Cipher> ciphers, IEnumerable<Collection> collections,
|
||||
IEnumerable<CollectionCipher> collectionCiphers);
|
||||
}
|
||||
}
|
||||
|
@ -401,6 +401,65 @@ namespace Bit.Core.Repositories.SqlServer
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CreateAsync(IEnumerable<Cipher> ciphers, IEnumerable<Collection> collections,
|
||||
IEnumerable<CollectionCipher> collectionCiphers)
|
||||
{
|
||||
if(!ciphers.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using(var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
connection.Open();
|
||||
|
||||
using(var transaction = connection.BeginTransaction())
|
||||
{
|
||||
try
|
||||
{
|
||||
using(var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepIdentity, transaction))
|
||||
{
|
||||
bulkCopy.DestinationTableName = "[dbo].[Cipher]";
|
||||
var dataTable = BuildCiphersTable(ciphers);
|
||||
bulkCopy.WriteToServer(dataTable);
|
||||
}
|
||||
|
||||
if(collections.Any())
|
||||
{
|
||||
using(var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepIdentity, transaction))
|
||||
{
|
||||
bulkCopy.DestinationTableName = "[dbo].[Collection]";
|
||||
var dataTable = BuildCollectionsTable(collections);
|
||||
bulkCopy.WriteToServer(dataTable);
|
||||
}
|
||||
|
||||
if(collectionCiphers.Any())
|
||||
{
|
||||
using(var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepIdentity, transaction))
|
||||
{
|
||||
bulkCopy.DestinationTableName = "[dbo].[CollectionCipher]";
|
||||
var dataTable = BuildCollectionCiphersTable(collectionCiphers);
|
||||
bulkCopy.WriteToServer(dataTable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await connection.ExecuteAsync(
|
||||
$"[{Schema}].[User_BumpAccountRevisionDateByOrganizationId]",
|
||||
new { OrganizationId = ciphers.First().OrganizationId },
|
||||
commandType: CommandType.StoredProcedure, transaction: transaction);
|
||||
|
||||
transaction.Commit();
|
||||
}
|
||||
catch
|
||||
{
|
||||
transaction.Rollback();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private DataTable BuildCiphersTable(IEnumerable<Cipher> ciphers)
|
||||
{
|
||||
var c = ciphers.FirstOrDefault();
|
||||
@ -498,6 +557,80 @@ namespace Bit.Core.Repositories.SqlServer
|
||||
return foldersTable;
|
||||
}
|
||||
|
||||
private DataTable BuildCollectionsTable(IEnumerable<Collection> collections)
|
||||
{
|
||||
var c = collections.FirstOrDefault();
|
||||
if(c == null)
|
||||
{
|
||||
throw new ApplicationException("Must have some collections to bulk import.");
|
||||
}
|
||||
|
||||
var collectionsTable = new DataTable("CollectionDataTable");
|
||||
|
||||
var idColumn = new DataColumn(nameof(c.Id), c.Id.GetType());
|
||||
collectionsTable.Columns.Add(idColumn);
|
||||
var organizationIdColumn = new DataColumn(nameof(c.OrganizationId), c.OrganizationId.GetType());
|
||||
collectionsTable.Columns.Add(organizationIdColumn);
|
||||
var nameColumn = new DataColumn(nameof(c.Name), typeof(string));
|
||||
collectionsTable.Columns.Add(nameColumn);
|
||||
var creationDateColumn = new DataColumn(nameof(c.CreationDate), c.CreationDate.GetType());
|
||||
collectionsTable.Columns.Add(creationDateColumn);
|
||||
var revisionDateColumn = new DataColumn(nameof(c.RevisionDate), c.RevisionDate.GetType());
|
||||
collectionsTable.Columns.Add(revisionDateColumn);
|
||||
|
||||
var keys = new DataColumn[1];
|
||||
keys[0] = idColumn;
|
||||
collectionsTable.PrimaryKey = keys;
|
||||
|
||||
foreach(var collection in collections)
|
||||
{
|
||||
var row = collectionsTable.NewRow();
|
||||
|
||||
row[idColumn] = collection.Id;
|
||||
row[organizationIdColumn] = collection.OrganizationId;
|
||||
row[nameColumn] = collection.Name;
|
||||
row[creationDateColumn] = collection.CreationDate;
|
||||
row[revisionDateColumn] = collection.RevisionDate;
|
||||
|
||||
collectionsTable.Rows.Add(row);
|
||||
}
|
||||
|
||||
return collectionsTable;
|
||||
}
|
||||
|
||||
private DataTable BuildCollectionCiphersTable(IEnumerable<CollectionCipher> collectionCiphers)
|
||||
{
|
||||
var cc = collectionCiphers.FirstOrDefault();
|
||||
if(cc == null)
|
||||
{
|
||||
throw new ApplicationException("Must have some collectionCiphers to bulk import.");
|
||||
}
|
||||
|
||||
var collectionCiphersTable = new DataTable("CollectionCipherDataTable");
|
||||
|
||||
var collectionIdColumn = new DataColumn(nameof(cc.CollectionId), cc.CollectionId.GetType());
|
||||
collectionCiphersTable.Columns.Add(collectionIdColumn);
|
||||
var cipherIdColumn = new DataColumn(nameof(cc.CipherId), cc.CipherId.GetType());
|
||||
collectionCiphersTable.Columns.Add(cipherIdColumn);
|
||||
|
||||
var keys = new DataColumn[2];
|
||||
keys[0] = collectionIdColumn;
|
||||
keys[1] = cipherIdColumn;
|
||||
collectionCiphersTable.PrimaryKey = keys;
|
||||
|
||||
foreach(var collectionCipher in collectionCiphers)
|
||||
{
|
||||
var row = collectionCiphersTable.NewRow();
|
||||
|
||||
row[collectionIdColumn] = collectionCipher.CollectionId;
|
||||
row[cipherIdColumn] = collectionCipher.CipherId;
|
||||
|
||||
collectionCiphersTable.Rows.Add(row);
|
||||
}
|
||||
|
||||
return collectionCiphersTable;
|
||||
}
|
||||
|
||||
public class CipherWithCollections : Cipher
|
||||
{
|
||||
public DataTable CollectionIds { get; set; }
|
||||
|
@ -25,5 +25,7 @@ namespace Bit.Core.Services
|
||||
Task SaveCollectionsAsync(Cipher cipher, IEnumerable<Guid> collectionIds, Guid savingUserId, bool orgAdmin);
|
||||
Task ImportCiphersAsync(List<Folder> folders, List<CipherDetails> ciphers,
|
||||
IEnumerable<KeyValuePair<int, int>> folderRelationships);
|
||||
Task ImportCiphersAsync(List<Collection> collections, List<CipherDetails> ciphers,
|
||||
IEnumerable<KeyValuePair<int, int>> collectionRelationships, Guid importingUserId);
|
||||
}
|
||||
}
|
||||
|
@ -464,6 +464,50 @@ namespace Bit.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ImportCiphersAsync(
|
||||
List<Collection> collections,
|
||||
List<CipherDetails> ciphers,
|
||||
IEnumerable<KeyValuePair<int, int>> collectionRelationships,
|
||||
Guid importingUserId)
|
||||
{
|
||||
// Init. ids for ciphers
|
||||
foreach(var cipher in ciphers)
|
||||
{
|
||||
cipher.SetNewId();
|
||||
}
|
||||
|
||||
// Init. ids for collections
|
||||
foreach(var collection in collections)
|
||||
{
|
||||
collection.SetNewId();
|
||||
}
|
||||
|
||||
// Create associations based on the newly assigned ids
|
||||
var collectionCiphers = new List<CollectionCipher>();
|
||||
foreach(var relationship in collectionRelationships)
|
||||
{
|
||||
var cipher = ciphers.ElementAtOrDefault(relationship.Key);
|
||||
var collection = collections.ElementAtOrDefault(relationship.Value);
|
||||
|
||||
if(cipher == null || collection == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
collectionCiphers.Add(new CollectionCipher
|
||||
{
|
||||
CipherId = cipher.Id,
|
||||
CollectionId = collection.Id
|
||||
});
|
||||
}
|
||||
|
||||
// Create it all
|
||||
await _cipherRepository.CreateAsync(ciphers, collections, collectionCiphers);
|
||||
|
||||
// push
|
||||
await _pushService.PushSyncVaultAsync(importingUserId);
|
||||
}
|
||||
|
||||
private async Task<bool> UserCanEditAsync(Cipher cipher, Guid userId)
|
||||
{
|
||||
if(!cipher.OrganizationId.HasValue && cipher.UserId.HasValue && cipher.UserId.Value == userId)
|
||||
|
Loading…
Reference in New Issue
Block a user