diff --git a/src/App/Abstractions/Repositories/IAccountsApiRepository.cs b/src/App/Abstractions/Repositories/IAccountsApiRepository.cs index 2c21643f5..26ab8a1aa 100644 --- a/src/App/Abstractions/Repositories/IAccountsApiRepository.cs +++ b/src/App/Abstractions/Repositories/IAccountsApiRepository.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Bit.App.Models.Api; +using System; namespace Bit.App.Abstractions { @@ -7,5 +8,6 @@ namespace Bit.App.Abstractions { Task<ApiResult> PostRegisterAsync(RegisterRequest requestObj); Task<ApiResult> PostPasswordHintAsync(PasswordHintRequest requestObj); + Task<ApiResult<DateTime?>> GetAccountRevisionDate(); } } \ No newline at end of file diff --git a/src/App/Abstractions/Services/ISyncService.cs b/src/App/Abstractions/Services/ISyncService.cs index deaaf4367..ffb56b40c 100644 --- a/src/App/Abstractions/Services/ISyncService.cs +++ b/src/App/Abstractions/Services/ISyncService.cs @@ -9,8 +9,7 @@ namespace Bit.App.Abstractions Task<bool> SyncAsync(string id); Task<bool> SyncDeleteFolderAsync(string id, DateTime revisionDate); Task<bool> SyncDeleteLoginAsync(string id); - Task<bool> FullSyncAsync(); - Task<bool> IncrementalSyncAsync(TimeSpan syncThreshold); - Task<bool> IncrementalSyncAsync(); + Task<bool> FullSyncAsync(bool forceSync = false); + Task<bool> FullSyncAsync(TimeSpan syncThreshold, bool forceSync = false); } } diff --git a/src/App/App.cs b/src/App/App.cs index b81761339..142864122 100644 --- a/src/App/App.cs +++ b/src/App/App.cs @@ -81,7 +81,7 @@ namespace Bit.App MessagingCenter.Subscribe<Application, bool>(Current, "Resumed", async (sender, args) => { await CheckLockAsync(args); - await Task.Run(() => IncrementalSyncAsync()).ConfigureAwait(false); + await Task.Run(() => FullSyncAsync()).ConfigureAwait(false); }); MessagingCenter.Subscribe<Application, bool>(Current, "Lock", (sender, args) => @@ -153,6 +153,11 @@ namespace Bit.App { lockPinPage.PinControl.Entry.FocusWithDelay(); } + + if(Device.OS == TargetPlatform.Android) + { + await Task.Run(() => FullSyncAsync()).ConfigureAwait(false); + } } private void SetMainPageFromAutofill() @@ -166,44 +171,6 @@ namespace Bit.App _uri = null; } - private async Task IncrementalSyncAsync() - { - if(_connectivity.IsConnected) - { - var attempt = 0; - do - { - try - { - await _syncService.IncrementalSyncAsync(TimeSpan.FromMinutes(30)).ConfigureAwait(false); - break; - } - catch(WebException) - { - Debug.WriteLine("Failed to incremental sync."); - if(attempt >= 1) - { - break; - } - else - { - await Task.Delay(1000); - } - attempt++; - } - catch(Exception e) when(e is TaskCanceledException || e is OperationCanceledException) - { - Debug.WriteLine("Cancellation exception."); - break; - } - } while(attempt <= 1); - } - else - { - Debug.WriteLine("Not connected."); - } - } - private async Task FullSyncAsync() { if(_connectivity.IsConnected) @@ -213,7 +180,7 @@ namespace Bit.App { try { - await _syncService.FullSyncAsync().ConfigureAwait(false); + await _syncService.FullSyncAsync(TimeSpan.FromMinutes(30)).ConfigureAwait(false); break; } catch(WebException) diff --git a/src/App/Pages/LoginPage.cs b/src/App/Pages/LoginPage.cs index bbf685d47..06ca9b6a4 100644 --- a/src/App/Pages/LoginPage.cs +++ b/src/App/Pages/LoginPage.cs @@ -218,7 +218,7 @@ namespace Bit.App.Pages _pushNotification.Register(); } - var task = Task.Run(async () => await _syncService.FullSyncAsync()); + var task = Task.Run(async () => await _syncService.FullSyncAsync(true)); Application.Current.MainPage = new MainPage(); } } diff --git a/src/App/Pages/LoginTwoFactorPage.cs b/src/App/Pages/LoginTwoFactorPage.cs index 781722057..201e08b2b 100644 --- a/src/App/Pages/LoginTwoFactorPage.cs +++ b/src/App/Pages/LoginTwoFactorPage.cs @@ -183,7 +183,7 @@ namespace Bit.App.Pages _pushNotification.Register(); } - var task = Task.Run(async () => await _syncService.FullSyncAsync()); + var task = Task.Run(async () => await _syncService.FullSyncAsync(true)); Application.Current.MainPage = new MainPage(); } } diff --git a/src/App/Pages/Settings/SettingsSyncPage.cs b/src/App/Pages/Settings/SettingsSyncPage.cs index a6343e855..48db5749e 100644 --- a/src/App/Pages/Settings/SettingsSyncPage.cs +++ b/src/App/Pages/Settings/SettingsSyncPage.cs @@ -94,7 +94,7 @@ namespace Bit.App.Pages } _userDialogs.ShowLoading(AppResources.Syncing, MaskType.Black); - var succeeded = await _syncService.FullSyncAsync(); + var succeeded = await _syncService.FullSyncAsync(true); _userDialogs.HideLoading(); if(succeeded) { diff --git a/src/App/Repositories/AccountsApiRepository.cs b/src/App/Repositories/AccountsApiRepository.cs index 84000e096..9817c0e1a 100644 --- a/src/App/Repositories/AccountsApiRepository.cs +++ b/src/App/Repositories/AccountsApiRepository.cs @@ -10,6 +10,8 @@ namespace Bit.App.Repositories { public class AccountsApiRepository : BaseApiRepository, IAccountsApiRepository { + private static readonly DateTime _epoc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + public AccountsApiRepository( IConnectivity connectivity, IHttpService httpService, @@ -82,5 +84,54 @@ namespace Bit.App.Repositories } } } + + public virtual async Task<ApiResult<DateTime?>> GetAccountRevisionDate() + { + if(!Connectivity.IsConnected) + { + return HandledNotConnected<DateTime?>(); + } + + var tokenStateResponse = await HandleTokenStateAsync<DateTime?>(); + if(!tokenStateResponse.Succeeded) + { + return tokenStateResponse; + } + + using(var client = HttpService.Client) + { + var requestMessage = new TokenHttpRequestMessage() + { + Method = HttpMethod.Get, + RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "/revision-date")), + }; + + try + { + var response = await client.SendAsync(requestMessage).ConfigureAwait(false); + if(!response.IsSuccessStatusCode) + { + return await HandleErrorAsync<DateTime?>(response).ConfigureAwait(false); + } + + var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + if(responseContent.Contains("null")) + { + return ApiResult<DateTime?>.Success(null, response.StatusCode); + } + + long ms; + if(!long.TryParse(responseContent, out ms)) + { + return await HandleErrorAsync<DateTime?>(response).ConfigureAwait(false); + } + return ApiResult<DateTime?>.Success(_epoc.AddMilliseconds(ms), response.StatusCode); + } + catch + { + return HandledWebException<DateTime?>(); + } + } + } } } diff --git a/src/App/Repositories/BaseApiRepository.cs b/src/App/Repositories/BaseApiRepository.cs index 24d3abdba..c5631fde1 100644 --- a/src/App/Repositories/BaseApiRepository.cs +++ b/src/App/Repositories/BaseApiRepository.cs @@ -60,7 +60,7 @@ namespace Bit.App.Repositories var requestMessage = new HttpRequestMessage { Method = HttpMethod.Post, - RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "/token")), + RequestUri = new Uri(client.BaseAddress, "connect/token"), Content = new FormUrlEncodedContent(new TokenRequest { Email = "abcdefgh", @@ -97,7 +97,7 @@ namespace Bit.App.Repositories var requestMessage = new HttpRequestMessage { Method = HttpMethod.Post, - RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "/token")), + RequestUri = new Uri(client.BaseAddress, "connect/token"), Content = new FormUrlEncodedContent(new Dictionary<string, string> { { "grant_type", "refresh_token" }, @@ -119,7 +119,7 @@ namespace Bit.App.Repositories TokenService.Token = tokenResponse.AccessToken; TokenService.RefreshToken = tokenResponse.RefreshToken; } - catch + catch(Exception ee) { return webException.Invoke(); } diff --git a/src/App/Services/PushNotificationListener.cs b/src/App/Services/PushNotificationListener.cs index 5dcbc2375..71d83dc36 100644 --- a/src/App/Services/PushNotificationListener.cs +++ b/src/App/Services/PushNotificationListener.cs @@ -88,7 +88,7 @@ namespace Bit.App.Services { break; } - _syncService.FullSyncAsync(); + _syncService.FullSyncAsync(true); break; default: break; diff --git a/src/App/Services/SyncService.cs b/src/App/Services/SyncService.cs index e846b4d4c..a2631c69b 100644 --- a/src/App/Services/SyncService.cs +++ b/src/App/Services/SyncService.cs @@ -15,6 +15,7 @@ namespace Bit.App.Services private readonly ICipherApiRepository _cipherApiRepository; private readonly IFolderApiRepository _folderApiRepository; private readonly ILoginApiRepository _loginApiRepository; + private readonly IAccountsApiRepository _accountsApiRepository; private readonly IFolderRepository _folderRepository; private readonly ILoginRepository _loginRepository; private readonly IAuthService _authService; @@ -24,6 +25,7 @@ namespace Bit.App.Services ICipherApiRepository cipherApiRepository, IFolderApiRepository folderApiRepository, ILoginApiRepository loginApiRepository, + IAccountsApiRepository accountsApiRepository, IFolderRepository folderRepository, ILoginRepository loginRepository, IAuthService authService, @@ -32,6 +34,7 @@ namespace Bit.App.Services _cipherApiRepository = cipherApiRepository; _folderApiRepository = folderApiRepository; _loginApiRepository = loginApiRepository; + _accountsApiRepository = accountsApiRepository; _folderRepository = folderRepository; _loginRepository = loginRepository; _authService = authService; @@ -126,13 +129,30 @@ namespace Bit.App.Services return true; } - public async Task<bool> FullSyncAsync() + public async Task<bool> FullSyncAsync(TimeSpan syncThreshold, bool forceSync = false) + { + DateTime? lastSync = _settings.GetValueOrDefault<DateTime?>(Constants.LastSync, null); + if(lastSync != null && DateTime.UtcNow - lastSync.Value < syncThreshold) + { + return false; + } + + return await FullSyncAsync(forceSync).ConfigureAwait(false); + } + + public async Task<bool> FullSyncAsync(bool forceSync = false) { if(!_authService.IsAuthenticated) { return false; } + if(!forceSync && !(await NeedsToSyncAsync())) + { + _settings.AddOrUpdateValue(Constants.LastSync, DateTime.UtcNow); + return false; + } + SyncStarted(); var now = DateTime.UtcNow; @@ -168,64 +188,22 @@ namespace Bit.App.Services return true; } - public async Task<bool> IncrementalSyncAsync(TimeSpan syncThreshold) + private async Task<bool> NeedsToSyncAsync() { DateTime? lastSync = _settings.GetValueOrDefault<DateTime?>(Constants.LastSync, null); - if(lastSync != null && DateTime.UtcNow - lastSync.Value < syncThreshold) + if(!lastSync.HasValue) { - return false; + return true; } - return await IncrementalSyncAsync().ConfigureAwait(false); - } - - public async Task<bool> IncrementalSyncAsync() - { - if(!_authService.IsAuthenticated) + var accountRevisionDate = await _accountsApiRepository.GetAccountRevisionDate(); + if(accountRevisionDate.Succeeded && accountRevisionDate.Result.HasValue && + accountRevisionDate.Result.Value > lastSync) { - return false; + return true; } - var now = DateTime.UtcNow; - DateTime? lastSync = _settings.GetValueOrDefault<DateTime?>(Constants.LastSync, null); - if(lastSync == null) - { - return await FullSyncAsync().ConfigureAwait(false); - } - - SyncStarted(); - - var ciphers = await _cipherApiRepository.GetByRevisionDateWithHistoryAsync(lastSync.Value).ConfigureAwait(false); - if(!ciphers.Succeeded) - { - SyncCompleted(false); - - if(Application.Current != null && (ciphers.StatusCode == System.Net.HttpStatusCode.Forbidden - || ciphers.StatusCode == System.Net.HttpStatusCode.Unauthorized)) - { - MessagingCenter.Send(Application.Current, "Logout", (string)null); - } - - return false; - } - - var logins = ciphers.Result.Revised.Where(c => c.Type == Enums.CipherType.Login).ToDictionary(s => s.Id); - var folders = ciphers.Result.Revised.Where(c => c.Type == Enums.CipherType.Folder).ToDictionary(f => f.Id); - - var loginTask = SyncLoginsAsync(logins, false); - var folderTask = SyncFoldersAsync(folders, false); - var deleteTask = DeleteCiphersAsync(ciphers.Result.Deleted); - - await Task.WhenAll(loginTask, folderTask, deleteTask).ConfigureAwait(false); - if(folderTask.Exception != null || loginTask.Exception != null || deleteTask.Exception != null) - { - SyncCompleted(false); - return false; - } - - _settings.AddOrUpdateValue(Constants.LastSync, now); - SyncCompleted(true); - return true; + return false; } private async Task SyncFoldersAsync(IDictionary<string, CipherResponse> serverFolders, bool deleteMissing) diff --git a/src/App/Utilities/ApiHttpClient.cs b/src/App/Utilities/ApiHttpClient.cs index 498cae7d6..5a68f46c7 100644 --- a/src/App/Utilities/ApiHttpClient.cs +++ b/src/App/Utilities/ApiHttpClient.cs @@ -19,6 +19,7 @@ namespace Bit.App private void Init() { + //BaseAddress = new Uri("http://192.168.1.3:4000"); BaseAddress = new Uri("https://api.bitwarden.com"); DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); }