1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-05 23:53:33 +00:00

Compare commits

...

14 Commits

Author SHA1 Message Date
github-actions[bot]
efe128b932 Bumped version to 2022.6.2 (#1981)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
(cherry picked from commit 7802da2b9c)
2022-07-21 11:16:57 -04:00
Federico Maccaroni
df2e52f82c EC-308 Fix crash produced by creating avatar image on AccountSwitchingOverlayHelper and also added more logging to see when it happens. (#1983)
(cherry picked from commit 846d3a85a2)
2022-07-20 17:51:00 -04:00
github-actions[bot]
2d224f5e22 Bumped version to 2022.6.1 (#1969)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
(cherry picked from commit a6ddc2496f)
2022-07-06 18:56:39 -04:00
Matt Gibson
4831097c0b Add user verification to reset password request (#1980)
We only need master password hash because this is currently
only used for sso password setting after auto-provisioning. Key
Connector is not involved in these accounts

(cherry picked from commit 58a3662d0f)
2022-07-06 18:24:33 -04:00
github-actions[bot]
72f2983885 Bumped version to 2022.6.0 (#1968)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
(cherry picked from commit d9a818279f)
2022-06-29 09:12:27 -07:00
Matt Gibson
ee68dcf3b0 Add ssoToken to limit lifetime of SSO redirect (#1965)
(cherry picked from commit 6e2e613fee)
2022-06-27 16:05:11 -04:00
Federico Andrés Maccaroni
daab473cbb SG-396 Fix tappable area after hiding account switching 2022-06-21 09:33:59 -03:00
Joseph Flinn
e6b99270cf Pin NuGet version (#1957)
* Pinning the version of NuGet to 5.x

* pinning NuGet verison to 5.9.x

* pinning NuGet to 5.9.0.7134

* pinning NuGet to 5.9.0

(cherry picked from commit c892e9fa57)
2022-06-16 14:48:47 -07:00
Federico Andrés Maccaroni
a8d9aaa7fe [SG-386] iOS Update user state when coming from background 2022-06-16 14:01:28 -03:00
mp-bw
542ef5f31a fix for missing personal items added prior to joining org with personal ownership policy (#1955) 2022-06-16 09:56:03 -04:00
mp-bw
0b2fc2a647 separate init and showVaultFilter property set (#1954) 2022-06-15 15:19:34 -04:00
mp-bw
a95fdf67a1 [SG-390] Fix for missing org items with single org & personal ownership enabled (#1953)
* fix for missing org items with single org & personal ownership enabled

* fix for ui issue with vault filter state change on pull to refresh
2022-06-15 10:45:01 -04:00
mp-bw
73890162bf Additional logic around filter display (#1951) 2022-06-14 13:05:28 -04:00
Joseph Flinn
ef14a8f850 Updating the release version check to use the new action (#1934)
* Updating the release version check to use the new action

* Update .github/workflows/release.yml

Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com>

Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com>
(cherry picked from commit 5579817f9f)
2022-06-13 12:59:24 -07:00
30 changed files with 323 additions and 227 deletions

View File

@@ -60,6 +60,11 @@ jobs:
runs-on: windows-2019
needs: setup
steps:
- name: Setup NuGet
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
with:
nuget-version: 5.9.0
- name: Set up MSBuild
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
@@ -209,6 +214,11 @@ jobs:
name: F-Droid Build
runs-on: windows-2019
steps:
- name: Setup NuGet
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
with:
nuget-version: 5.9.0
- name: Set up MSBuild
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
@@ -368,6 +378,11 @@ jobs:
runs-on: macos-11
needs: setup
steps:
- name: Setup NuGet
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
with:
nuget-version: 5.9.0
- name: Print environment
run: |
nuget help | grep Version

View File

@@ -34,29 +34,13 @@ jobs:
- name: Checkout repo
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
- name: Retrieve Mobile release version
id: retrieve-mobile-version
run: |
ver=$(sed -E -n '/^<manifest/s/^.*[ ]android:versionName="([^"]+)".*$/\1/p' \
./src/Android/Properties/AndroidManifest.xml | tr -d '"')
echo "::set-output name=mobile_version::${ver}"
shell: bash
- 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: Check Release Version
id: version
uses: bitwarden/gh-actions/release-version-check@8f055ef543c7433c967a1b9b04a0f230923233bb
with:
release-type: ${{ github.event.inputs.release_type }}
project-type: xamarin
file: src/Android/Properties/AndroidManifest.xml
- name: Get branch name
id: branch
@@ -83,8 +67,8 @@ jobs:
./com.x8bit.bitwarden-fdroid.apk/com.x8bit.bitwarden-fdroid.apk,
./Bitwarden iOS.zip"
commit: ${{ github.sha }}
tag: v${{ steps.retrieve-mobile-version.outputs.mobile_version }}
name: Version ${{ steps.retrieve-mobile-version.outputs.mobile_version }}
tag: v${{ steps.version.outputs.version }}
name: Version ${{ steps.version.outputs.version }}
body: "<insert release notes here>"
token: ${{ secrets.GITHUB_TOKEN }}
draft: true

View File

@@ -130,7 +130,7 @@ namespace Bit.Droid
var secureStorageService = new SecureStorageService();
var cryptoPrimitiveService = new CryptoPrimitiveService();
var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage);
var stateService = new StateService(mobileStorageService, secureStorageService);
var stateService = new StateService(mobileStorageService, secureStorageService, messagingService);
var stateMigrationService =
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
var clipboardService = new ClipboardService(stateService);

View File

@@ -1,5 +1,5 @@
<?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"/>

View File

@@ -202,6 +202,7 @@ namespace Bit.App
private async Task ResumedAsync()
{
await _stateService.CheckExtensionActiveUserAndSwitchIfNeededAsync();
await _vaultTimeoutService.CheckVaultTimeoutAsync();
_messagingService.Send("startEventTimer");
await UpdateThemeAsync();

View File

@@ -65,6 +65,8 @@ namespace Bit.App.Controls
public bool LongPressAccountEnabled { get; set; } = true;
public Action AfterHide { get; set; }
public async Task ToggleVisibilityAsync()
{
if (IsVisible)
@@ -137,6 +139,8 @@ namespace Bit.App.Controls
// remove overlay
IsVisible = false;
AfterHide?.Invoke();
});
}

View File

@@ -45,23 +45,28 @@ namespace Bit.App.Controls
public ICommand LongPressAccountCommand { get; }
public bool FromIOSExtension { get; set; }
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);
_messagingService.Send("switchedAccount");
}
else if (AllowActiveAccountSelection)
{
_messagingService.Send("switchedAccount");
await _stateService.SaveExtensionActiveUserIdToStorageAsync(item.AccountView.UserId);
}
}
else
else if (AllowActiveAccountSelection)
{
_messagingService.Send("addAccount");
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
}
}

View File

@@ -81,10 +81,12 @@ namespace Bit.App.Pages
}
await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn);
string ssoToken;
try
{
await _apiService.PreValidateSso(OrgIdentifier);
var response = await _apiService.PreValidateSso(OrgIdentifier);
ssoToken = response.Token;
}
catch (ApiException e)
{
@@ -112,7 +114,8 @@ namespace Bit.App.Pages
"response_type=code&scope=api%20offline_access&" +
"state=" + state + "&code_challenge=" + codeChallenge + "&" +
"code_challenge_method=S256&response_mode=query&" +
"domain_hint=" + Uri.EscapeDataString(OrgIdentifier);
"domain_hint=" + Uri.EscapeDataString(OrgIdentifier) + "&" +
"ssoToken=" + Uri.EscapeDataString(ssoToken);
WebAuthenticatorResult authResult = null;
try

View File

@@ -219,7 +219,8 @@ namespace Bit.App.Pages
// Request
var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest
{
ResetPasswordKey = encryptedKey.EncryptedString
ResetPasswordKey = encryptedKey.EncryptedString,
MasterPasswordHash = masterPasswordHash,
};
var userId = await _stateService.GetActiveUserIdAsync();
// Enroll user

View File

@@ -3,14 +3,11 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Domain;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
@@ -18,7 +15,7 @@ using Xamarin.Forms;
namespace Bit.App.Pages
{
public class CiphersPageViewModel : BaseViewModel
public class CiphersPageViewModel : VaultFilterViewModel
{
private readonly IPlatformUtilsService _platformUtilsService;
private readonly ICipherService _cipherService;
@@ -31,12 +28,9 @@ namespace Bit.App.Pages
private CancellationTokenSource _searchCancellationTokenSource;
private readonly ILogger _logger;
private bool _showVaultFilter;
private string _vaultFilterSelection;
private bool _showNoData;
private bool _showList;
private bool _websiteIconsEnabled;
private List<Organization> _organizations;
public CiphersPageViewModel()
{
@@ -52,18 +46,19 @@ namespace Bit.App.Pages
Ciphers = new ExtendedObservableCollection<CipherView>();
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync,
onException: ex => _logger.Exception(ex),
allowsMultipleExecutions: false);
}
public Command CipherOptionsCommand { get; set; }
public ICommand VaultFilterCommand { get; }
public ExtendedObservableCollection<CipherView> Ciphers { get; set; }
public Func<CipherView, bool> Filter { get; set; }
public string AutofillUrl { 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
{
get => _showNoData;
@@ -81,23 +76,6 @@ namespace Bit.App.Pages
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;
@@ -109,12 +87,7 @@ namespace Bit.App.Pages
public async Task InitAsync()
{
_organizations = await _organizationService.GetAllAsync();
ShowVaultFilter = await _policyService.ShouldShowVaultFilterAsync();
if (ShowVaultFilter && _vaultFilterSelection == null)
{
_vaultFilterSelection = AppResources.AllVaults;
}
await InitVaultFilterAsync(true);
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
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();
}
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)
{
if ((Page as BaseContentPage).DoOnce())

View File

@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Resources;
@@ -17,7 +16,7 @@ using Xamarin.Forms;
namespace Bit.App.Pages
{
public class GroupingsPageViewModel : BaseViewModel
public class GroupingsPageViewModel : VaultFilterViewModel
{
private const int NoFolderListSize = 100;
@@ -30,10 +29,7 @@ namespace Bit.App.Pages
private bool _showList;
private bool _websiteIconsEnabled;
private bool _syncRefreshing;
private bool _showVaultFilter;
private string _vaultFilterSelection;
private string _noDataText;
private List<Organization> _organizations;
private List<CipherView> _allCiphers;
private Dictionary<string, int> _folderCounts = new Dictionary<string, int>();
private Dictionary<string, int> _collectionCounts = new Dictionary<string, int>();
@@ -78,9 +74,6 @@ namespace Bit.App.Pages
await LoadAsync();
});
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync,
onException: ex => _logger.Exception(ex),
allowsMultipleExecutions: false);
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<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
{
get => _refreshing;
@@ -153,30 +151,12 @@ namespace Bit.App.Pages
get => _websiteIconsEnabled;
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 ObservableRangeCollection<IGroupingsPageListItem> GroupedItems { get; set; }
public Command RefreshCommand { get; set; }
public Command<CipherView> CipherOptionsCommand { get; set; }
public ICommand VaultFilterCommand { get; }
public bool LoadedOnce { get; set; }
public async Task LoadAsync()
@@ -201,14 +181,9 @@ namespace Bit.App.Pages
return;
}
_organizations = await _organizationService.GetAllAsync();
await InitVaultFilterAsync(MainPage);
if (MainPage)
{
ShowVaultFilter = await _policyService.ShouldShowVaultFilterAsync();
if (ShowVaultFilter && _vaultFilterSelection == null)
{
_vaultFilterSelection = AppResources.AllVaults;
}
PageTitle = ShowVaultFilter ? AppResources.Vaults : AppResources.MyVault;
}
@@ -394,30 +369,11 @@ namespace Bit.App.Pages
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();
}
public string GetVaultFilterOrgId()
{
return _organizations?.FirstOrDefault(o => o.Name == _vaultFilterSelection)?.Id;
}
public async Task SelectCipherAsync(CipherView cipher)
{
var page = new ViewPage(cipher.Id);
@@ -501,8 +457,8 @@ namespace Bit.App.Pages
private async Task LoadDataAsync()
{
var orgId = await FillAllCiphersAndGetOrgIdIfNeededAsync();
NoDataText = AppResources.NoItems;
_allCiphers = await GetAllCiphersAsync();
HasCiphers = _allCiphers.Any();
FavoriteCiphers?.Clear();
NoFolderCiphers?.Clear();
@@ -516,7 +472,7 @@ namespace Bit.App.Pages
if (MainPage)
{
await FillFoldersAndCollectionsAsync(orgId);
await FillFoldersAndCollectionsAsync();
NestedFolders = await _folderService.GetAllNestedAsync(Folders);
HasFolders = NestedFolders.Any(f => f.Node?.Id != null);
NestedCollections = Collections != null ? await _collectionService.GetAllNestedAsync(Collections) : null;
@@ -640,28 +596,9 @@ namespace Bit.App.Pages
}
}
private async Task<string> FillAllCiphersAndGetOrgIdIfNeededAsync()
{
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)
private async Task FillFoldersAndCollectionsAsync()
{
var orgId = GetVaultFilterOrgId();
var decFolders = await _folderService.GetAllDecryptedAsync();
var decCollections = await _collectionService.GetAllDecryptedAsync();
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)
{
var folders = decFolders.Where(f => _allCiphers.Any(c => c.FolderId == f.Id)).ToList();

View 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();
}
}

View File

@@ -25,6 +25,7 @@ namespace Bit.App.Services
Constants.LastBuildKey,
Constants.ClearCiphersCacheKey,
Constants.BiometricIntegrityKey,
Constants.iOSExtensionActiveUserIdKey,
Constants.iOSAutoFillClearCiphersCacheKey,
Constants.iOSAutoFillBiometricIntegrityKey,
Constants.iOSExtensionClearCiphersCacheKey,
@@ -32,7 +33,7 @@ namespace Bit.App.Services
Constants.iOSShareExtensionClearCiphersCacheKey,
Constants.iOSShareExtensionBiometricIntegrityKey,
Constants.RememberedEmailKey,
Constants.RememberedOrgIdentifierKey,
Constants.RememberedOrgIdentifierKey
};
public MobileStorageService(

View File

@@ -6,21 +6,11 @@ using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Bit.Core.Utilities;
using Xamarin.Forms;
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
{
private readonly IBroadcasterService _broadcasterService;
@@ -209,7 +199,7 @@ namespace Bit.App.Utilities.AccountManagement
private async Task SwitchedAccountAsync()
{
await AppHelpers.OnAccountSwitchAsync();
Device.BeginInvokeOnMainThread(async () =>
await Device.InvokeOnMainThreadAsync(async () =>
{
if (await _vaultTimeoutService.ShouldTimeoutAsync())
{

View File

@@ -44,7 +44,7 @@ namespace Bit.Core.Abstractions
Task PutDeleteCipherAsync(string id);
Task<CipherResponse> PutRestoreCipherAsync(string id);
Task RefreshIdentityTokenAsync();
Task<object> PreValidateSso(string identifier);
Task<SsoPrevalidateResponse> PreValidateSso(string identifier);
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,
TRequest body, bool authed, bool hasResponse, bool logoutOnUnauthorized = true);
void SetUrls(EnvironmentUrls urls);

View File

@@ -14,6 +14,7 @@ namespace Bit.Core.Abstractions
Task<string> GetActiveUserIdAsync();
Task<bool> IsActiveAccountAsync(string userId = null);
Task SetActiveUserAsync(string userId);
Task CheckExtensionActiveUserAndSwitchIfNeededAsync();
Task<bool> IsAuthenticatedAsync(string userId = null);
Task<string> GetUserIdAsync(string email);
Task RefreshAccountViewsAsync(bool allowAddAccountRow);
@@ -145,5 +146,6 @@ namespace Bit.Core.Abstractions
Task SetRefreshTokenAsync(string value, bool skipTokenStorage, string userId = null);
Task<string> GetTwoFactorTokenAsync(string email = null);
Task SetTwoFactorTokenAsync(string value, string email = null);
Task SaveExtensionActiveUserIdToStorageAsync(string userId);
}
}

View File

@@ -25,6 +25,7 @@
public static string iOSExtensionBiometricIntegrityKey = "iOSExtensionBiometricIntegrityState";
public static string iOSShareExtensionClearCiphersCacheKey = "iOSShareExtensionClearCiphersCache";
public static string iOSShareExtensionBiometricIntegrityKey = "iOSShareExtensionBiometricIntegrityState";
public static string iOSExtensionActiveUserIdKey = "iOSExtensionActiveUserId";
public static string EventCollectionKey = "eventCollection";
public static string RememberedEmailKey = "rememberedEmail";
public static string RememberedOrgIdentifierKey = "rememberedOrgIdentifier";

View File

@@ -2,6 +2,7 @@
{
public class OrganizationUserResetPasswordEnrollmentRequest
{
public string MasterPasswordHash { get; set; }
public string ResetPasswordKey { get; set; }
}
}

View File

@@ -0,0 +1,7 @@
namespace Bit.Core.Models.Response
{
public class SsoPrevalidateResponse
{
public string Token { get; set; }
}
}

View File

@@ -547,7 +547,7 @@ namespace Bit.Core.Services
return accessToken;
}
public async Task<object> PreValidateSso(string identifier)
public async Task<SsoPrevalidateResponse> PreValidateSso(string identifier)
{
var path = "/account/prevalidate?domainHint=" + WebUtility.UrlEncode(identifier);
using (var requestMessage = new HttpRequestMessage())
@@ -571,7 +571,8 @@ namespace Bit.Core.Services
var error = await HandleErrorAsync(response, false, true);
throw new ApiException(error);
}
return null;
var responseJsonString = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<SsoPrevalidateResponse>(responseJsonString);
}
}

View File

@@ -249,6 +249,12 @@ namespace Bit.Core.Services
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();
return organizations?.Any() ?? false;
}

View File

@@ -15,16 +15,20 @@ namespace Bit.Core.Services
{
private readonly IStorageService _storageService;
private readonly IStorageService _secureStorageService;
private readonly IMessagingService _messagingService;
private State _state;
private bool _migrationChecked;
public List<AccountView> AccountViews { get; set; }
public StateService(IStorageService storageService, IStorageService secureStorageService)
public StateService(IStorageService storageService,
IStorageService secureStorageService,
IMessagingService messagingService)
{
_storageService = storageService;
_secureStorageService = secureStorageService;
_messagingService = messagingService;
}
public async Task<string> GetActiveUserIdAsync()
@@ -67,6 +71,28 @@ namespace Bit.Core.Services
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)
{
return await GetAccessTokenAsync(userId) != null;
@@ -1510,6 +1536,16 @@ namespace Bit.Core.Services
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()
{
if (!_migrationChecked)

View 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";
}
}

View File

@@ -11,7 +11,7 @@
<key>CFBundleIdentifier</key>
<string>com.8bit.bitwarden.autofill</string>
<key>CFBundleShortVersionString</key>
<string>2022.05.1</string>
<string>2022.6.2</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>CFBundleLocalizations</key>

View File

@@ -1,4 +1,5 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
using Bit.App.Controls;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
@@ -10,6 +11,8 @@ namespace Bit.iOS.Core.Utilities
{
public class AccountSwitchingOverlayHelper
{
const string DEFAULT_SYSTEM_AVATAR_IMAGE = "person.2";
IStateService _stateService;
IMessagingService _messagingService;
ILogger _logger;
@@ -23,20 +26,44 @@ namespace Bit.iOS.Core.Utilities
public async Task<UIImage> CreateAvatarImageAsync()
{
var avatarImageSource = new AvatarImageSource(await _stateService.GetNameAsync(), await _stateService.GetEmailAsync());
var avatarUIImage = await avatarImageSource.GetNativeImageAsync();
return avatarUIImage.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal);
try
{
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)
{
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.IsVisible = false;
var renderer = Platform.CreateRenderer(overlay.Content);
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)
{
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);
containerView.Hidden = false;
containerView.UserInteractionEnabled = !overlayVisible;
containerView.Subviews[0].UserInteractionEnabled = !overlayVisible;
}

View File

@@ -1,9 +1,9 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Bit.Core.Services;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Internals;
using Xamarin.Forms.Platform.iOS;
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))
{
if (source == null || source.IsEmpty)
{
return null;
}
var handler = Xamarin.Forms.Internals.Registrar.Registered.GetHandlerForObject<IImageSourceHandler>(source);
if (handler == null)
{
LoggerHelper.LogEvenIfCantBeResolved(new InvalidOperationException("GetNativeImageAsync failed cause IImageSourceHandler couldn't be found"));
return null;
}
try
{
float scale = (float)UIScreen.MainScreen.Scale;
return await handler.LoadImageAsync(source, scale: scale, cancelationToken: cancellationToken);
}
catch (OperationCanceledException)
{
Log.Warning("Image loading", "Image load cancelled");
LoggerHelper.LogEvenIfCantBeResolved(new OperationCanceledException("GetNativeImageAsync was cancelled"));
}
catch (Exception ex)
{
Log.Warning("Image loading", $"Image load failed: {ex}");
LoggerHelper.LogEvenIfCantBeResolved(new InvalidOperationException("GetNativeImageAsync failed", ex));
}
return null;

View File

@@ -59,7 +59,7 @@ namespace Bit.iOS.Core.Utilities
() => ServiceContainer.Resolve<IAppIdService>("appIdService").GetAppIdAsync());
var cryptoPrimitiveService = new CryptoPrimitiveService();
var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage);
var stateService = new StateService(mobileStorageService, secureStorageService);
var stateService = new StateService(mobileStorageService, secureStorageService, messagingService);
var stateMigrationService =
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
var deviceActionService = new DeviceActionService(stateService, messagingService);

View File

@@ -11,7 +11,7 @@
<key>CFBundleIdentifier</key>
<string>com.8bit.bitwarden.find-login-action-extension</string>
<key>CFBundleShortVersionString</key>
<string>2022.05.1</string>
<string>2022.6.2</string>
<key>CFBundleLocalizations</key>
<array>
<string>en</string>

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>2022.05.1</string>
<string>2022.6.2</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>MinimumOSVersion</key>

View File

@@ -11,7 +11,7 @@
<key>CFBundleIdentifier</key>
<string>com.8bit.bitwarden</string>
<key>CFBundleShortVersionString</key>
<string>2022.05.1</string>
<string>2022.6.2</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>CFBundleIconName</key>