mirror of
https://github.com/bitwarden/mobile
synced 2025-12-17 16:53:26 +00:00
sync org keys and refactors
This commit is contained in:
@@ -11,6 +11,7 @@ namespace Bit.App.Abstractions
|
|||||||
Task<bool> SyncDeleteFolderAsync(string id, DateTime revisionDate);
|
Task<bool> SyncDeleteFolderAsync(string id, DateTime revisionDate);
|
||||||
Task<bool> SyncDeleteLoginAsync(string id);
|
Task<bool> SyncDeleteLoginAsync(string id);
|
||||||
Task<bool> SyncSettingsAsync();
|
Task<bool> SyncSettingsAsync();
|
||||||
|
Task<bool> SyncProfileAsync();
|
||||||
Task<bool> FullSyncAsync(bool forceSync = false);
|
Task<bool> FullSyncAsync(bool forceSync = false);
|
||||||
Task<bool> FullSyncAsync(TimeSpan syncThreshold, bool forceSync = false);
|
Task<bool> FullSyncAsync(TimeSpan syncThreshold, bool forceSync = false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,11 +60,13 @@ namespace Bit.App.Services
|
|||||||
case Enums.PushType.SyncCipherUpdate:
|
case Enums.PushType.SyncCipherUpdate:
|
||||||
case Enums.PushType.SyncCipherCreate:
|
case Enums.PushType.SyncCipherCreate:
|
||||||
var cipherCreateUpdateMessage = values.ToObject<SyncCipherPushNotification>();
|
var cipherCreateUpdateMessage = values.ToObject<SyncCipherPushNotification>();
|
||||||
if(cipherCreateUpdateMessage.UserId != null && cipherCreateUpdateMessage.UserId != _authService.UserId)
|
if(cipherCreateUpdateMessage.OrganizationId == null &&
|
||||||
|
cipherCreateUpdateMessage.UserId != _authService.UserId)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else if(!_authService.BelongsToOrganization(cipherCreateUpdateMessage.OrganizationId))
|
else if(cipherCreateUpdateMessage.OrganizationId != null &&
|
||||||
|
!_authService.BelongsToOrganization(cipherCreateUpdateMessage.OrganizationId))
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -89,11 +91,13 @@ namespace Bit.App.Services
|
|||||||
break;
|
break;
|
||||||
case Enums.PushType.SyncLoginDelete:
|
case Enums.PushType.SyncLoginDelete:
|
||||||
var loginDeleteMessage = values.ToObject<SyncCipherPushNotification>();
|
var loginDeleteMessage = values.ToObject<SyncCipherPushNotification>();
|
||||||
if(loginDeleteMessage.UserId != null && loginDeleteMessage.UserId != _authService.UserId)
|
if(loginDeleteMessage.OrganizationId == null &&
|
||||||
|
loginDeleteMessage.UserId != _authService.UserId)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else if(!_authService.BelongsToOrganization(loginDeleteMessage.OrganizationId))
|
else if(loginDeleteMessage.OrganizationId != null &&
|
||||||
|
!_authService.BelongsToOrganization(loginDeleteMessage.OrganizationId))
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -116,6 +120,14 @@ namespace Bit.App.Services
|
|||||||
}
|
}
|
||||||
_syncService.SyncSettingsAsync();
|
_syncService.SyncSettingsAsync();
|
||||||
break;
|
break;
|
||||||
|
case Enums.PushType.SyncOrgKeys:
|
||||||
|
var orgKeysMessage = values.ToObject<SyncUserPushNotification>();
|
||||||
|
if(orgKeysMessage.UserId != _authService.UserId)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_syncService.SyncProfileAsync();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ using Bit.App.Models.Api;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using Bit.App.Models;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace Bit.App.Services
|
namespace Bit.App.Services
|
||||||
{
|
{
|
||||||
@@ -22,6 +24,7 @@ namespace Bit.App.Services
|
|||||||
private readonly ILoginRepository _loginRepository;
|
private readonly ILoginRepository _loginRepository;
|
||||||
private readonly ISettingsRepository _settingsRepository;
|
private readonly ISettingsRepository _settingsRepository;
|
||||||
private readonly IAuthService _authService;
|
private readonly IAuthService _authService;
|
||||||
|
private readonly ICryptoService _cryptoService;
|
||||||
private readonly ISettings _settings;
|
private readonly ISettings _settings;
|
||||||
|
|
||||||
public SyncService(
|
public SyncService(
|
||||||
@@ -34,6 +37,7 @@ namespace Bit.App.Services
|
|||||||
ILoginRepository loginRepository,
|
ILoginRepository loginRepository,
|
||||||
ISettingsRepository settingsRepository,
|
ISettingsRepository settingsRepository,
|
||||||
IAuthService authService,
|
IAuthService authService,
|
||||||
|
ICryptoService cryptoService,
|
||||||
ISettings settings)
|
ISettings settings)
|
||||||
{
|
{
|
||||||
_cipherApiRepository = cipherApiRepository;
|
_cipherApiRepository = cipherApiRepository;
|
||||||
@@ -45,6 +49,7 @@ namespace Bit.App.Services
|
|||||||
_loginRepository = loginRepository;
|
_loginRepository = loginRepository;
|
||||||
_settingsRepository = settingsRepository;
|
_settingsRepository = settingsRepository;
|
||||||
_authService = authService;
|
_authService = authService;
|
||||||
|
_cryptoService = cryptoService;
|
||||||
_settings = settings;
|
_settings = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,16 +65,8 @@ namespace Bit.App.Services
|
|||||||
SyncStarted();
|
SyncStarted();
|
||||||
|
|
||||||
var cipher = await _cipherApiRepository.GetByIdAsync(id).ConfigureAwait(false);
|
var cipher = await _cipherApiRepository.GetByIdAsync(id).ConfigureAwait(false);
|
||||||
if(!cipher.Succeeded)
|
if(!CheckSuccess(cipher))
|
||||||
{
|
{
|
||||||
SyncCompleted(false);
|
|
||||||
|
|
||||||
if(Application.Current != null && (cipher.StatusCode == System.Net.HttpStatusCode.Forbidden
|
|
||||||
|| cipher.StatusCode == System.Net.HttpStatusCode.Unauthorized))
|
|
||||||
{
|
|
||||||
MessagingCenter.Send(Application.Current, "Logout", (string)null);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,16 +103,8 @@ namespace Bit.App.Services
|
|||||||
SyncStarted();
|
SyncStarted();
|
||||||
|
|
||||||
var folder = await _folderApiRepository.GetByIdAsync(id).ConfigureAwait(false);
|
var folder = await _folderApiRepository.GetByIdAsync(id).ConfigureAwait(false);
|
||||||
if(!folder.Succeeded)
|
if(!CheckSuccess(folder))
|
||||||
{
|
{
|
||||||
SyncCompleted(false);
|
|
||||||
|
|
||||||
if(Application.Current != null && (folder.StatusCode == System.Net.HttpStatusCode.Forbidden
|
|
||||||
|| folder.StatusCode == System.Net.HttpStatusCode.Unauthorized))
|
|
||||||
{
|
|
||||||
MessagingCenter.Send(Application.Current, "Logout", (string)null);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,16 +177,8 @@ namespace Bit.App.Services
|
|||||||
SyncStarted();
|
SyncStarted();
|
||||||
|
|
||||||
var domains = await _settingsApiRepository.GetDomains(false).ConfigureAwait(false);
|
var domains = await _settingsApiRepository.GetDomains(false).ConfigureAwait(false);
|
||||||
if(!domains.Succeeded)
|
if(!CheckSuccess(domains))
|
||||||
{
|
{
|
||||||
SyncCompleted(false);
|
|
||||||
|
|
||||||
if(Application.Current != null && (domains.StatusCode == System.Net.HttpStatusCode.Forbidden
|
|
||||||
|| domains.StatusCode == System.Net.HttpStatusCode.Unauthorized))
|
|
||||||
{
|
|
||||||
MessagingCenter.Send(Application.Current, "Logout", (string)null);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,6 +187,26 @@ namespace Bit.App.Services
|
|||||||
SyncCompleted(true);
|
SyncCompleted(true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
public async Task<bool> SyncProfileAsync()
|
||||||
|
{
|
||||||
|
if(!_authService.IsAuthenticated)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SyncStarted();
|
||||||
|
|
||||||
|
var profile = await _accountsApiRepository.GetProfileAsync().ConfigureAwait(false);
|
||||||
|
if(!CheckSuccess(profile))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SyncOrgKeys(profile.Result);
|
||||||
|
|
||||||
|
SyncCompleted(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<bool> FullSyncAsync(TimeSpan syncThreshold, bool forceSync = false)
|
public async Task<bool> FullSyncAsync(TimeSpan syncThreshold, bool forceSync = false)
|
||||||
{
|
{
|
||||||
@@ -234,26 +235,19 @@ namespace Bit.App.Services
|
|||||||
SyncStarted();
|
SyncStarted();
|
||||||
|
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
var ciphers = await _cipherApiRepository.GetAsync().ConfigureAwait(false);
|
|
||||||
var folders = await _folderApiRepository.GetAsync().ConfigureAwait(false);
|
|
||||||
var domains = await _settingsApiRepository.GetDomains(false).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if(!ciphers.Succeeded || !domains.Succeeded)
|
// Just check profile first to make sure we'll have a success with the API
|
||||||
{
|
var profile = await _accountsApiRepository.GetProfileAsync().ConfigureAwait(false);
|
||||||
SyncCompleted(false);
|
if(!CheckSuccess(profile))
|
||||||
if(Application.Current == null)
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(ciphers.StatusCode == System.Net.HttpStatusCode.Forbidden ||
|
var ciphers = await _cipherApiRepository.GetAsync().ConfigureAwait(false);
|
||||||
ciphers.StatusCode == System.Net.HttpStatusCode.Unauthorized ||
|
var folders = await _folderApiRepository.GetAsync().ConfigureAwait(false);
|
||||||
domains.StatusCode == System.Net.HttpStatusCode.Forbidden ||
|
var domains = await _settingsApiRepository.GetDomains(false).ConfigureAwait(false);
|
||||||
domains.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
if(!CheckSuccess(ciphers) || !CheckSuccess(folders) || !CheckSuccess(domains))
|
||||||
{
|
{
|
||||||
MessagingCenter.Send(Application.Current, "Logout", (string)null);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,6 +257,7 @@ namespace Bit.App.Services
|
|||||||
var loginTask = SyncLoginsAsync(loginsDict);
|
var loginTask = SyncLoginsAsync(loginsDict);
|
||||||
var folderTask = SyncFoldersAsync(foldersDict);
|
var folderTask = SyncFoldersAsync(foldersDict);
|
||||||
var domainsTask = SyncDomainsAsync(domains.Result);
|
var domainsTask = SyncDomainsAsync(domains.Result);
|
||||||
|
SyncOrgKeys(profile.Result);
|
||||||
await Task.WhenAll(loginTask, folderTask, domainsTask).ConfigureAwait(false);
|
await Task.WhenAll(loginTask, folderTask, domainsTask).ConfigureAwait(false);
|
||||||
|
|
||||||
if(folderTask.Exception != null || loginTask.Exception != null || domainsTask.Exception != null)
|
if(folderTask.Exception != null || loginTask.Exception != null || domainsTask.Exception != null)
|
||||||
@@ -394,6 +389,29 @@ namespace Bit.App.Services
|
|||||||
catch(SQLite.SQLiteException) { }
|
catch(SQLite.SQLiteException) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SyncOrgKeys(ProfileResponse profile)
|
||||||
|
{
|
||||||
|
var orgKeysDict = new Dictionary<string, CryptoKey>();
|
||||||
|
|
||||||
|
if(profile.Organizations != null)
|
||||||
|
{
|
||||||
|
foreach(var org in profile.Organizations)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var decBytes = _cryptoService.RsaDecryptToBytes(new CipherString(org.Key), null);
|
||||||
|
orgKeysDict.Add(org.Id, new CryptoKey(decBytes));
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Debug.WriteLine($"Cannot set org key {org.Id}. Decryption failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_cryptoService.OrgKeys = orgKeysDict;
|
||||||
|
}
|
||||||
|
|
||||||
private void SyncStarted()
|
private void SyncStarted()
|
||||||
{
|
{
|
||||||
if(Application.Current == null)
|
if(Application.Current == null)
|
||||||
@@ -415,5 +433,23 @@ namespace Bit.App.Services
|
|||||||
SyncInProgress = false;
|
SyncInProgress = false;
|
||||||
MessagingCenter.Send(Application.Current, "SyncCompleted", successfully);
|
MessagingCenter.Send(Application.Current, "SyncCompleted", successfully);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool CheckSuccess<T>(ApiResult<T> result)
|
||||||
|
{
|
||||||
|
if(!result.Succeeded)
|
||||||
|
{
|
||||||
|
SyncCompleted(false);
|
||||||
|
|
||||||
|
if(Application.Current != null && (result.StatusCode == System.Net.HttpStatusCode.Forbidden
|
||||||
|
|| result.StatusCode == System.Net.HttpStatusCode.Unauthorized))
|
||||||
|
{
|
||||||
|
MessagingCenter.Send(Application.Current, "Logout", (string)null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6245" systemVersion="13F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="X5k-f2-b5h">
|
|
||||||
<dependencies>
|
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6238"/>
|
|
||||||
</dependencies>
|
|
||||||
<scenes>
|
|
||||||
<!--View Controller-->
|
|
||||||
<scene sceneID="gAE-YM-kbH">
|
|
||||||
<objects>
|
|
||||||
<viewController id="X5k-f2-b5h" sceneMemberID="viewController">
|
|
||||||
<layoutGuides>
|
|
||||||
<viewControllerLayoutGuide type="top" id="Y8P-hJ-Z43"/>
|
|
||||||
<viewControllerLayoutGuide type="bottom" id="9ZL-r4-8FZ"/>
|
|
||||||
</layoutGuides>
|
|
||||||
<view key="view" contentMode="scaleToFill" id="yd7-JS-zBw">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
|
||||||
<subviews>
|
|
||||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" misplaced="YES" image="Icon-60.png" translatesAutoresizingMaskIntoConstraints="NO" id="23">
|
|
||||||
<rect key="frame" x="270" y="270" width="60" height="60"/>
|
|
||||||
<rect key="contentStretch" x="0.0" y="0.0" width="0.0" height="0.0"/>
|
|
||||||
</imageView>
|
|
||||||
</subviews>
|
|
||||||
<color key="backgroundColor" red="0.20392156862745098" green="0.59607843137254901" blue="0.85882352941176465" alpha="1" colorSpace="calibratedRGB"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstItem="23" firstAttribute="centerY" secondItem="yd7-JS-zBw" secondAttribute="centerY" priority="1" id="39"/>
|
|
||||||
<constraint firstItem="23" firstAttribute="centerX" secondItem="yd7-JS-zBw" secondAttribute="centerX" priority="1" id="41"/>
|
|
||||||
</constraints>
|
|
||||||
</view>
|
|
||||||
</viewController>
|
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="XAI-xm-WK6" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
|
||||||
</objects>
|
|
||||||
<point key="canvasLocation" x="349" y="339"/>
|
|
||||||
</scene>
|
|
||||||
</scenes>
|
|
||||||
<resources>
|
|
||||||
<image name="Icon-60.png" width="180" height="180"/>
|
|
||||||
</resources>
|
|
||||||
</document>
|
|
||||||
@@ -253,7 +253,6 @@
|
|||||||
<BundleResource Include="Resources\Icon-Small.png" />
|
<BundleResource Include="Resources\Icon-Small.png" />
|
||||||
<BundleResource Include="Resources\Icon-Small%402x.png" />
|
<BundleResource Include="Resources\Icon-Small%402x.png" />
|
||||||
<BundleResource Include="Resources\Icon-Small%403x.png" />
|
<BundleResource Include="Resources\Icon-Small%403x.png" />
|
||||||
<InterfaceDefinition Include="Resources\LaunchScreen.storyboard" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="Acr.Support.iOS, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
<Reference Include="Acr.Support.iOS, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
|
|||||||
Reference in New Issue
Block a user