mirror of
https://github.com/bitwarden/mobile
synced 2025-12-05 23:53:33 +00:00
Compare commits
14 Commits
renovate/l
...
v2022.6.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
efe128b932 | ||
|
|
df2e52f82c | ||
|
|
2d224f5e22 | ||
|
|
4831097c0b | ||
|
|
72f2983885 | ||
|
|
ee68dcf3b0 | ||
|
|
daab473cbb | ||
|
|
e6b99270cf | ||
|
|
a8d9aaa7fe | ||
|
|
542ef5f31a | ||
|
|
0b2fc2a647 | ||
|
|
a95fdf67a1 | ||
|
|
73890162bf | ||
|
|
ef14a8f850 |
15
.github/workflows/build.yml
vendored
15
.github/workflows/build.yml
vendored
@@ -60,6 +60,11 @@ jobs:
|
|||||||
runs-on: windows-2019
|
runs-on: windows-2019
|
||||||
needs: setup
|
needs: setup
|
||||||
steps:
|
steps:
|
||||||
|
- name: Setup NuGet
|
||||||
|
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
|
||||||
|
with:
|
||||||
|
nuget-version: 5.9.0
|
||||||
|
|
||||||
- name: Set up MSBuild
|
- name: Set up MSBuild
|
||||||
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
|
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
|
||||||
|
|
||||||
@@ -209,6 +214,11 @@ jobs:
|
|||||||
name: F-Droid Build
|
name: F-Droid Build
|
||||||
runs-on: windows-2019
|
runs-on: windows-2019
|
||||||
steps:
|
steps:
|
||||||
|
- name: Setup NuGet
|
||||||
|
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
|
||||||
|
with:
|
||||||
|
nuget-version: 5.9.0
|
||||||
|
|
||||||
- name: Set up MSBuild
|
- name: Set up MSBuild
|
||||||
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
|
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
|
||||||
|
|
||||||
@@ -368,6 +378,11 @@ jobs:
|
|||||||
runs-on: macos-11
|
runs-on: macos-11
|
||||||
needs: setup
|
needs: setup
|
||||||
steps:
|
steps:
|
||||||
|
- name: Setup NuGet
|
||||||
|
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
|
||||||
|
with:
|
||||||
|
nuget-version: 5.9.0
|
||||||
|
|
||||||
- name: Print environment
|
- name: Print environment
|
||||||
run: |
|
run: |
|
||||||
nuget help | grep Version
|
nuget help | grep Version
|
||||||
|
|||||||
34
.github/workflows/release.yml
vendored
34
.github/workflows/release.yml
vendored
@@ -34,29 +34,13 @@ jobs:
|
|||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
|
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
|
||||||
|
|
||||||
- name: Retrieve Mobile release version
|
- name: Check Release Version
|
||||||
id: retrieve-mobile-version
|
id: version
|
||||||
run: |
|
uses: bitwarden/gh-actions/release-version-check@8f055ef543c7433c967a1b9b04a0f230923233bb
|
||||||
ver=$(sed -E -n '/^<manifest/s/^.*[ ]android:versionName="([^"]+)".*$/\1/p' \
|
with:
|
||||||
./src/Android/Properties/AndroidManifest.xml | tr -d '"')
|
release-type: ${{ github.event.inputs.release_type }}
|
||||||
echo "::set-output name=mobile_version::${ver}"
|
project-type: xamarin
|
||||||
shell: bash
|
file: src/Android/Properties/AndroidManifest.xml
|
||||||
|
|
||||||
- name: Check to make sure Mobile release version has been bumped
|
|
||||||
if: ${{ github.event.inputs.release_type == 'Initial Release' }}
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
latest_ver=$(hub release -L 1 -f '%T')
|
|
||||||
latest_ver=${latest_ver:1}
|
|
||||||
echo "Latest version: $latest_ver"
|
|
||||||
ver=${{ steps.retrieve-mobile-version.outputs.mobile_version }}
|
|
||||||
echo "Version: $ver"
|
|
||||||
if [ "$latest_ver" = "$ver" ]; then
|
|
||||||
echo "Version has not been bumped!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Get branch name
|
- name: Get branch name
|
||||||
id: branch
|
id: branch
|
||||||
@@ -83,8 +67,8 @@ jobs:
|
|||||||
./com.x8bit.bitwarden-fdroid.apk/com.x8bit.bitwarden-fdroid.apk,
|
./com.x8bit.bitwarden-fdroid.apk/com.x8bit.bitwarden-fdroid.apk,
|
||||||
./Bitwarden iOS.zip"
|
./Bitwarden iOS.zip"
|
||||||
commit: ${{ github.sha }}
|
commit: ${{ github.sha }}
|
||||||
tag: v${{ steps.retrieve-mobile-version.outputs.mobile_version }}
|
tag: v${{ steps.version.outputs.version }}
|
||||||
name: Version ${{ steps.retrieve-mobile-version.outputs.mobile_version }}
|
name: Version ${{ steps.version.outputs.version }}
|
||||||
body: "<insert release notes here>"
|
body: "<insert release notes here>"
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
draft: true
|
draft: true
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ namespace Bit.Droid
|
|||||||
var secureStorageService = new SecureStorageService();
|
var secureStorageService = new SecureStorageService();
|
||||||
var cryptoPrimitiveService = new CryptoPrimitiveService();
|
var cryptoPrimitiveService = new CryptoPrimitiveService();
|
||||||
var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage);
|
var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage);
|
||||||
var stateService = new StateService(mobileStorageService, secureStorageService);
|
var stateService = new StateService(mobileStorageService, secureStorageService, messagingService);
|
||||||
var stateMigrationService =
|
var stateMigrationService =
|
||||||
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
|
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
|
||||||
var clipboardService = new ClipboardService(stateService);
|
var clipboardService = new ClipboardService(stateService);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2022.05.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2022.6.2" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>
|
||||||
|
|
||||||
|
|||||||
@@ -202,6 +202,7 @@ namespace Bit.App
|
|||||||
|
|
||||||
private async Task ResumedAsync()
|
private async Task ResumedAsync()
|
||||||
{
|
{
|
||||||
|
await _stateService.CheckExtensionActiveUserAndSwitchIfNeededAsync();
|
||||||
await _vaultTimeoutService.CheckVaultTimeoutAsync();
|
await _vaultTimeoutService.CheckVaultTimeoutAsync();
|
||||||
_messagingService.Send("startEventTimer");
|
_messagingService.Send("startEventTimer");
|
||||||
await UpdateThemeAsync();
|
await UpdateThemeAsync();
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ namespace Bit.App.Controls
|
|||||||
|
|
||||||
public bool LongPressAccountEnabled { get; set; } = true;
|
public bool LongPressAccountEnabled { get; set; } = true;
|
||||||
|
|
||||||
|
public Action AfterHide { get; set; }
|
||||||
|
|
||||||
public async Task ToggleVisibilityAsync()
|
public async Task ToggleVisibilityAsync()
|
||||||
{
|
{
|
||||||
if (IsVisible)
|
if (IsVisible)
|
||||||
@@ -137,6 +139,8 @@ namespace Bit.App.Controls
|
|||||||
|
|
||||||
// remove overlay
|
// remove overlay
|
||||||
IsVisible = false;
|
IsVisible = false;
|
||||||
|
|
||||||
|
AfterHide?.Invoke();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,23 +45,28 @@ namespace Bit.App.Controls
|
|||||||
|
|
||||||
public ICommand LongPressAccountCommand { get; }
|
public ICommand LongPressAccountCommand { get; }
|
||||||
|
|
||||||
|
public bool FromIOSExtension { get; set; }
|
||||||
|
|
||||||
private async Task SelectAccountAsync(AccountViewCellViewModel item)
|
private async Task SelectAccountAsync(AccountViewCellViewModel item)
|
||||||
{
|
{
|
||||||
if (item.AccountView.IsAccount)
|
if (!item.AccountView.IsAccount)
|
||||||
{
|
{
|
||||||
if (!item.AccountView.IsActive)
|
_messagingService.Send(AccountsManagerMessageCommands.ADD_ACCOUNT);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!item.AccountView.IsActive)
|
||||||
|
{
|
||||||
|
await _stateService.SetActiveUserAsync(item.AccountView.UserId);
|
||||||
|
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
|
||||||
|
if (FromIOSExtension)
|
||||||
{
|
{
|
||||||
await _stateService.SetActiveUserAsync(item.AccountView.UserId);
|
await _stateService.SaveExtensionActiveUserIdToStorageAsync(item.AccountView.UserId);
|
||||||
_messagingService.Send("switchedAccount");
|
|
||||||
}
|
|
||||||
else if (AllowActiveAccountSelection)
|
|
||||||
{
|
|
||||||
_messagingService.Send("switchedAccount");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else if (AllowActiveAccountSelection)
|
||||||
{
|
{
|
||||||
_messagingService.Send("addAccount");
|
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,10 +81,12 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn);
|
await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn);
|
||||||
|
string ssoToken;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _apiService.PreValidateSso(OrgIdentifier);
|
var response = await _apiService.PreValidateSso(OrgIdentifier);
|
||||||
|
ssoToken = response.Token;
|
||||||
}
|
}
|
||||||
catch (ApiException e)
|
catch (ApiException e)
|
||||||
{
|
{
|
||||||
@@ -112,7 +114,8 @@ namespace Bit.App.Pages
|
|||||||
"response_type=code&scope=api%20offline_access&" +
|
"response_type=code&scope=api%20offline_access&" +
|
||||||
"state=" + state + "&code_challenge=" + codeChallenge + "&" +
|
"state=" + state + "&code_challenge=" + codeChallenge + "&" +
|
||||||
"code_challenge_method=S256&response_mode=query&" +
|
"code_challenge_method=S256&response_mode=query&" +
|
||||||
"domain_hint=" + Uri.EscapeDataString(OrgIdentifier);
|
"domain_hint=" + Uri.EscapeDataString(OrgIdentifier) + "&" +
|
||||||
|
"ssoToken=" + Uri.EscapeDataString(ssoToken);
|
||||||
|
|
||||||
WebAuthenticatorResult authResult = null;
|
WebAuthenticatorResult authResult = null;
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -219,7 +219,8 @@ namespace Bit.App.Pages
|
|||||||
// Request
|
// Request
|
||||||
var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest
|
var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest
|
||||||
{
|
{
|
||||||
ResetPasswordKey = encryptedKey.EncryptedString
|
ResetPasswordKey = encryptedKey.EncryptedString,
|
||||||
|
MasterPasswordHash = masterPasswordHash,
|
||||||
};
|
};
|
||||||
var userId = await _stateService.GetActiveUserIdAsync();
|
var userId = await _stateService.GetActiveUserIdAsync();
|
||||||
// Enroll user
|
// Enroll user
|
||||||
|
|||||||
@@ -3,14 +3,11 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Input;
|
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.Core;
|
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Models.Domain;
|
|
||||||
using Bit.Core.Models.View;
|
using Bit.Core.Models.View;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
@@ -18,7 +15,7 @@ using Xamarin.Forms;
|
|||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public class CiphersPageViewModel : BaseViewModel
|
public class CiphersPageViewModel : VaultFilterViewModel
|
||||||
{
|
{
|
||||||
private readonly IPlatformUtilsService _platformUtilsService;
|
private readonly IPlatformUtilsService _platformUtilsService;
|
||||||
private readonly ICipherService _cipherService;
|
private readonly ICipherService _cipherService;
|
||||||
@@ -31,12 +28,9 @@ namespace Bit.App.Pages
|
|||||||
private CancellationTokenSource _searchCancellationTokenSource;
|
private CancellationTokenSource _searchCancellationTokenSource;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
private bool _showVaultFilter;
|
|
||||||
private string _vaultFilterSelection;
|
|
||||||
private bool _showNoData;
|
private bool _showNoData;
|
||||||
private bool _showList;
|
private bool _showList;
|
||||||
private bool _websiteIconsEnabled;
|
private bool _websiteIconsEnabled;
|
||||||
private List<Organization> _organizations;
|
|
||||||
|
|
||||||
public CiphersPageViewModel()
|
public CiphersPageViewModel()
|
||||||
{
|
{
|
||||||
@@ -52,18 +46,19 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
Ciphers = new ExtendedObservableCollection<CipherView>();
|
Ciphers = new ExtendedObservableCollection<CipherView>();
|
||||||
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
|
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
|
||||||
VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync,
|
|
||||||
onException: ex => _logger.Exception(ex),
|
|
||||||
allowsMultipleExecutions: false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Command CipherOptionsCommand { get; set; }
|
public Command CipherOptionsCommand { get; set; }
|
||||||
public ICommand VaultFilterCommand { get; }
|
|
||||||
public ExtendedObservableCollection<CipherView> Ciphers { get; set; }
|
public ExtendedObservableCollection<CipherView> Ciphers { get; set; }
|
||||||
public Func<CipherView, bool> Filter { get; set; }
|
public Func<CipherView, bool> Filter { get; set; }
|
||||||
public string AutofillUrl { get; set; }
|
public string AutofillUrl { get; set; }
|
||||||
public bool Deleted { get; set; }
|
public bool Deleted { get; set; }
|
||||||
|
|
||||||
|
protected override ICipherService cipherService => _cipherService;
|
||||||
|
protected override IPolicyService policyService => _policyService;
|
||||||
|
protected override IOrganizationService organizationService => _organizationService;
|
||||||
|
protected override ILogger logger => _logger;
|
||||||
|
|
||||||
public bool ShowNoData
|
public bool ShowNoData
|
||||||
{
|
{
|
||||||
get => _showNoData;
|
get => _showNoData;
|
||||||
@@ -81,23 +76,6 @@ namespace Bit.App.Pages
|
|||||||
nameof(ShowSearchDirection)
|
nameof(ShowSearchDirection)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public bool ShowVaultFilter
|
|
||||||
{
|
|
||||||
get => _showVaultFilter;
|
|
||||||
set => SetProperty(ref _showVaultFilter, value);
|
|
||||||
}
|
|
||||||
public string VaultFilterDescription
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_vaultFilterSelection == null || _vaultFilterSelection == AppResources.AllVaults)
|
|
||||||
{
|
|
||||||
return string.Format(AppResources.VaultFilterDescription, AppResources.All);
|
|
||||||
}
|
|
||||||
return string.Format(AppResources.VaultFilterDescription, _vaultFilterSelection);
|
|
||||||
}
|
|
||||||
set => SetProperty(ref _vaultFilterSelection, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ShowSearchDirection => !ShowList && !ShowNoData;
|
public bool ShowSearchDirection => !ShowList && !ShowNoData;
|
||||||
|
|
||||||
@@ -109,12 +87,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
_organizations = await _organizationService.GetAllAsync();
|
await InitVaultFilterAsync(true);
|
||||||
ShowVaultFilter = await _policyService.ShouldShowVaultFilterAsync();
|
|
||||||
if (ShowVaultFilter && _vaultFilterSelection == null)
|
|
||||||
{
|
|
||||||
_vaultFilterSelection = AppResources.AllVaults;
|
|
||||||
}
|
|
||||||
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
|
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
|
||||||
PerformSearchIfPopulated();
|
PerformSearchIfPopulated();
|
||||||
}
|
}
|
||||||
@@ -237,50 +210,11 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task VaultFilterOptionsAsync()
|
protected override async Task OnVaultFilterSelectedAsync()
|
||||||
{
|
{
|
||||||
var options = new List<string> { AppResources.AllVaults, AppResources.MyVault };
|
|
||||||
if (_organizations.Any())
|
|
||||||
{
|
|
||||||
options.AddRange(_organizations.OrderBy(o => o.Name).Select(o => o.Name));
|
|
||||||
}
|
|
||||||
var selection = await Page.DisplayActionSheet(AppResources.FilterByVault, AppResources.Cancel, null,
|
|
||||||
options.ToArray());
|
|
||||||
if (selection == null || selection == AppResources.Cancel ||
|
|
||||||
(_vaultFilterSelection == null && selection == AppResources.AllVaults) ||
|
|
||||||
(_vaultFilterSelection != null && _vaultFilterSelection == selection))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
VaultFilterDescription = selection;
|
|
||||||
PerformSearchIfPopulated();
|
PerformSearchIfPopulated();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<CipherView>> GetAllCiphersAsync()
|
|
||||||
{
|
|
||||||
var decCiphers = await _cipherService.GetAllDecryptedAsync();
|
|
||||||
if (IsVaultFilterMyVault)
|
|
||||||
{
|
|
||||||
return decCiphers.Where(c => c.OrganizationId == null).ToList();
|
|
||||||
}
|
|
||||||
if (IsVaultFilterOrgVault)
|
|
||||||
{
|
|
||||||
var orgId = GetVaultFilterOrgId();
|
|
||||||
return decCiphers.Where(c => c.OrganizationId == orgId).ToList();
|
|
||||||
}
|
|
||||||
return decCiphers;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsVaultFilterMyVault => _vaultFilterSelection == AppResources.MyVault;
|
|
||||||
|
|
||||||
private bool IsVaultFilterOrgVault => _vaultFilterSelection != AppResources.AllVaults &&
|
|
||||||
_vaultFilterSelection != AppResources.MyVault;
|
|
||||||
|
|
||||||
private string GetVaultFilterOrgId()
|
|
||||||
{
|
|
||||||
return _organizations?.FirstOrDefault(o => o.Name == _vaultFilterSelection)?.Id;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void CipherOptionsAsync(CipherView cipher)
|
private async void CipherOptionsAsync(CipherView cipher)
|
||||||
{
|
{
|
||||||
if ((Page as BaseContentPage).DoOnce())
|
if ((Page as BaseContentPage).DoOnce())
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Input;
|
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Controls;
|
using Bit.App.Controls;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
@@ -17,7 +16,7 @@ using Xamarin.Forms;
|
|||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public class GroupingsPageViewModel : BaseViewModel
|
public class GroupingsPageViewModel : VaultFilterViewModel
|
||||||
{
|
{
|
||||||
private const int NoFolderListSize = 100;
|
private const int NoFolderListSize = 100;
|
||||||
|
|
||||||
@@ -30,10 +29,7 @@ namespace Bit.App.Pages
|
|||||||
private bool _showList;
|
private bool _showList;
|
||||||
private bool _websiteIconsEnabled;
|
private bool _websiteIconsEnabled;
|
||||||
private bool _syncRefreshing;
|
private bool _syncRefreshing;
|
||||||
private bool _showVaultFilter;
|
|
||||||
private string _vaultFilterSelection;
|
|
||||||
private string _noDataText;
|
private string _noDataText;
|
||||||
private List<Organization> _organizations;
|
|
||||||
private List<CipherView> _allCiphers;
|
private List<CipherView> _allCiphers;
|
||||||
private Dictionary<string, int> _folderCounts = new Dictionary<string, int>();
|
private Dictionary<string, int> _folderCounts = new Dictionary<string, int>();
|
||||||
private Dictionary<string, int> _collectionCounts = new Dictionary<string, int>();
|
private Dictionary<string, int> _collectionCounts = new Dictionary<string, int>();
|
||||||
@@ -78,9 +74,6 @@ namespace Bit.App.Pages
|
|||||||
await LoadAsync();
|
await LoadAsync();
|
||||||
});
|
});
|
||||||
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
|
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
|
||||||
VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync,
|
|
||||||
onException: ex => _logger.Exception(ex),
|
|
||||||
allowsMultipleExecutions: false);
|
|
||||||
|
|
||||||
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
|
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
|
||||||
{
|
{
|
||||||
@@ -108,6 +101,11 @@ namespace Bit.App.Pages
|
|||||||
public List<Core.Models.View.CollectionView> Collections { get; set; }
|
public List<Core.Models.View.CollectionView> Collections { get; set; }
|
||||||
public List<TreeNode<Core.Models.View.CollectionView>> NestedCollections { get; set; }
|
public List<TreeNode<Core.Models.View.CollectionView>> NestedCollections { get; set; }
|
||||||
|
|
||||||
|
protected override ICipherService cipherService => _cipherService;
|
||||||
|
protected override IPolicyService policyService => _policyService;
|
||||||
|
protected override IOrganizationService organizationService => _organizationService;
|
||||||
|
protected override ILogger logger => _logger;
|
||||||
|
|
||||||
public bool Refreshing
|
public bool Refreshing
|
||||||
{
|
{
|
||||||
get => _refreshing;
|
get => _refreshing;
|
||||||
@@ -153,30 +151,12 @@ namespace Bit.App.Pages
|
|||||||
get => _websiteIconsEnabled;
|
get => _websiteIconsEnabled;
|
||||||
set => SetProperty(ref _websiteIconsEnabled, value);
|
set => SetProperty(ref _websiteIconsEnabled, value);
|
||||||
}
|
}
|
||||||
public bool ShowVaultFilter
|
|
||||||
{
|
|
||||||
get => _showVaultFilter;
|
|
||||||
set => SetProperty(ref _showVaultFilter, value);
|
|
||||||
}
|
|
||||||
public string VaultFilterDescription
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_vaultFilterSelection == null || _vaultFilterSelection == AppResources.AllVaults)
|
|
||||||
{
|
|
||||||
return string.Format(AppResources.VaultFilterDescription, AppResources.All);
|
|
||||||
}
|
|
||||||
return string.Format(AppResources.VaultFilterDescription, _vaultFilterSelection);
|
|
||||||
}
|
|
||||||
set => SetProperty(ref _vaultFilterSelection, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
|
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
|
||||||
|
|
||||||
public ObservableRangeCollection<IGroupingsPageListItem> GroupedItems { get; set; }
|
public ObservableRangeCollection<IGroupingsPageListItem> GroupedItems { get; set; }
|
||||||
public Command RefreshCommand { get; set; }
|
public Command RefreshCommand { get; set; }
|
||||||
public Command<CipherView> CipherOptionsCommand { get; set; }
|
public Command<CipherView> CipherOptionsCommand { get; set; }
|
||||||
public ICommand VaultFilterCommand { get; }
|
|
||||||
public bool LoadedOnce { get; set; }
|
public bool LoadedOnce { get; set; }
|
||||||
|
|
||||||
public async Task LoadAsync()
|
public async Task LoadAsync()
|
||||||
@@ -201,14 +181,9 @@ namespace Bit.App.Pages
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_organizations = await _organizationService.GetAllAsync();
|
await InitVaultFilterAsync(MainPage);
|
||||||
if (MainPage)
|
if (MainPage)
|
||||||
{
|
{
|
||||||
ShowVaultFilter = await _policyService.ShouldShowVaultFilterAsync();
|
|
||||||
if (ShowVaultFilter && _vaultFilterSelection == null)
|
|
||||||
{
|
|
||||||
_vaultFilterSelection = AppResources.AllVaults;
|
|
||||||
}
|
|
||||||
PageTitle = ShowVaultFilter ? AppResources.Vaults : AppResources.MyVault;
|
PageTitle = ShowVaultFilter ? AppResources.Vaults : AppResources.MyVault;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,30 +369,11 @@ namespace Bit.App.Pages
|
|||||||
SyncRefreshing = false;
|
SyncRefreshing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task VaultFilterOptionsAsync()
|
protected override async Task OnVaultFilterSelectedAsync()
|
||||||
{
|
{
|
||||||
var options = new List<string> { AppResources.AllVaults, AppResources.MyVault };
|
|
||||||
if (_organizations.Any())
|
|
||||||
{
|
|
||||||
options.AddRange(_organizations.OrderBy(o => o.Name).Select(o => o.Name));
|
|
||||||
}
|
|
||||||
var selection = await Page.DisplayActionSheet(AppResources.FilterByVault, AppResources.Cancel, null,
|
|
||||||
options.ToArray());
|
|
||||||
if (selection == null || selection == AppResources.Cancel ||
|
|
||||||
(_vaultFilterSelection == null && selection == AppResources.AllVaults) ||
|
|
||||||
(_vaultFilterSelection != null && _vaultFilterSelection == selection))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
VaultFilterDescription = selection;
|
|
||||||
await LoadAsync();
|
await LoadAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetVaultFilterOrgId()
|
|
||||||
{
|
|
||||||
return _organizations?.FirstOrDefault(o => o.Name == _vaultFilterSelection)?.Id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SelectCipherAsync(CipherView cipher)
|
public async Task SelectCipherAsync(CipherView cipher)
|
||||||
{
|
{
|
||||||
var page = new ViewPage(cipher.Id);
|
var page = new ViewPage(cipher.Id);
|
||||||
@@ -501,8 +457,8 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
private async Task LoadDataAsync()
|
private async Task LoadDataAsync()
|
||||||
{
|
{
|
||||||
var orgId = await FillAllCiphersAndGetOrgIdIfNeededAsync();
|
|
||||||
NoDataText = AppResources.NoItems;
|
NoDataText = AppResources.NoItems;
|
||||||
|
_allCiphers = await GetAllCiphersAsync();
|
||||||
HasCiphers = _allCiphers.Any();
|
HasCiphers = _allCiphers.Any();
|
||||||
FavoriteCiphers?.Clear();
|
FavoriteCiphers?.Clear();
|
||||||
NoFolderCiphers?.Clear();
|
NoFolderCiphers?.Clear();
|
||||||
@@ -516,7 +472,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
if (MainPage)
|
if (MainPage)
|
||||||
{
|
{
|
||||||
await FillFoldersAndCollectionsAsync(orgId);
|
await FillFoldersAndCollectionsAsync();
|
||||||
NestedFolders = await _folderService.GetAllNestedAsync(Folders);
|
NestedFolders = await _folderService.GetAllNestedAsync(Folders);
|
||||||
HasFolders = NestedFolders.Any(f => f.Node?.Id != null);
|
HasFolders = NestedFolders.Any(f => f.Node?.Id != null);
|
||||||
NestedCollections = Collections != null ? await _collectionService.GetAllNestedAsync(Collections) : null;
|
NestedCollections = Collections != null ? await _collectionService.GetAllNestedAsync(Collections) : null;
|
||||||
@@ -640,28 +596,9 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> FillAllCiphersAndGetOrgIdIfNeededAsync()
|
private async Task FillFoldersAndCollectionsAsync()
|
||||||
{
|
|
||||||
string orgId = null;
|
|
||||||
var decCiphers = await _cipherService.GetAllDecryptedAsync();
|
|
||||||
if (IsVaultFilterMyVault)
|
|
||||||
{
|
|
||||||
_allCiphers = decCiphers.Where(c => c.OrganizationId == null).ToList();
|
|
||||||
}
|
|
||||||
else if (IsVaultFilterOrgVault)
|
|
||||||
{
|
|
||||||
orgId = GetVaultFilterOrgId();
|
|
||||||
_allCiphers = decCiphers.Where(c => c.OrganizationId == orgId).ToList();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_allCiphers = decCiphers;
|
|
||||||
}
|
|
||||||
return orgId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task FillFoldersAndCollectionsAsync(string orgId)
|
|
||||||
{
|
{
|
||||||
|
var orgId = GetVaultFilterOrgId();
|
||||||
var decFolders = await _folderService.GetAllDecryptedAsync();
|
var decFolders = await _folderService.GetAllDecryptedAsync();
|
||||||
var decCollections = await _collectionService.GetAllDecryptedAsync();
|
var decCollections = await _collectionService.GetAllDecryptedAsync();
|
||||||
if (IsVaultFilterMyVault)
|
if (IsVaultFilterMyVault)
|
||||||
@@ -681,11 +618,6 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsVaultFilterMyVault => _vaultFilterSelection == AppResources.MyVault;
|
|
||||||
|
|
||||||
private bool IsVaultFilterOrgVault => _vaultFilterSelection != AppResources.AllVaults &&
|
|
||||||
_vaultFilterSelection != AppResources.MyVault;
|
|
||||||
|
|
||||||
private List<FolderView> BuildFolders(List<FolderView> decFolders)
|
private List<FolderView> BuildFolders(List<FolderView> decFolders)
|
||||||
{
|
{
|
||||||
var folders = decFolders.Where(f => _allCiphers.Any(c => c.FolderId == f.Id)).ToList();
|
var folders = decFolders.Where(f => _allCiphers.Any(c => c.FolderId == f.Id)).ToList();
|
||||||
|
|||||||
123
src/App/Pages/VaultFilterViewModel.cs
Normal file
123
src/App/Pages/VaultFilterViewModel.cs
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using Bit.App.Resources;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Models.Domain;
|
||||||
|
using Bit.Core.Models.View;
|
||||||
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
|
|
||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
public abstract class VaultFilterViewModel : BaseViewModel
|
||||||
|
{
|
||||||
|
protected abstract ICipherService cipherService { get; }
|
||||||
|
protected abstract IPolicyService policyService { get; }
|
||||||
|
protected abstract IOrganizationService organizationService { get; }
|
||||||
|
protected abstract ILogger logger { get; }
|
||||||
|
|
||||||
|
protected bool _showVaultFilter;
|
||||||
|
protected bool _personalOwnershipPolicyApplies;
|
||||||
|
protected string _vaultFilterSelection;
|
||||||
|
protected List<Organization> _organizations;
|
||||||
|
|
||||||
|
public VaultFilterViewModel()
|
||||||
|
{
|
||||||
|
VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync,
|
||||||
|
onException: ex => logger.Exception(ex),
|
||||||
|
allowsMultipleExecutions: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommand VaultFilterCommand { get; set; }
|
||||||
|
|
||||||
|
public bool ShowVaultFilter
|
||||||
|
{
|
||||||
|
get => _showVaultFilter;
|
||||||
|
set => SetProperty(ref _showVaultFilter, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string VaultFilterDescription
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_vaultFilterSelection == null || _vaultFilterSelection == AppResources.AllVaults)
|
||||||
|
{
|
||||||
|
return string.Format(AppResources.VaultFilterDescription, AppResources.All);
|
||||||
|
}
|
||||||
|
return string.Format(AppResources.VaultFilterDescription, _vaultFilterSelection);
|
||||||
|
}
|
||||||
|
set => SetProperty(ref _vaultFilterSelection, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetVaultFilterOrgId()
|
||||||
|
{
|
||||||
|
return _organizations?.FirstOrDefault(o => o.Name == _vaultFilterSelection)?.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool IsVaultFilterMyVault => _vaultFilterSelection == AppResources.MyVault;
|
||||||
|
|
||||||
|
protected bool IsVaultFilterOrgVault => _vaultFilterSelection != AppResources.AllVaults &&
|
||||||
|
_vaultFilterSelection != AppResources.MyVault;
|
||||||
|
|
||||||
|
protected async Task InitVaultFilterAsync(bool shouldUpdateShowVaultFilter)
|
||||||
|
{
|
||||||
|
_organizations = await organizationService.GetAllAsync();
|
||||||
|
if (_organizations?.Any() ?? false)
|
||||||
|
{
|
||||||
|
_personalOwnershipPolicyApplies = await policyService.PolicyAppliesToUser(PolicyType.PersonalOwnership);
|
||||||
|
var singleOrgPolicyApplies = await policyService.PolicyAppliesToUser(PolicyType.OnlyOrg);
|
||||||
|
if (_vaultFilterSelection == null || (_personalOwnershipPolicyApplies && singleOrgPolicyApplies))
|
||||||
|
{
|
||||||
|
VaultFilterDescription = AppResources.AllVaults;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shouldUpdateShowVaultFilter)
|
||||||
|
{
|
||||||
|
await Task.Delay(100);
|
||||||
|
ShowVaultFilter = await policyService.ShouldShowVaultFilterAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task<List<CipherView>> GetAllCiphersAsync()
|
||||||
|
{
|
||||||
|
var decCiphers = await cipherService.GetAllDecryptedAsync();
|
||||||
|
if (IsVaultFilterMyVault)
|
||||||
|
{
|
||||||
|
return decCiphers.Where(c => c.OrganizationId == null).ToList();
|
||||||
|
}
|
||||||
|
if (IsVaultFilterOrgVault)
|
||||||
|
{
|
||||||
|
var orgId = GetVaultFilterOrgId();
|
||||||
|
return decCiphers.Where(c => c.OrganizationId == orgId).ToList();
|
||||||
|
}
|
||||||
|
return decCiphers;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task VaultFilterOptionsAsync()
|
||||||
|
{
|
||||||
|
var options = new List<string> { AppResources.AllVaults };
|
||||||
|
if (!_personalOwnershipPolicyApplies)
|
||||||
|
{
|
||||||
|
options.Add(AppResources.MyVault);
|
||||||
|
}
|
||||||
|
if (_organizations.Any())
|
||||||
|
{
|
||||||
|
options.AddRange(_organizations.OrderBy(o => o.Name).Select(o => o.Name));
|
||||||
|
}
|
||||||
|
var selection = await Page.DisplayActionSheet(AppResources.FilterByVault, AppResources.Cancel, null,
|
||||||
|
options.ToArray());
|
||||||
|
if (selection == null || selection == AppResources.Cancel ||
|
||||||
|
(_vaultFilterSelection == null && selection == AppResources.AllVaults) ||
|
||||||
|
(_vaultFilterSelection != null && _vaultFilterSelection == selection))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
VaultFilterDescription = selection;
|
||||||
|
await OnVaultFilterSelectedAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Task OnVaultFilterSelectedAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,7 @@ namespace Bit.App.Services
|
|||||||
Constants.LastBuildKey,
|
Constants.LastBuildKey,
|
||||||
Constants.ClearCiphersCacheKey,
|
Constants.ClearCiphersCacheKey,
|
||||||
Constants.BiometricIntegrityKey,
|
Constants.BiometricIntegrityKey,
|
||||||
|
Constants.iOSExtensionActiveUserIdKey,
|
||||||
Constants.iOSAutoFillClearCiphersCacheKey,
|
Constants.iOSAutoFillClearCiphersCacheKey,
|
||||||
Constants.iOSAutoFillBiometricIntegrityKey,
|
Constants.iOSAutoFillBiometricIntegrityKey,
|
||||||
Constants.iOSExtensionClearCiphersCacheKey,
|
Constants.iOSExtensionClearCiphersCacheKey,
|
||||||
@@ -32,7 +33,7 @@ namespace Bit.App.Services
|
|||||||
Constants.iOSShareExtensionClearCiphersCacheKey,
|
Constants.iOSShareExtensionClearCiphersCacheKey,
|
||||||
Constants.iOSShareExtensionBiometricIntegrityKey,
|
Constants.iOSShareExtensionBiometricIntegrityKey,
|
||||||
Constants.RememberedEmailKey,
|
Constants.RememberedEmailKey,
|
||||||
Constants.RememberedOrgIdentifierKey,
|
Constants.RememberedOrgIdentifierKey
|
||||||
};
|
};
|
||||||
|
|
||||||
public MobileStorageService(
|
public MobileStorageService(
|
||||||
|
|||||||
@@ -6,21 +6,11 @@ using Bit.App.Resources;
|
|||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Utilities.AccountManagement
|
namespace Bit.App.Utilities.AccountManagement
|
||||||
{
|
{
|
||||||
public static class AccountsManagerMessageCommands
|
|
||||||
{
|
|
||||||
public const string LOCKED = "locked";
|
|
||||||
public const string LOCK_VAULT = "lockVault";
|
|
||||||
public const string LOGOUT = "logout";
|
|
||||||
public const string LOGGED_OUT = "loggedOut";
|
|
||||||
public const string ADD_ACCOUNT = "addAccount";
|
|
||||||
public const string ACCOUNT_ADDED = "accountAdded";
|
|
||||||
public const string SWITCHED_ACCOUNT = "switchedAccount";
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AccountsManager : IAccountsManager
|
public class AccountsManager : IAccountsManager
|
||||||
{
|
{
|
||||||
private readonly IBroadcasterService _broadcasterService;
|
private readonly IBroadcasterService _broadcasterService;
|
||||||
@@ -209,7 +199,7 @@ namespace Bit.App.Utilities.AccountManagement
|
|||||||
private async Task SwitchedAccountAsync()
|
private async Task SwitchedAccountAsync()
|
||||||
{
|
{
|
||||||
await AppHelpers.OnAccountSwitchAsync();
|
await AppHelpers.OnAccountSwitchAsync();
|
||||||
Device.BeginInvokeOnMainThread(async () =>
|
await Device.InvokeOnMainThreadAsync(async () =>
|
||||||
{
|
{
|
||||||
if (await _vaultTimeoutService.ShouldTimeoutAsync())
|
if (await _vaultTimeoutService.ShouldTimeoutAsync())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ namespace Bit.Core.Abstractions
|
|||||||
Task PutDeleteCipherAsync(string id);
|
Task PutDeleteCipherAsync(string id);
|
||||||
Task<CipherResponse> PutRestoreCipherAsync(string id);
|
Task<CipherResponse> PutRestoreCipherAsync(string id);
|
||||||
Task RefreshIdentityTokenAsync();
|
Task RefreshIdentityTokenAsync();
|
||||||
Task<object> PreValidateSso(string identifier);
|
Task<SsoPrevalidateResponse> PreValidateSso(string identifier);
|
||||||
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,
|
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,
|
||||||
TRequest body, bool authed, bool hasResponse, bool logoutOnUnauthorized = true);
|
TRequest body, bool authed, bool hasResponse, bool logoutOnUnauthorized = true);
|
||||||
void SetUrls(EnvironmentUrls urls);
|
void SetUrls(EnvironmentUrls urls);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ namespace Bit.Core.Abstractions
|
|||||||
Task<string> GetActiveUserIdAsync();
|
Task<string> GetActiveUserIdAsync();
|
||||||
Task<bool> IsActiveAccountAsync(string userId = null);
|
Task<bool> IsActiveAccountAsync(string userId = null);
|
||||||
Task SetActiveUserAsync(string userId);
|
Task SetActiveUserAsync(string userId);
|
||||||
|
Task CheckExtensionActiveUserAndSwitchIfNeededAsync();
|
||||||
Task<bool> IsAuthenticatedAsync(string userId = null);
|
Task<bool> IsAuthenticatedAsync(string userId = null);
|
||||||
Task<string> GetUserIdAsync(string email);
|
Task<string> GetUserIdAsync(string email);
|
||||||
Task RefreshAccountViewsAsync(bool allowAddAccountRow);
|
Task RefreshAccountViewsAsync(bool allowAddAccountRow);
|
||||||
@@ -145,5 +146,6 @@ namespace Bit.Core.Abstractions
|
|||||||
Task SetRefreshTokenAsync(string value, bool skipTokenStorage, string userId = null);
|
Task SetRefreshTokenAsync(string value, bool skipTokenStorage, string userId = null);
|
||||||
Task<string> GetTwoFactorTokenAsync(string email = null);
|
Task<string> GetTwoFactorTokenAsync(string email = null);
|
||||||
Task SetTwoFactorTokenAsync(string value, string email = null);
|
Task SetTwoFactorTokenAsync(string value, string email = null);
|
||||||
|
Task SaveExtensionActiveUserIdToStorageAsync(string userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
public static string iOSExtensionBiometricIntegrityKey = "iOSExtensionBiometricIntegrityState";
|
public static string iOSExtensionBiometricIntegrityKey = "iOSExtensionBiometricIntegrityState";
|
||||||
public static string iOSShareExtensionClearCiphersCacheKey = "iOSShareExtensionClearCiphersCache";
|
public static string iOSShareExtensionClearCiphersCacheKey = "iOSShareExtensionClearCiphersCache";
|
||||||
public static string iOSShareExtensionBiometricIntegrityKey = "iOSShareExtensionBiometricIntegrityState";
|
public static string iOSShareExtensionBiometricIntegrityKey = "iOSShareExtensionBiometricIntegrityState";
|
||||||
|
public static string iOSExtensionActiveUserIdKey = "iOSExtensionActiveUserId";
|
||||||
public static string EventCollectionKey = "eventCollection";
|
public static string EventCollectionKey = "eventCollection";
|
||||||
public static string RememberedEmailKey = "rememberedEmail";
|
public static string RememberedEmailKey = "rememberedEmail";
|
||||||
public static string RememberedOrgIdentifierKey = "rememberedOrgIdentifier";
|
public static string RememberedOrgIdentifierKey = "rememberedOrgIdentifier";
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
{
|
{
|
||||||
public class OrganizationUserResetPasswordEnrollmentRequest
|
public class OrganizationUserResetPasswordEnrollmentRequest
|
||||||
{
|
{
|
||||||
|
public string MasterPasswordHash { get; set; }
|
||||||
public string ResetPasswordKey { get; set; }
|
public string ResetPasswordKey { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
src/Core/Models/Response/SsoPrevalidateResponse.cs
Normal file
7
src/Core/Models/Response/SsoPrevalidateResponse.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Bit.Core.Models.Response
|
||||||
|
{
|
||||||
|
public class SsoPrevalidateResponse
|
||||||
|
{
|
||||||
|
public string Token { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -547,7 +547,7 @@ namespace Bit.Core.Services
|
|||||||
return accessToken;
|
return accessToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<object> PreValidateSso(string identifier)
|
public async Task<SsoPrevalidateResponse> PreValidateSso(string identifier)
|
||||||
{
|
{
|
||||||
var path = "/account/prevalidate?domainHint=" + WebUtility.UrlEncode(identifier);
|
var path = "/account/prevalidate?domainHint=" + WebUtility.UrlEncode(identifier);
|
||||||
using (var requestMessage = new HttpRequestMessage())
|
using (var requestMessage = new HttpRequestMessage())
|
||||||
@@ -571,7 +571,8 @@ namespace Bit.Core.Services
|
|||||||
var error = await HandleErrorAsync(response, false, true);
|
var error = await HandleErrorAsync(response, false, true);
|
||||||
throw new ApiException(error);
|
throw new ApiException(error);
|
||||||
}
|
}
|
||||||
return null;
|
var responseJsonString = await response.Content.ReadAsStringAsync();
|
||||||
|
return JsonConvert.DeserializeObject<SsoPrevalidateResponse>(responseJsonString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -249,6 +249,12 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
public async Task<bool> ShouldShowVaultFilterAsync()
|
public async Task<bool> ShouldShowVaultFilterAsync()
|
||||||
{
|
{
|
||||||
|
var personalOwnershipPolicyApplies = await PolicyAppliesToUser(PolicyType.PersonalOwnership);
|
||||||
|
var singleOrgPolicyApplies = await PolicyAppliesToUser(PolicyType.OnlyOrg);
|
||||||
|
if (personalOwnershipPolicyApplies && singleOrgPolicyApplies)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
var organizations = await _organizationService.GetAllAsync();
|
var organizations = await _organizationService.GetAllAsync();
|
||||||
return organizations?.Any() ?? false;
|
return organizations?.Any() ?? false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,16 +15,20 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
private readonly IStorageService _storageService;
|
private readonly IStorageService _storageService;
|
||||||
private readonly IStorageService _secureStorageService;
|
private readonly IStorageService _secureStorageService;
|
||||||
|
private readonly IMessagingService _messagingService;
|
||||||
|
|
||||||
private State _state;
|
private State _state;
|
||||||
private bool _migrationChecked;
|
private bool _migrationChecked;
|
||||||
|
|
||||||
public List<AccountView> AccountViews { get; set; }
|
public List<AccountView> AccountViews { get; set; }
|
||||||
|
|
||||||
public StateService(IStorageService storageService, IStorageService secureStorageService)
|
public StateService(IStorageService storageService,
|
||||||
|
IStorageService secureStorageService,
|
||||||
|
IMessagingService messagingService)
|
||||||
{
|
{
|
||||||
_storageService = storageService;
|
_storageService = storageService;
|
||||||
_secureStorageService = secureStorageService;
|
_secureStorageService = secureStorageService;
|
||||||
|
_messagingService = messagingService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GetActiveUserIdAsync()
|
public async Task<string> GetActiveUserIdAsync()
|
||||||
@@ -67,6 +71,28 @@ namespace Bit.Core.Services
|
|||||||
await SetPreAuthEnvironmentUrlsAsync(await GetEnvironmentUrlsAsync());
|
await SetPreAuthEnvironmentUrlsAsync(await GetEnvironmentUrlsAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task CheckExtensionActiveUserAndSwitchIfNeededAsync()
|
||||||
|
{
|
||||||
|
var extensionUserId = await GetExtensionActiveUserIdFromStorageAsync();
|
||||||
|
if (string.IsNullOrEmpty(extensionUserId))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_state?.ActiveUserId == extensionUserId)
|
||||||
|
{
|
||||||
|
// Clear the value in case the user changes the active user from the app
|
||||||
|
// so if that happens and the user sends the app to background and comes back
|
||||||
|
// the user is not changed again.
|
||||||
|
await SaveExtensionActiveUserIdToStorageAsync(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await SetActiveUserAsync(extensionUserId);
|
||||||
|
await SaveExtensionActiveUserIdToStorageAsync(null);
|
||||||
|
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<bool> IsAuthenticatedAsync(string userId = null)
|
public async Task<bool> IsAuthenticatedAsync(string userId = null)
|
||||||
{
|
{
|
||||||
return await GetAccessTokenAsync(userId) != null;
|
return await GetAccessTokenAsync(userId) != null;
|
||||||
@@ -1510,6 +1536,16 @@ namespace Bit.Core.Services
|
|||||||
await _storageService.SaveAsync(Constants.StateKey, state);
|
await _storageService.SaveAsync(Constants.StateKey, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<string> GetExtensionActiveUserIdFromStorageAsync()
|
||||||
|
{
|
||||||
|
return await _storageService.GetAsync<string>(Constants.iOSExtensionActiveUserIdKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SaveExtensionActiveUserIdToStorageAsync(string userId)
|
||||||
|
{
|
||||||
|
await _storageService.SaveAsync(Constants.iOSExtensionActiveUserIdKey, userId);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task CheckStateAsync()
|
private async Task CheckStateAsync()
|
||||||
{
|
{
|
||||||
if (!_migrationChecked)
|
if (!_migrationChecked)
|
||||||
|
|||||||
13
src/Core/Utilities/AccountsManagerMessageCommands.cs
Normal file
13
src/Core/Utilities/AccountsManagerMessageCommands.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
namespace Bit.Core.Utilities
|
||||||
|
{
|
||||||
|
public static class AccountsManagerMessageCommands
|
||||||
|
{
|
||||||
|
public const string LOCKED = "locked";
|
||||||
|
public const string LOCK_VAULT = "lockVault";
|
||||||
|
public const string LOGOUT = "logout";
|
||||||
|
public const string LOGGED_OUT = "loggedOut";
|
||||||
|
public const string ADD_ACCOUNT = "addAccount";
|
||||||
|
public const string ACCOUNT_ADDED = "accountAdded";
|
||||||
|
public const string SWITCHED_ACCOUNT = "switchedAccount";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.8bit.bitwarden.autofill</string>
|
<string>com.8bit.bitwarden.autofill</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2022.05.1</string>
|
<string>2022.6.2</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>CFBundleLocalizations</key>
|
<key>CFBundleLocalizations</key>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Threading.Tasks;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Controls;
|
using Bit.App.Controls;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
@@ -10,6 +11,8 @@ namespace Bit.iOS.Core.Utilities
|
|||||||
{
|
{
|
||||||
public class AccountSwitchingOverlayHelper
|
public class AccountSwitchingOverlayHelper
|
||||||
{
|
{
|
||||||
|
const string DEFAULT_SYSTEM_AVATAR_IMAGE = "person.2";
|
||||||
|
|
||||||
IStateService _stateService;
|
IStateService _stateService;
|
||||||
IMessagingService _messagingService;
|
IMessagingService _messagingService;
|
||||||
ILogger _logger;
|
ILogger _logger;
|
||||||
@@ -23,20 +26,44 @@ namespace Bit.iOS.Core.Utilities
|
|||||||
|
|
||||||
public async Task<UIImage> CreateAvatarImageAsync()
|
public async Task<UIImage> CreateAvatarImageAsync()
|
||||||
{
|
{
|
||||||
var avatarImageSource = new AvatarImageSource(await _stateService.GetNameAsync(), await _stateService.GetEmailAsync());
|
try
|
||||||
var avatarUIImage = await avatarImageSource.GetNativeImageAsync();
|
{
|
||||||
return avatarUIImage.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal);
|
if (_stateService is null)
|
||||||
|
{
|
||||||
|
throw new NullReferenceException(nameof(_stateService));
|
||||||
|
}
|
||||||
|
|
||||||
|
var avatarImageSource = new AvatarImageSource(await _stateService.GetNameAsync(), await _stateService.GetEmailAsync());
|
||||||
|
var avatarUIImage = await avatarImageSource.GetNativeImageAsync();
|
||||||
|
return avatarUIImage?.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal) ?? UIImage.GetSystemImage(DEFAULT_SYSTEM_AVATAR_IMAGE);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Exception(ex);
|
||||||
|
return UIImage.GetSystemImage(DEFAULT_SYSTEM_AVATAR_IMAGE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccountSwitchingOverlayView CreateAccountSwitchingOverlayView(UIView containerView)
|
public AccountSwitchingOverlayView CreateAccountSwitchingOverlayView(UIView containerView)
|
||||||
{
|
{
|
||||||
var overlay = new AccountSwitchingOverlayView()
|
var overlay = new AccountSwitchingOverlayView()
|
||||||
{
|
{
|
||||||
LongPressAccountEnabled = false
|
LongPressAccountEnabled = false,
|
||||||
|
AfterHide = () =>
|
||||||
|
{
|
||||||
|
if (containerView != null)
|
||||||
|
{
|
||||||
|
containerView.Hidden = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var vm = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger);
|
var vm = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
|
||||||
|
{
|
||||||
|
FromIOSExtension = true
|
||||||
|
};
|
||||||
overlay.BindingContext = vm;
|
overlay.BindingContext = vm;
|
||||||
|
overlay.IsVisible = false;
|
||||||
|
|
||||||
var renderer = Platform.CreateRenderer(overlay.Content);
|
var renderer = Platform.CreateRenderer(overlay.Content);
|
||||||
renderer.SetElementSize(new Size(containerView.Frame.Size.Width, containerView.Frame.Size.Height));
|
renderer.SetElementSize(new Size(containerView.Frame.Size.Width, containerView.Frame.Size.Height));
|
||||||
@@ -60,8 +87,13 @@ namespace Bit.iOS.Core.Utilities
|
|||||||
public void OnToolbarItemActivated(AccountSwitchingOverlayView accountSwitchingOverlayView, UIView containerView)
|
public void OnToolbarItemActivated(AccountSwitchingOverlayView accountSwitchingOverlayView, UIView containerView)
|
||||||
{
|
{
|
||||||
var overlayVisible = accountSwitchingOverlayView.IsVisible;
|
var overlayVisible = accountSwitchingOverlayView.IsVisible;
|
||||||
|
if (!overlayVisible)
|
||||||
|
{
|
||||||
|
// So that the animation doesn't break we only care about showing it
|
||||||
|
// and the hiding if done through AccountSwitchingOverlayView -> AfterHide
|
||||||
|
containerView.Hidden = false;
|
||||||
|
}
|
||||||
accountSwitchingOverlayView.ToggleVisibililtyCommand.Execute(null);
|
accountSwitchingOverlayView.ToggleVisibililtyCommand.Execute(null);
|
||||||
containerView.Hidden = false;
|
|
||||||
containerView.UserInteractionEnabled = !overlayVisible;
|
containerView.UserInteractionEnabled = !overlayVisible;
|
||||||
containerView.Subviews[0].UserInteractionEnabled = !overlayVisible;
|
containerView.Subviews[0].UserInteractionEnabled = !overlayVisible;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Bit.Core.Services;
|
||||||
using UIKit;
|
using UIKit;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
using Xamarin.Forms.Internals;
|
|
||||||
using Xamarin.Forms.Platform.iOS;
|
using Xamarin.Forms.Platform.iOS;
|
||||||
|
|
||||||
namespace Bit.iOS.Core.Utilities
|
namespace Bit.iOS.Core.Utilities
|
||||||
@@ -17,25 +17,29 @@ namespace Bit.iOS.Core.Utilities
|
|||||||
public static async Task<UIImage> GetNativeImageAsync(this ImageSource source, CancellationToken cancellationToken = default(CancellationToken))
|
public static async Task<UIImage> GetNativeImageAsync(this ImageSource source, CancellationToken cancellationToken = default(CancellationToken))
|
||||||
{
|
{
|
||||||
if (source == null || source.IsEmpty)
|
if (source == null || source.IsEmpty)
|
||||||
|
{
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var handler = Xamarin.Forms.Internals.Registrar.Registered.GetHandlerForObject<IImageSourceHandler>(source);
|
var handler = Xamarin.Forms.Internals.Registrar.Registered.GetHandlerForObject<IImageSourceHandler>(source);
|
||||||
if (handler == null)
|
if (handler == null)
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(new InvalidOperationException("GetNativeImageAsync failed cause IImageSourceHandler couldn't be found"));
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
float scale = (float)UIScreen.MainScreen.Scale;
|
float scale = (float)UIScreen.MainScreen.Scale;
|
||||||
|
|
||||||
return await handler.LoadImageAsync(source, scale: scale, cancelationToken: cancellationToken);
|
return await handler.LoadImageAsync(source, scale: scale, cancelationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
Log.Warning("Image loading", "Image load cancelled");
|
LoggerHelper.LogEvenIfCantBeResolved(new OperationCanceledException("GetNativeImageAsync was cancelled"));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Warning("Image loading", $"Image load failed: {ex}");
|
LoggerHelper.LogEvenIfCantBeResolved(new InvalidOperationException("GetNativeImageAsync failed", ex));
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ namespace Bit.iOS.Core.Utilities
|
|||||||
() => ServiceContainer.Resolve<IAppIdService>("appIdService").GetAppIdAsync());
|
() => ServiceContainer.Resolve<IAppIdService>("appIdService").GetAppIdAsync());
|
||||||
var cryptoPrimitiveService = new CryptoPrimitiveService();
|
var cryptoPrimitiveService = new CryptoPrimitiveService();
|
||||||
var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage);
|
var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage);
|
||||||
var stateService = new StateService(mobileStorageService, secureStorageService);
|
var stateService = new StateService(mobileStorageService, secureStorageService, messagingService);
|
||||||
var stateMigrationService =
|
var stateMigrationService =
|
||||||
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
|
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
|
||||||
var deviceActionService = new DeviceActionService(stateService, messagingService);
|
var deviceActionService = new DeviceActionService(stateService, messagingService);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.8bit.bitwarden.find-login-action-extension</string>
|
<string>com.8bit.bitwarden.find-login-action-extension</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2022.05.1</string>
|
<string>2022.6.2</string>
|
||||||
<key>CFBundleLocalizations</key>
|
<key>CFBundleLocalizations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>en</string>
|
<string>en</string>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>XPC!</string>
|
<string>XPC!</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2022.05.1</string>
|
<string>2022.6.2</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>MinimumOSVersion</key>
|
<key>MinimumOSVersion</key>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.8bit.bitwarden</string>
|
<string>com.8bit.bitwarden</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2022.05.1</string>
|
<string>2022.6.2</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>CFBundleIconName</key>
|
<key>CFBundleIconName</key>
|
||||||
|
|||||||
Reference in New Issue
Block a user