mirror of
https://github.com/bitwarden/android.git
synced 2024-12-25 10:28:28 +03:00
sync service
This commit is contained in:
parent
808fcea655
commit
818414eb37
7 changed files with 262 additions and 7 deletions
|
@ -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);
|
||||||
|
|
19
src/Core/Abstractions/ISyncService.cs
Normal file
19
src/Core/Abstractions/ISyncService.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
20
src/Core/Enums/NotificationType.cs
Normal file
20
src/Core/Enums/NotificationType.cs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
34
src/Core/Models/Response/NotificationResponse.cs
Normal file
34
src/Core/Models/Response/NotificationResponse.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 });
|
||||||
|
|
Loading…
Reference in a new issue