bitwarden-android/src/Core/Services/SyncService.cs

374 lines
14 KiB
C#
Raw Normal View History

2019-04-17 16:38:20 +03:00
using Bit.Core.Abstractions;
2019-04-17 19:12:43 +03:00
using Bit.Core.Exceptions;
2019-04-17 16:38:20 +03:00
using Bit.Core.Models.Data;
using Bit.Core.Models.Response;
using Bit.Core.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Bit.Core.Services
{
2019-04-17 19:12:43 +03:00
public class SyncService : ISyncService
2019-04-17 16:38:20 +03:00
{
private const string Keys_LastSyncFormat = "lastSync_{0}";
private readonly IUserService _userService;
private readonly IApiService _apiService;
private readonly ISettingsService _settingsService;
private readonly IFolderService _folderService;
private readonly ICipherService _cipherService;
private readonly ICryptoService _cryptoService;
private readonly ICollectionService _collectionService;
private readonly IStorageService _storageService;
private readonly IMessagingService _messagingService;
private readonly IPolicyService _policyService;
2019-10-22 23:30:28 +03:00
private readonly Func<bool, Task> _logoutCallbackAsync;
2019-04-17 16:38:20 +03:00
public SyncService(
IUserService userService,
IApiService apiService,
ISettingsService settingsService,
IFolderService folderService,
ICipherService cipherService,
ICryptoService cryptoService,
ICollectionService collectionService,
IStorageService storageService,
2019-05-30 06:41:43 +03:00
IMessagingService messagingService,
IPolicyService policyService,
2019-10-22 23:30:28 +03:00
Func<bool, Task> logoutCallbackAsync)
2019-04-17 16:38:20 +03:00
{
_userService = userService;
_apiService = apiService;
_settingsService = settingsService;
_folderService = folderService;
_cipherService = cipherService;
_cryptoService = cryptoService;
_collectionService = collectionService;
_storageService = storageService;
_messagingService = messagingService;
_policyService = policyService;
2019-10-22 23:30:28 +03:00
_logoutCallbackAsync = logoutCallbackAsync;
2019-04-17 16:38:20 +03:00
}
public bool SyncInProgress { get; set; }
public async Task<DateTime?> GetLastSyncAsync()
{
var userId = await _userService.GetUserIdAsync();
if(userId == null)
{
return null;
}
return await _storageService.GetAsync<DateTime?>(string.Format(Keys_LastSyncFormat, userId));
}
2019-04-17 19:12:43 +03:00
public async Task SetLastSyncAsync(DateTime date)
2019-04-17 16:38:20 +03:00
{
var userId = await _userService.GetUserIdAsync();
if(userId == null)
{
return;
}
await _storageService.SaveAsync(string.Format(Keys_LastSyncFormat, userId), date);
}
2019-10-15 18:05:56 +03:00
public async Task<bool> FullSyncAsync(bool forceSync, bool allowThrowOnError = false)
2019-04-17 19:12:43 +03:00
{
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 SyncPolicies(response.Policies);
2019-04-17 19:12:43 +03:00
await SetLastSyncAsync(now);
return SyncCompleted(true);
}
catch
{
2019-10-15 18:05:56 +03:00
if(allowThrowOnError)
{
throw;
}
else
{
return SyncCompleted(false);
}
2019-04-17 19:12:43 +03:00
}
}
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);
}
2019-04-17 16:38:20 +03:00
// Helpers
private void SyncStarted()
{
SyncInProgress = true;
2019-04-19 19:29:37 +03:00
_messagingService.Send("syncStarted");
2019-04-17 16:38:20 +03:00
}
2019-04-17 19:12:43 +03:00
private bool SyncCompleted(bool successfully)
2019-04-17 16:38:20 +03:00
{
SyncInProgress = false;
_messagingService.Send("syncCompleted", new Dictionary<string, object> { ["successfully"] = successfully });
return successfully;
}
private async Task<Tuple<bool, bool>> NeedsSyncingAsync(bool forceSync)
{
if(forceSync)
{
return new Tuple<bool, bool>(true, false);
}
var lastSync = await GetLastSyncAsync();
if(lastSync == null || lastSync == DateTime.MinValue)
{
return new Tuple<bool, bool>(true, false);
}
try
{
var response = await _apiService.GetAccountRevisionDateAsync();
var d = CoreHelpers.Epoc.AddMilliseconds(response);
if(d <= lastSync.Value)
{
return new Tuple<bool, bool>(false, false);
}
return new Tuple<bool, bool>(true, false);
}
catch
{
return new Tuple<bool, bool>(false, true);
}
}
private async Task SyncProfileAsync(ProfileResponse response)
{
var stamp = await _userService.GetSecurityStampAsync();
if(stamp != null && stamp != response.SecurityStamp)
{
2019-10-22 23:30:28 +03:00
if(_logoutCallbackAsync != null)
{
await _logoutCallbackAsync(true);
}
2019-05-30 06:41:43 +03:00
return;
2019-04-17 16:38:20 +03:00
}
await _cryptoService.SetEncKeyAsync(response.Key);
await _cryptoService.SetEncPrivateKeyAsync(response.PrivateKey);
await _cryptoService.SetOrgKeysAsync(response.Organizations);
await _userService.SetSecurityStampAsync(response.SecurityStamp);
var organizations = response.Organizations.ToDictionary(o => o.Id, o => new OrganizationData(o));
await _userService.ReplaceOrganizationsAsync(organizations);
}
private async Task SyncFoldersAsync(string userId, List<FolderResponse> response)
{
var folders = response.ToDictionary(f => f.Id, f => new FolderData(f, userId));
await _folderService.ReplaceAsync(folders);
}
private async Task SyncCollectionsAsync(List<CollectionDetailsResponse> response)
{
var collections = response.ToDictionary(c => c.Id, c => new CollectionData(c));
await _collectionService.ReplaceAsync(collections);
}
private async Task SyncCiphersAsync(string userId, List<CipherResponse> response)
{
var ciphers = response.ToDictionary(c => c.Id, c => new CipherData(c, userId));
await _cipherService.ReplaceAsync(ciphers);
}
private async Task SyncSettingsAsync(string userId, DomainsResponse response)
{
var eqDomains = new List<List<string>>();
if(response != null && response.EquivalentDomains != null)
{
eqDomains = eqDomains.Concat(response.EquivalentDomains).ToList();
}
if(response != null && response.GlobalEquivalentDomains != null)
{
foreach(var global in response.GlobalEquivalentDomains)
{
if(global.Domains.Any())
{
eqDomains.Add(global.Domains);
}
}
}
await _settingsService.SetEquivalentDomainsAsync(eqDomains);
}
private async Task SyncPolicies(List<PolicyResponse> response)
{
var policies = response?.ToDictionary(p => p.Id, p => new PolicyData(p)) ??
new Dictionary<string, PolicyData>();
await _policyService.Replace(policies);
}
2019-04-17 16:38:20 +03:00
}
}