1
0
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:
Kyle Spearrin 2017-09-05 17:49:34 -04:00
parent e7aa6980d5
commit 95181aef89
9 changed files with 228 additions and 25 deletions

View File

@ -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)

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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))

View File

@ -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; }
}
}

View File

@ -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);
}
}

View File

@ -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; }

View File

@ -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);
}
}

View File

@ -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)