sync service

This commit is contained in:
Kyle Spearrin 2019-04-17 12:12:43 -04:00
parent 808fcea655
commit 818414eb37
7 changed files with 262 additions and 7 deletions

View file

@ -22,7 +22,7 @@ namespace Bit.Core.Abstractions
Task<CipherResponse> GetCipherAsync(string id); Task<CipherResponse> GetCipherAsync(string id);
Task<FolderResponse> GetFolderAsync(string id); Task<FolderResponse> GetFolderAsync(string id);
Task<ProfileResponse> GetProfileAsync(); Task<ProfileResponse> GetProfileAsync();
Task<SyncResponse> GetSyncAsync(string id); Task<SyncResponse> GetSyncAsync();
Task PostAccountKeysAsync(KeysRequest request); Task PostAccountKeysAsync(KeysRequest request);
Task<CipherResponse> PostCipherAsync(CipherRequest request); Task<CipherResponse> PostCipherAsync(CipherRequest request);
Task<CipherResponse> PostCipherCreateAsync(CipherCreateRequest request); Task<CipherResponse> PostCipherCreateAsync(CipherCreateRequest request);

View file

@ -0,0 +1,19 @@
using System;
using System.Threading.Tasks;
using Bit.Core.Models.Response;
namespace Bit.Core.Abstractions
{
public interface ISyncService
{
bool SyncInProgress { get; set; }
Task<bool> FullSyncAsync(bool forceSync);
Task<DateTime?> GetLastSyncAsync();
Task SetLastSyncAsync(DateTime date);
Task<bool> SyncDeleteCipherAsync(SyncCipherNotification notification);
Task<bool> SyncDeleteFolderAsync(SyncFolderNotification notification);
Task<bool> SyncUpsertCipherAsync(SyncCipherNotification notification, bool isEdit);
Task<bool> SyncUpsertFolderAsync(SyncFolderNotification notification, bool isEdit);
}
}

View file

@ -0,0 +1,20 @@
namespace Bit.Core.Enums
{
public enum NotificationType : byte
{
SyncCipherUpdate = 0,
SyncCipherCreate = 1,
SyncLoginDelete = 2,
SyncFolderDelete = 3,
SyncCiphers = 4,
SyncVault = 5,
SyncOrgKeys = 6,
SyncFolderCreate = 7,
SyncFolderUpdate = 8,
SyncCipherDelete = 9,
SyncSettings = 10,
LogOut = 11,
}
}

View file

@ -0,0 +1,34 @@
using Bit.Core.Enums;
using System;
using System.Collections.Generic;
namespace Bit.Core.Models.Response
{
public class PushNotificationResponse
{
public NotificationType Type { get; set; }
public string Payload { get; set; }
}
public class SyncCipherNotification
{
public string Id { get; set; }
public string UserId { get; set; }
public string OrganizationId { get; set; }
public HashSet<string> CollectionIds { get; set; }
public DateTime RevisionDate { get; set; }
}
public class SyncFolderNotification
{
public string Id { get; set; }
public string UserId { get; set; }
public DateTime RevisionDate { get; set; }
}
public class UserNotification
{
public string UserId { get; set; }
public DateTime Date { get; set; }
}
}

View file

@ -6,7 +6,7 @@ namespace Bit.Core.Models.Response
{ {
public ProfileResponse Profile { get; set; } public ProfileResponse Profile { get; set; }
public List<FolderResponse> Folders { get; set; } = new List<FolderResponse>(); public List<FolderResponse> Folders { get; set; } = new List<FolderResponse>();
public List<CollectionResponse> Collections { get; set; } = new List<CollectionResponse>(); public List<CollectionDetailsResponse> Collections { get; set; } = new List<CollectionDetailsResponse>();
public List<CipherResponse> Ciphers { get; set; } = new List<CipherResponse>(); public List<CipherResponse> Ciphers { get; set; } = new List<CipherResponse>();
public DomainsResponse Domains { get; set; } public DomainsResponse Domains { get; set; }
} }

View file

@ -254,7 +254,7 @@ namespace Bit.Core.Services
#region Sync APIs #region Sync APIs
public Task<SyncResponse> GetSyncAsync(string id) public Task<SyncResponse> GetSyncAsync()
{ {
return SendAsync<object, SyncResponse>(HttpMethod.Get, "/sync", null, true, true); return SendAsync<object, SyncResponse>(HttpMethod.Get, "/sync", null, true, true);
} }

View file

@ -1,16 +1,16 @@
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Exceptions;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Core.Models.Response; using Bit.Core.Models.Response;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Bit.Core.Services namespace Bit.Core.Services
{ {
public class SyncService public class SyncService : ISyncService
{ {
private const string Keys_LastSyncFormat = "lastSync_{0}"; private const string Keys_LastSyncFormat = "lastSync_{0}";
@ -58,7 +58,7 @@ namespace Bit.Core.Services
return await _storageService.GetAsync<DateTime?>(string.Format(Keys_LastSyncFormat, userId)); return await _storageService.GetAsync<DateTime?>(string.Format(Keys_LastSyncFormat, userId));
} }
public async Task SetLastSync(DateTime date) public async Task SetLastSyncAsync(DateTime date)
{ {
var userId = await _userService.GetUserIdAsync(); var userId = await _userService.GetUserIdAsync();
if(userId == null) if(userId == null)
@ -68,6 +68,188 @@ namespace Bit.Core.Services
await _storageService.SaveAsync(string.Format(Keys_LastSyncFormat, userId), date); await _storageService.SaveAsync(string.Format(Keys_LastSyncFormat, userId), date);
} }
public async Task<bool> FullSyncAsync(bool forceSync)
{
SyncStarted();
var isAuthenticated = await _userService.IsAuthenticatedAsync();
if(!isAuthenticated)
{
return SyncCompleted(false);
}
var now = DateTime.UtcNow;
var needsSyncResult = await NeedsSyncingAsync(forceSync);
var needsSync = needsSyncResult.Item1;
var skipped = needsSyncResult.Item2;
if(skipped)
{
return SyncCompleted(false);
}
if(!needsSync)
{
await SetLastSyncAsync(now);
return SyncCompleted(false);
}
var userId = await _userService.GetUserIdAsync();
try
{
var response = await _apiService.GetSyncAsync();
await SyncProfileAsync(response.Profile);
await SyncFoldersAsync(userId, response.Folders);
await SyncCollectionsAsync(response.Collections);
await SyncCiphersAsync(userId, response.Ciphers);
await SyncSettingsAsync(userId, response.Domains);
await SetLastSyncAsync(now);
return SyncCompleted(true);
}
catch
{
return SyncCompleted(false);
}
}
public async Task<bool> SyncUpsertFolderAsync(SyncFolderNotification notification, bool isEdit)
{
SyncStarted();
if(await _userService.IsAuthenticatedAsync())
{
try
{
var localFolder = await _folderService.GetAsync(notification.Id);
if((!isEdit && localFolder == null) ||
(isEdit && localFolder != null && localFolder.RevisionDate < notification.RevisionDate))
{
var remoteFolder = await _apiService.GetFolderAsync(notification.Id);
if(remoteFolder != null)
{
var userId = await _userService.GetUserIdAsync();
await _folderService.UpsertAsync(new FolderData(remoteFolder, userId));
_messagingService.Send("syncedUpsertedFolder", new Dictionary<string, string>
{
["folderId"] = notification.Id
});
return SyncCompleted(true);
}
}
}
catch { }
}
return SyncCompleted(false);
}
public async Task<bool> SyncDeleteFolderAsync(SyncFolderNotification notification)
{
SyncStarted();
if(await _userService.IsAuthenticatedAsync())
{
await _folderService.DeleteAsync(notification.Id);
_messagingService.Send("syncedDeletedFolder", new Dictionary<string, string>
{
["folderId"] = notification.Id
});
return SyncCompleted(true);
}
return SyncCompleted(false);
}
public async Task<bool> SyncUpsertCipherAsync(SyncCipherNotification notification, bool isEdit)
{
SyncStarted();
if(await _userService.IsAuthenticatedAsync())
{
try
{
var shouldUpdate = true;
var localCipher = await _cipherService.GetAsync(notification.Id);
if(localCipher != null && localCipher.RevisionDate >= notification.RevisionDate)
{
shouldUpdate = false;
}
var checkCollections = false;
if(shouldUpdate)
{
if(isEdit)
{
shouldUpdate = localCipher != null;
checkCollections = true;
}
else
{
if(notification.CollectionIds == null || notification.OrganizationId == null)
{
shouldUpdate = localCipher == null;
}
else
{
shouldUpdate = false;
checkCollections = true;
}
}
}
if(!shouldUpdate && checkCollections && notification.OrganizationId != null &&
notification.CollectionIds != null && notification.CollectionIds.Any())
{
var collections = await _collectionService.GetAllAsync();
if(collections != null)
{
foreach(var c in collections)
{
if(notification.CollectionIds.Contains(c.Id))
{
shouldUpdate = true;
break;
}
}
}
}
if(shouldUpdate)
{
var remoteCipher = await _apiService.GetCipherAsync(notification.Id);
if(remoteCipher != null)
{
var userId = await _userService.GetUserIdAsync();
await _cipherService.UpsertAsync(new CipherData(remoteCipher, userId));
_messagingService.Send("syncedUpsertedCipher", new Dictionary<string, string>
{
["cipherId"] = notification.Id
});
return SyncCompleted(true);
}
}
}
catch(ApiException e)
{
if(e.Error != null && e.Error.StatusCode == System.Net.HttpStatusCode.NotFound && isEdit)
{
await _cipherService.DeleteAsync(notification.Id);
_messagingService.Send("syncedDeletedCipher", new Dictionary<string, string>
{
["cipherId"] = notification.Id
});
return SyncCompleted(true);
}
}
}
return SyncCompleted(false);
}
public async Task<bool> SyncDeleteCipherAsync(SyncCipherNotification notification)
{
SyncStarted();
if(await _userService.IsAuthenticatedAsync())
{
await _cipherService.DeleteAsync(notification.Id);
_messagingService.Send("syncedDeletedCipher", new Dictionary<string, string>
{
["cipherId"] = notification.Id
});
return SyncCompleted(true);
}
return SyncCompleted(false);
}
// Helpers // Helpers
private void SyncStarted() private void SyncStarted()
@ -76,7 +258,7 @@ namespace Bit.Core.Services
_messagingService.Send("syncStarted"); _messagingService.Send("syncStarted");
} }
private bool SyncCompelted(bool successfully) private bool SyncCompleted(bool successfully)
{ {
SyncInProgress = false; SyncInProgress = false;
_messagingService.Send("syncCompleted", new Dictionary<string, object> { ["successfully"] = successfully }); _messagingService.Send("syncCompleted", new Dictionary<string, object> { ["successfully"] = successfully });