1
0
mirror of https://github.com/bitwarden/mobile synced 2026-01-21 11:53:15 +00:00

Compare commits

...

29 Commits

Author SHA1 Message Date
Jacob Fink
a484ca633c Merge branch 'beeep-totp' of https://github.com/bitwarden/mobile into beeep-totp 2022-06-06 08:58:52 -04:00
André Bispo
655b51b6a5 Merge branch 'master' into beeep-totp
# Conflicts:
#	src/App/App.csproj
2022-06-04 18:05:14 +01:00
CarleyDiaz-Bitwarden-zz
8168089591 Updating icons for Linked and Boolean fields (#1935)
Update to icons used for Boolean and Linked to use real icons

Co-authored-by: CarleyDiaz-Bitwarden <103955722+CarleyDiaz-Bitwarden@users.noreply.github.com>
2022-06-02 16:43:14 -04:00
github-actions[bot]
6b55fc3032 Bumped version to 2022.05.1 (#1933)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-06-02 09:30:39 -04:00
github-actions[bot]
87ab42b155 Bumped version to 2022.05.0 (#1931)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-06-01 16:18:06 -04:00
André Filipe da Silva Bispo
98130e89de PS-689 Android: Accessibility - back buttons in search and vault > bin lack appropriate accessible name (#1929)
* PS-689 Added back buttons accessibility text

* PS-689 Changed resource key from "GoBack" to "TapToGoBack"

* PS-689: class rename
2022-06-01 20:50:19 +01:00
André Filipe da Silva Bispo
121f0e3628 PS-675 Added accessibility text to password show/hide toggles (#1926)
* PS-675 Added accessibility text to password show hide toggles

* PS-675 refactor string resource key name
2022-06-01 16:02:28 +01:00
mp-bw
8a3d88b3ce [SG-79] Mobile Vault Filter (#1928)
* [SG-79] Vault Filter

* Update vault button text after sync

* formatting

* cleanup

* cleanup
2022-05-31 13:34:54 -04:00
Federico Maccaroni
b8b41fe847 [PS-536] Fix vault blank after unlocking and back navigation (#1930)
* PS-536 Fix by hack vault blank after unlocking and back navigate when previous page has value on iOS

* PS-536 Added platform check to the hack so it doesn't affect Android performance given that's an issue particular for iOS
2022-05-27 17:17:08 -04:00
André Filipe da Silva Bispo
5bbef3ee16 [PS-676] Accessibility - "Options" expand/collapse control does not announce state (#1925)
* PS-676: Accessibility - "Options" expand/collapse control does not announce state
- Moved to click event to the stacklayout
- Added accessibility text to stacklayout
- Removed accessibility on views inside stacklayout

* PS-676 Changed event to command

Co-authored-by: André Bispo <abispo@bitwarden.com>
2022-05-27 14:52:19 +01:00
Carlos Gonçalves
9a2b6c8ec9 PS-593 - View model properties are now updated on main thread (#1927) 2022-05-27 14:16:45 +01:00
André Filipe da Silva Bispo
5272c99643 PS-674: Accessibility password generator toggles (#1924)
- Additional information for VoiceOver and TalkBack
- Added new labels

Co-authored-by: André Bispo <abispo@bitwarden.com>
2022-05-25 17:25:21 +01:00
André Filipe da Silva Bispo
43e9515a03 [PS-672] Accessibility - File/Text controls unintuitive, "Text" accessible name incorrect (#1923)
* PS-672: Accessibility - File/Text controls unintuitive, "Text" accessible name incorrect
- Added new text to help identify File / Text segmented button
- Added missing text for Text button accessibility

* PS-672: refactor code with pr suggestions

* PS-672: removed unnecessary code

* PS-672: change text from "click" to "tap"

* PS-672: removed comma

Co-authored-by: André Bispo <abispo@bitwarden.com>
2022-05-25 17:20:51 +01:00
André Filipe da Silva Bispo
7e9b7398c8 PS-90: App keeps asking for 2FA even though I've checked the "remember me" checkbox (#1921)
- Logout was removing the 2FA token, portion of code deleted.
- Clear saved token when 2FA error is returned by the server.

Co-authored-by: André Bispo <abispo@bitwarden.com>
2022-05-24 15:09:24 +01:00
Jake Fink
58d7b001a5 add master password reprompt to share sheet extension (#1922) 2022-05-23 16:01:04 -04:00
André Filipe da Silva Bispo
a259560d29 PS-592 Mobile - Name field is not prioritised in search results (#1907)
- Search method now gives priority to matches in the Name property

Co-authored-by: André Bispo <abispo@bitwarden.com>
2022-05-23 10:20:48 +01:00
André Filipe da Silva Bispo
22c746543a PS-518 - Add setting to block AppCenter / Analytics - Mobile (#1905)
* PS-518 - Add setting to block AppCenter / Analytics - Mobile
- Added another entry into Settings page under the Others section
- Added prompt to ask user to enable / disable Crash Reports
- Added compilation tags to remove if the build is FDroid 

* PS-518 Add setting to block AppCenter / Analytics - Mobile
- Reduced FDroid compilation tags throughout the code
- Added Init, Enable and State methods to Logger
- Simplified SettingsPageViewModel Enable/Disable code

* PS-518 Add setting to block AppCenter / Analytics - Mobile
- Appcenter references were removed from App project, 
- Removed FDroid build.yml code that was deleting Appcenter packages from App.csproj

Co-authored-by: André Bispo <abispo@bitwarden.com>
2022-05-18 17:59:19 +01:00
André Filipe da Silva Bispo
bcbc2738ca PS-591 Fix avoid ambiguous characters #1664 (#1906)
* PS-591 - iOS - Avoid ambiguous characters is activated inside the main client, but is deactivated when creating a vault item from the autofill prompt. #1664
- Refactor the name of the property Ambiguous to AvoidAmbiguous, this naming was misleading.
- Fixed bug where the boolean value for the AvoidAmbiguous property was being stored inverted.

* PS-591 - iOS - Avoid ambiguous characters is activated inside the main client, but is deactivated when creating a vault item from the autofill prompt. #1664
- Changed AvoidAmbiguous to AllowAmbiguous
- Added wrapper Prop to bind UI Toggle to VM

Co-authored-by: André Bispo <abispo@bitwarden.com>
2022-05-18 14:58:49 +01:00
Thomas Rittson
604e3b6892 Remove testing requirements from pr template (#1901) 2022-05-10 17:02:40 -04:00
mp-bw
b081a8c634 fix a11y issue with hCaptcha on iOS (#1896) 2022-04-28 15:30:33 -04:00
Federico Maccaroni
c251b950d1 PS-77 Updated two-factor email request to include the device identifier to check whether to send the 2fa email because of a new device (#1895) 2022-04-28 10:51:13 -03:00
Jacob Fink
0b626cedc7 got the countdown working 2022-03-04 16:44:22 -05:00
Jacob Fink
3ac2580742 add toolbar icons 2022-02-17 11:26:14 -05:00
Jacob Fink
008ed8eb56 add authentication view cell 2022-02-16 15:55:02 -05:00
Jacob Fink
26e0e43bb4 Merge branch 'master' into beeep-totp 2022-02-16 09:25:29 -05:00
Jacob Fink
0ad992faec add tab page 2022-02-16 09:25:19 -05:00
Jacob Fink
98dd8298ea clear extra code and fix build 2022-02-15 14:15:42 -05:00
Jacob Fink
bb37bac620 Revert config files from previous commit
This reverts commit b02c58e362.
2022-02-15 13:36:32 -05:00
Carlos J. Muentes
b02c58e362 Initial commit of new TOTP page 2022-02-11 15:22:51 -05:00
80 changed files with 1713 additions and 297 deletions

View File

@@ -21,11 +21,6 @@
## Testing requirements
<!--What functionality requires testing by QA? This includes testing new behavior and regression testing-->
## Before you submit
- [ ] I have added **unit tests** where it makes sense to do so (encouraged but not required)
- [ ] This change requires a **documentation update** (notify the documentation team)

View File

@@ -304,18 +304,6 @@ jobs:
$xml.Save($androidPath);
Write-Output "########################################"
Write-Output "##### Uninstall from App.csproj"
Write-Output "########################################"
$xml=New-Object XML;
$xml.Load($appPath);
$appCenterNode=$xml.SelectSingleNode("/Project/ItemGroup/PackageReference[@Include='Microsoft.AppCenter.Crashes']");
$appCenterNode.ParentNode.RemoveChild($appCenterNode);
$xml.Save($appPath);
Write-Output "########################################"
Write-Output "##### Uninstall from Core.csproj"
Write-Output "########################################"

View File

@@ -145,12 +145,12 @@
<Compile Include="Tiles\GeneratorTileService.cs" />
<Compile Include="Tiles\MyVaultTileService.cs" />
<Compile Include="Utilities\AndroidHelpers.cs" />
<Compile Include="Utilities\AppCenterHelper.cs" />
<Compile Include="Utilities\ThemeHelpers.cs" />
<Compile Include="WebAuthCallbackActivity.cs" />
<Compile Include="Renderers\SelectableLabelRenderer.cs" />
<Compile Include="Services\ClipboardService.cs" />
<Compile Include="Utilities\IntentExtensions.cs" />
<Compile Include="Renderers\CustomPageRenderer.cs" />
</ItemGroup>
<ItemGroup>
<AndroidAsset Include="Assets\bwi-font.ttf" />

View File

@@ -69,10 +69,7 @@ namespace Bit.Droid
Window.AddFlags(Android.Views.WindowManagerFlags.Secure);
}
#if !DEBUG && !FDROID
var appCenterHelper = new AppCenterHelper(_appIdService, _stateService);
var appCenterTask = appCenterHelper.InitAsync();
#endif
ServiceContainer.Resolve<ILogger>("logger").InitAsync();
var toplayout = Window?.DecorView?.RootView;
if (toplayout != null)
@@ -85,6 +82,7 @@ namespace Bit.Droid
_appOptions = GetOptions();
LoadApplication(new App.App(_appOptions));
_broadcasterService.Subscribe(_activityKey, (message) =>
{
if (message.Command == "startEventTimer")

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="2.18.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.05.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>

View File

@@ -0,0 +1,31 @@
using System;
using Android.App;
using Android.Content;
using AndroidX.AppCompat.Widget;
using Bit.App.Resources;
using Bit.Droid.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(ContentPage), typeof(CustomPageRenderer))]
namespace Bit.Droid.Renderers
{
public class CustomPageRenderer : PageRenderer
{
public CustomPageRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
Activity context = (Activity)this.Context;
var toolbar = context.FindViewById<Toolbar>(Resource.Id.toolbar);
if(toolbar != null)
{
toolbar.NavigationContentDescription = AppResources.TapToGoBack;
}
}
}
}

View File

@@ -3,11 +3,10 @@ using System.Threading.Tasks;
using Android.OS;
using Android.Security.Keystore;
using Bit.Core.Abstractions;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Java.Security;
using Javax.Crypto;
#if !FDROID
using Microsoft.AppCenter.Crashes;
#endif
namespace Bit.Droid.Services
{
@@ -74,9 +73,7 @@ namespace Bit.Droid.Services
catch (InvalidKeyException e)
{
// Fallback for old bitwarden users without a key
#if !FDROID
Crashes.TrackError(e);
#endif
LoggerHelper.LogEvenIfCantBeResolved(e);
CreateKey();
}
@@ -101,9 +98,7 @@ namespace Bit.Droid.Services
{
// Catch silently to allow biometrics to function on devices that are in a state where key generation
// is not functioning
#if !FDROID
Crashes.TrackError(e);
#endif
LoggerHelper.LogEvenIfCantBeResolved(e);
}
}
}

View File

@@ -1,58 +0,0 @@
#if !FDROID
using Bit.Core.Abstractions;
using System.Threading.Tasks;
using Microsoft.AppCenter;
using Microsoft.AppCenter.Crashes;
using Newtonsoft.Json;
namespace Bit.Droid.Utilities
{
public class AppCenterHelper
{
private const string AppSecret = "d3834185-b4a6-4347-9047-b86c65293d42";
private readonly IAppIdService _appIdService;
private readonly IStateService _stateService;
private string _userId;
private string _appId;
public AppCenterHelper(
IAppIdService appIdService,
IStateService stateService)
{
_appIdService = appIdService;
_stateService = stateService;
}
public async Task InitAsync()
{
_userId = await _stateService.GetActiveUserIdAsync();
_appId = await _appIdService.GetAppIdAsync();
AppCenter.Start(AppSecret, typeof(Crashes));
AppCenter.SetUserId(_userId);
Crashes.GetErrorAttachments = (ErrorReport report) =>
{
return new ErrorAttachmentLog[]
{
ErrorAttachmentLog.AttachmentWithText(Description, "crshdesc.txt"),
};
};
}
public string Description
{
get
{
return JsonConvert.SerializeObject(new
{
AppId = _appId,
UserId = _userId
}, Formatting.Indented);
}
}
}
}
#endif

View File

@@ -13,7 +13,6 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="4.4.0" />
<PackageReference Include="Plugin.Fingerprint" Version="2.1.4" />
<PackageReference Include="SkiaSharp.Views.Forms" Version="2.80.3" />
<PackageReference Include="Xamarin.CommunityToolkit" Version="2.0.1" />
@@ -128,6 +127,7 @@
<ItemGroup>
<Folder Include="Resources\" />
<Folder Include="Behaviors\" />
<Folder Include="Pages\Authenticator\" />
<Folder Include="Controls\AccountSwitchingOverlay\" />
</ItemGroup>

View File

@@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8"?>
<controls:ExtendedGrid xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Controls.AuthenticatorViewCell"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:ff="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
StyleClass="list-row, list-row-platform"
RowSpacing="0"
ColumnSpacing="0"
x:DataType="pages:AuthenticatorPageListItem">
<Grid.Resources>
<u:IconGlyphConverter x:Key="iconGlyphConverter"/>
<u:IconImageConverter x:Key="iconImageConverter"/>
<u:InverseBoolConverter x:Key="inverseBool" />
<u:StringHasValueConverter x:Key="stringHasValueConverter" />
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="60" />
</Grid.ColumnDefinitions>
<controls:IconLabel
Grid.Column="0"
HorizontalOptions="Center"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform"
IsVisible="{Binding ShowIconImage, Converter={StaticResource inverseBool}}"
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
AutomationProperties.IsInAccessibleTree="False" />
<ff:CachedImage
Grid.Column="0"
BitmapOptimizations="True"
ErrorPlaceholder="login.png"
LoadingPlaceholder="login.png"
HorizontalOptions="Center"
VerticalOptions="Center"
WidthRequest="22"
HeightRequest="22"
IsVisible="{Binding ShowIconImage}"
Source="{Binding IconImageSource, Mode=OneTime}"
AutomationProperties.IsInAccessibleTree="False" />
<Grid RowSpacing="0" ColumnSpacing="0" Grid.Row="0" Grid.Column="1" VerticalOptions="Center" Padding="0, 7">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<controls:MonoLabel
LineBreakMode="TailTruncation"
Grid.Column="0"
Grid.Row="0"
Grid.ColumnSpan="3"
StyleClass="list-title, list-title-platform"
Text="{Binding TotpCodeFormatted, Mode=OneWay}" />
<Label
LineBreakMode="TailTruncation"
Grid.Column="0"
Grid.Row="1"
StyleClass="list-subtitle, list-subtitle-platform"
Text="{Binding Cipher.Name}" />
<controls:IconLabel
Grid.Column="1"
Grid.Row="1"
HorizontalOptions="Start"
VerticalOptions="Center"
StyleClass="list-title-icon"
Margin="5, 0, 0, 0"
Text="{Binding Source={x:Static core:BitwardenIcons.Collection}}"
IsVisible="{Binding Cipher.Shared, Mode=OneTime}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Shared}" />
</Grid>
<Label
Text="{Binding TotpSec, Mode=OneWay}"
Style="{DynamicResource textTotp}"
Margin="0, 0, 10, 0"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2"
HorizontalOptions="End"
HorizontalTextAlignment="End"
VerticalOptions="CenterAndExpand" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
Command="{Binding CopyCommand}"
CommandParameter="LoginTotp"
Grid.Row="0"
Grid.Column="2"
Grid.RowSpan="2"
Padding="0,0,1,0"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n CopyTotp}" />
</controls:ExtendedGrid>

View File

@@ -0,0 +1,121 @@
using System;
using Bit.App.Utilities;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public partial class AuthenticatorViewCell : ExtendedGrid
{
public static readonly BindableProperty CipherProperty = BindableProperty.Create(
nameof(Cipher), typeof(CipherView), typeof(AuthenticatorViewCell), default(CipherView), BindingMode.TwoWay);
public static readonly BindableProperty WebsiteIconsEnabledProperty = BindableProperty.Create(
nameof(WebsiteIconsEnabled), typeof(bool?), typeof(AuthenticatorViewCell));
public static readonly BindableProperty TotpSecProperty = BindableProperty.Create(
nameof(TotpSec), typeof(long), typeof(AuthenticatorViewCell));
//public static readonly BindableProperty ButtonCommandProperty = BindableProperty.Create(
// nameof(ButtonCommand), typeof(Command<CipherView>), typeof(AuthenticatorViewCell));
public AuthenticatorViewCell()
{
InitializeComponent();
}
public Command CopyCommand { get; set; }
public CipherView Cipher
{
get => GetValue(CipherProperty) as CipherView;
set => SetValue(CipherProperty, value);
}
public bool? WebsiteIconsEnabled
{
get => (bool)GetValue(WebsiteIconsEnabledProperty);
set => SetValue(WebsiteIconsEnabledProperty, value);
}
public long TotpSec
{
get => (long)GetValue(TotpSecProperty);
set => SetValue(TotpSecProperty, value);
}
public bool ShowIconImage
{
get => WebsiteIconsEnabled ?? false
&& !string.IsNullOrWhiteSpace(Cipher.Login?.Uri)
&& IconImageSource != null;
}
private string _iconImageSource = string.Empty;
public string IconImageSource
{
get
{
if (_iconImageSource == string.Empty) // default value since icon source can return null
{
_iconImageSource = IconImageHelper.GetLoginIconImage(Cipher);
}
return _iconImageSource;
}
}
private string _totpCodeFormatted = "938 928";
public string TotpCodeFormatted
{
get => _totpCodeFormatted;
set => _totpCodeFormatted = value;
}
//public Command<CipherView> ButtonCommand
//{
// get => GetValue(ButtonCommandProperty) as Command<CipherView>;
// set => SetValue(ButtonCommandProperty, value);
//}
//protected override void OnPropertyChanged(string propertyName = null)
//{
// base.OnPropertyChanged(propertyName);
// if (propertyName == CipherProperty.PropertyName)
// {
// if (Cipher == null)
// {
// return;
// }
// _cipherLabel.Text = Cipher.Name;
// }
// else if (propertyName == WebsiteIconsEnabledProperty.PropertyName)
// {
// if (Cipher == null)
// {
// return;
// }
// ((AuthenticatorViewCellViewModel)BindingContext).WebsiteIconsEnabled = WebsiteIconsEnabled ?? false;
// }
// else if (propertyName == TotpSecProperty.PropertyName)
// {
// if (Cipher == null)
// {
// return;
// }
// ((AuthenticatorViewCellViewModel)BindingContext).UpdateTotpSec(TotpSec);
// }
//}
private void MoreButton_Clicked(object sender, EventArgs e)
{
var cipher = ((sender as MiButton)?.BindingContext as AuthenticatorViewCellViewModel)?.Cipher;
if (cipher != null)
{
//ButtonCommand?.Execute(cipher);
}
}
}
}

View File

@@ -0,0 +1,103 @@
using System.Threading.Tasks;
using Bit.App.Utilities;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class AuthenticatorViewCellViewModel : ExtendedViewModel
{
private CipherView _cipher;
private string _totpCodeFormatted = "938 928";
private string _totpSec;
private bool _websiteIconsEnabled;
private string _iconImageSource = string.Empty;
public AuthenticatorViewCellViewModel(CipherView cipherView, bool websiteIconsEnabled)
{
Cipher = cipherView;
WebsiteIconsEnabled = websiteIconsEnabled;
}
public Command CopyCommand { get; set; }
public CipherView Cipher
{
get => _cipher;
set => SetProperty(ref _cipher, value);
}
public string TotpCodeFormatted
{
get => _totpCodeFormatted;
set => SetProperty(ref _totpCodeFormatted, value);
}
public string TotpSec
{
get => _totpSec;
set => SetProperty(ref _totpSec, value);
}
public bool WebsiteIconsEnabled
{
get => _websiteIconsEnabled;
set => SetProperty(ref _websiteIconsEnabled, value);
}
public bool ShowIconImage
{
get => WebsiteIconsEnabled
&& !string.IsNullOrWhiteSpace(Cipher.Login?.Uri)
&& IconImageSource != null;
}
public string IconImageSource
{
get
{
if (_iconImageSource == string.Empty) // default value since icon source can return null
{
_iconImageSource = IconImageHelper.GetLoginIconImage(Cipher);
}
return _iconImageSource;
}
}
public void UpdateTotpSec(long totpSec)
{
_totpSec = totpSec.ToString();
}
//private async Task TotpUpdateCodeAsync()
//{
// if (Cipher == null || Cipher.Type != Core.Enums.CipherType.Login || Cipher.Login.Totp == null)
// {
// _totpInterval = null;
// return;
// }
// _totpCode = await _totpService.GetCodeAsync(Cipher.Login.Totp);
// if (_totpCode != null)
// {
// if (_totpCode.Length > 4)
// {
// var half = (int)Math.Floor(_totpCode.Length / 2M);
// TotpCodeFormatted = string.Format("{0} {1}", _totpCode.Substring(0, half),
// _totpCode.Substring(half));
// }
// else
// {
// TotpCodeFormatted = _totpCode;
// }
// }
// else
// {
// TotpCodeFormatted = null;
// _totpInterval = null;
// }
//}
}
}

View File

@@ -108,7 +108,7 @@
<controls:MiButton
Grid.Row="0"
Grid.Column="2"
Text="&#xe5d3;"
Text="{Binding Source={x:Static core:BitwardenIcons.ViewCellMenu}}"
StyleClass="list-row-button, list-row-button-platform, btn-disabled"
Clicked="MoreButton_Clicked"
VerticalOptions="CenterAndExpand"

View File

@@ -122,7 +122,7 @@
<controls:MiButton
Grid.Row="0"
Grid.Column="2"
Text="&#xe5d3;"
Text="{Binding Source={x:Static core:BitwardenIcons.ViewCellMenu}}"
IsVisible="{Binding ShowOptions, Mode=OneWay}"
StyleClass="list-row-button, list-row-button-platform, btn-disabled"
Clicked="MoreButton_Clicked"

View File

@@ -46,7 +46,11 @@ namespace Bit.App.Pages
{
get => _showPassword;
set => SetProperty(ref _showPassword, value,
additionalPropertyNames: new[] { nameof(ShowPasswordIcon) });
additionalPropertyNames: new[]
{
nameof(ShowPasswordIcon),
nameof(PasswordVisibilityAccessibilityText)
});
}
public bool IsPolicyInEffect
@@ -68,6 +72,7 @@ namespace Bit.App.Pages
}
public string ShowPasswordIcon => ShowPassword ? "" : "";
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public string MasterPassword { get; set; }
public string ConfirmMasterPassword { get; set; }
public string Hint { get; set; }

View File

@@ -80,7 +80,8 @@
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"/>
</Grid>
<Grid
x:Name="_passwordGrid"
@@ -119,7 +120,8 @@
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
</Grid>
<StackLayout
StyleClass="box-row"

View File

@@ -72,7 +72,8 @@ namespace Bit.App.Pages
set => SetProperty(ref _showPassword, value,
additionalPropertyNames: new string[]
{
nameof(ShowPasswordIcon)
nameof(ShowPasswordIcon),
nameof(PasswordVisibilityAccessibilityText),
});
}
@@ -128,6 +129,7 @@ namespace Bit.App.Pages
public Command SubmitCommand { get; }
public Command TogglePasswordCommand { get; }
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public string MasterPassword { get; set; }
public string Pin { get; set; }
public Action UnlockedAction { get; set; }

View File

@@ -101,7 +101,8 @@
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"/>
</Grid>
</StackLayout>
<StackLayout Padding="10, 0">

View File

@@ -58,7 +58,8 @@ namespace Bit.App.Pages
set => SetProperty(ref _showPassword, value,
additionalPropertyNames: new string[]
{
nameof(ShowPasswordIcon)
nameof(ShowPasswordIcon),
nameof(PasswordVisibilityAccessibilityText)
});
}
@@ -85,6 +86,7 @@ namespace Bit.App.Pages
public Command LogInCommand { get; }
public Command TogglePasswordCommand { get; }
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public Action StartTwoFactorAction { get; set; }
public Action LogInSuccessAction { get; set; }
public Action UpdateTempPasswordAction { get; set; }

View File

@@ -68,7 +68,8 @@
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"/>
</Grid>
<Label
Text="{u:I18n MasterPasswordDescription}"
@@ -106,7 +107,8 @@
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
</Grid>
<StackLayout StyleClass="box-row">
<Label

View File

@@ -51,7 +51,8 @@ namespace Bit.App.Pages
set => SetProperty(ref _showPassword, value,
additionalPropertyNames: new string[]
{
nameof(ShowPasswordIcon)
nameof(ShowPasswordIcon),
nameof(PasswordVisibilityAccessibilityText)
});
}
@@ -73,6 +74,7 @@ namespace Bit.App.Pages
public Command TogglePasswordCommand { get; }
public Command ToggleConfirmPasswordCommand { get; }
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public string Name { get; set; }
public string Email { get; set; }
public string MasterPassword { get; set; }

View File

@@ -107,7 +107,8 @@
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
</Grid>
<Label
Text="{u:I18n MasterPasswordDescription}"
@@ -145,7 +146,8 @@
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
</Grid>
<StackLayout StyleClass="box-row">
<Label

View File

@@ -55,7 +55,11 @@ namespace Bit.App.Pages
{
get => _showPassword;
set => SetProperty(ref _showPassword, value,
additionalPropertyNames: new[] { nameof(ShowPasswordIcon) });
additionalPropertyNames: new[]
{
nameof(ShowPasswordIcon),
nameof(PasswordVisibilityAccessibilityText)
});
}
public bool IsPolicyInEffect
@@ -86,6 +90,7 @@ namespace Bit.App.Pages
public Command TogglePasswordCommand { get; }
public Command ToggleConfirmPasswordCommand { get; }
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public string MasterPassword { get; set; }
public string ConfirmMasterPassword { get; set; }
public string Hint { get; set; }

View File

@@ -29,6 +29,7 @@ namespace Bit.App.Pages
private readonly IBroadcasterService _broadcasterService;
private readonly IStateService _stateService;
private readonly II18nService _i18nService;
private readonly IAppIdService _appIdService;
private TwoFactorProviderType? _selectedProviderType;
private string _totpInstruction;
@@ -49,6 +50,7 @@ namespace Bit.App.Pages
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
PageTitle = AppResources.TwoStepLogin;
SubmitCommand = new Command(async () => await SubmitAsync());
@@ -380,7 +382,8 @@ namespace Bit.App.Pages
var request = new TwoFactorEmailRequest
{
Email = _authService.Email,
MasterPasswordHash = _authService.MasterPasswordHash
MasterPasswordHash = _authService.MasterPasswordHash,
DeviceIdentifier = await _appIdService.GetAppIdAsync()
};
await _apiService.PostTwoFactorEmailAsync(request);
if (showLoading)

View File

@@ -105,7 +105,8 @@
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
</Grid>
</StackLayout>
<StackLayout StyleClass="box">
@@ -140,7 +141,8 @@
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
</Grid>
<StackLayout StyleClass="box-row">
<Label

View File

@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="utf-8" ?>
<pages:BaseContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Pages.AuthenticatorPage"
xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:effects="clr-namespace:Bit.App.Effects"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:controls="clr-namespace:Bit.App.Controls"
x:DataType="pages:AuthenticatorPageViewModel"
Title="{Binding PageTitle}"
x:Name="_page">
<ContentPage.BindingContext>
<pages:AuthenticatorPageViewModel />
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<ToolbarItem Icon="search.png" Clicked="Search_Clicked"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Search}" />
</ContentPage.ToolbarItems>
<ContentPage.Resources>
<ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" />
<ToolbarItem x:Name="_aboutIconItem" x:Key="aboutIconItem" Icon="info.png"
Clicked="About_Clicked" Order="Primary" Priority="-1"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n AboutSend}" />
<ToolbarItem x:Name="_syncItem" x:Key="syncItem" Text="{u:I18n Sync}"
Clicked="Sync_Clicked" Order="Secondary" />
<ToolbarItem x:Name="_lockItem" x:Key="lockItem" Text="{u:I18n Lock}"
Clicked="Lock_Clicked" Order="Secondary" />
<ToolbarItem x:Name="_aboutTextItem" x:Key="aboutTextItem" Text="{u:I18n AboutSend}"
Clicked="About_Clicked" Order="Secondary" />
<ToolbarItem x:Name="_addItem" x:Key="addItem" Icon="plus.png"
Clicked="AddButton_Clicked" Order="Primary"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n AddItem}" />
<DataTemplate x:Key="authenticatorTemplate"
x:DataType="pages:AuthenticatorPageListItem">
<controls:AuthenticatorViewCell
Cipher="{Binding Cipher}"
WebsiteIconsEnabled="{Binding BindingContext.WebsiteIconsEnabled, Source={x:Reference _page}}"
TotpSec="{Binding TotpSec}"/>
</DataTemplate>
<StackLayout x:Key="mainLayout" x:Name="_mainLayout">
<RefreshView>
<controls:ExtendedCollectionView
ItemsSource="{Binding Items}"
VerticalOptions="FillAndExpand"
SelectionMode="Single"
SelectionChanged="RowSelected"
StyleClass="list, list-platform">
<controls:ExtendedCollectionView.ItemTemplate>
<DataTemplate x:DataType="pages:AuthenticatorPageListItem">
<controls:AuthenticatorViewCell />
</DataTemplate>
</controls:ExtendedCollectionView.ItemTemplate>
</controls:ExtendedCollectionView>
</RefreshView>
</StackLayout>
</ResourceDictionary>
</ContentPage.Resources>
<AbsoluteLayout
x:Name="_absLayout"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
<ContentView
x:Name="_mainContent"
AbsoluteLayout.LayoutFlags="All"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1" />
<Button
x:Name="_fab"
Image="plus.png"
Clicked="AddButton_Clicked"
Style="{StaticResource btn-fab}"
AbsoluteLayout.LayoutFlags="PositionProportional"
AbsoluteLayout.LayoutBounds="1, 1, AutoSize, AutoSize"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n AddItem}">
<Button.Effects>
<effects:FabShadowEffect />
</Button.Effects>
</Button>
</AbsoluteLayout>
</pages:BaseContentPage>

View File

@@ -0,0 +1,176 @@
using Bit.App.Resources;
using System;
using System.Linq;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Xamarin.Forms;
using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
namespace Bit.App.Pages
{
public partial class AuthenticatorPage : BaseContentPage
{
#region Members
private readonly IBroadcasterService _broadcasterService;
private readonly ISyncService _syncService;
private readonly ICipherService _cipherService;
private AuthenticatorPageViewModel _vm;
private readonly bool _fromTabPage;
private readonly Action<string> _selectAction;
private readonly TabsPage _tabsPage;
#endregion
public AuthenticatorPage(bool fromTabPage, Action<string> selectAction = null, TabsPage tabsPage = null)
{
//_tabsPage = tabsPage;
InitializeComponent();
//_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_vm = BindingContext as AuthenticatorPageViewModel;
//_vm.Page = this;
//_fromTabPage = fromTabPage;
//_selectAction = selectAction;
if (Device.RuntimePlatform == Device.iOS)
{
_absLayout.Children.Remove(_fab);
ToolbarItems.Add(_aboutIconItem);
ToolbarItems.Add(_addItem);
}
else
{
ToolbarItems.Add(_syncItem);
ToolbarItems.Add(_lockItem);
ToolbarItems.Add(_aboutTextItem);
}
}
public async Task InitAsync()
{
await _vm.LoadAsync();
}
protected async override void OnAppearing()
{
base.OnAppearing();
//if (!_fromTabPage)
//{
// await InitAsync();
//}
//_broadcasterService.Subscribe(nameof(GeneratorPage), async (message) =>
//{
// if (message.Command == "updatedTheme")
// {
// Device.BeginInvokeOnMainThread(() =>
// {
// //_vm.RedrawPassword();
// });
// }
//});
await LoadOnAppearedAsync(_mainLayout, false, async () =>
{
if (!_syncService.SyncInProgress || (await _cipherService.GetAllAsync()).Any())
{
try
{
await _vm.LoadAsync();
}
catch (Exception e) when (e.Message.Contains("No key."))
{
await Task.Delay(1000);
await _vm.LoadAsync();
}
}
else
{
await Task.Delay(5000);
if (!_vm.Loaded)
{
await _vm.LoadAsync();
}
}
AdjustToolbar();
//await CheckAddRequest();
}, _mainContent);
}
private async void Search_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
// var page = new SendsPage(_vm.Filter, _vm.Type != null);
// await Navigation.PushModalAsync(new NavigationPage(page));
}
}
private async void Sync_Clicked(object sender, EventArgs e)
{
// await _vm.SyncAsync();
}
private async void Lock_Clicked(object sender, EventArgs e)
{
// await _vaultTimeoutService.LockAsync(true, true);
}
private void About_Clicked(object sender, EventArgs e)
{
// _vm.ShowAbout();
}
private async void AddButton_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
// var page = new SendAddEditPage(null, null, _vm.Type);
// await Navigation.PushModalAsync(new NavigationPage(page));
}
}
private async void RowSelected(object sender, SelectionChangedEventArgs e)
{
}
private async void Copy_Clicked(object sender, EventArgs e)
{
//await _vm.CopyAsync();
}
private async void More_Clicked(object sender, EventArgs e)
{
//if (!DoOnce())
//{
// return;
//}
//var selection = await DisplayActionSheet(AppResources.Options, AppResources.Cancel,
// null, AppResources.PasswordHistory);
//if (selection == AppResources.PasswordHistory)
//{
// var page = new GeneratorHistoryPage();
// await Navigation.PushModalAsync(new Xamarin.Forms.NavigationPage(page));
//}
}
protected override void OnDisappearing()
{
base.OnDisappearing();
//_broadcasterService.Unsubscribe(nameof(GeneratorPage));
}
private void AdjustToolbar()
{
//_addItem.IsEnabled = !_vm.Deleted;
//_addItem.IconImageSource = _vm.Deleted ? null : "plus.png";
}
}
}

View File

@@ -0,0 +1,129 @@
using System;
using System.Threading.Tasks;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public class AuthenticatorPageListItem : ExtendedViewModel
{
//private string _totpCode;
private readonly ITotpService _totpService;
//public CipherView Cipher { get; set; }
//public CipherType? Type { get; set; }
//public int interval { get; set; }
//public long TotpSec { get; set; }
//private DateTime? _totpInterval = null;
private CipherView _cipher;
private bool _websiteIconsEnabled;
private string _iconImageSource = string.Empty;
public int interval { get; set; }
private string _totpSec;
private string _totpCode;
private string _totpCodeFormatted = "938 928";
public AuthenticatorPageListItem(CipherView cipherView, bool websiteIconsEnabled)
{
_totpService = ServiceContainer.Resolve<ITotpService>("totpService");
Cipher = cipherView;
WebsiteIconsEnabled = websiteIconsEnabled;
interval = _totpService.GetTimeInterval(Cipher.Login.Totp);
}
public Command CopyCommand { get; set; }
public CipherView Cipher
{
get => _cipher;
set => SetProperty(ref _cipher, value);
}
public string TotpCodeFormatted
{
get => _totpCodeFormatted;
set => SetProperty(ref _totpCodeFormatted, value);
}
public string TotpSec
{
get => _totpSec;
set => SetProperty(ref _totpSec, value);
}
public bool WebsiteIconsEnabled
{
get => _websiteIconsEnabled;
set => SetProperty(ref _websiteIconsEnabled, value);
}
public bool ShowIconImage
{
get => WebsiteIconsEnabled
&& !string.IsNullOrWhiteSpace(Cipher.Login?.Uri)
&& IconImageSource != null;
}
public string IconImageSource
{
get
{
if (_iconImageSource == string.Empty) // default value since icon source can return null
{
_iconImageSource = IconImageHelper.GetLoginIconImage(Cipher);
}
return _iconImageSource;
}
}
public async Task TotpTickAsync()
{
var epoc = CoreHelpers.EpocUtcNow() / 1000;
var mod = epoc % interval;
var totpSec = interval - mod;
TotpSec = totpSec.ToString();
//TotpLow = totpSec < 7;
if (mod == 0)
{
await TotpUpdateCodeAsync();
}
}
public async Task TotpUpdateCodeAsync()
{
_totpCode = await _totpService.GetCodeAsync(Cipher.Login.Totp);
if (_totpCode != null)
{
if (_totpCode.Length > 4)
{
var half = (int)Math.Floor(_totpCode.Length / 2M);
TotpCodeFormatted = string.Format("{0} {1}", _totpCode.Substring(0, half),
_totpCode.Substring(half));
}
else
{
TotpCodeFormatted = _totpCode;
}
}
else
{
TotpCodeFormatted = null;
}
}
}
}

View File

@@ -0,0 +1,153 @@
using System;
using System.Threading.Tasks;
using System.Linq;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public class AuthenticatorPageViewModel : BaseViewModel
{
#region Members
private readonly IClipboardService _clipboardService;
private readonly ITotpService _totpService;
private readonly IUserService _userService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly ICipherService _cipherService;
private bool _showList = true;
private bool _refreshing;
private bool _loaded;
private bool _websiteIconsEnabled = true;
//private long _totpSec;
#endregion
#region Ctor
public AuthenticatorPageViewModel()
{
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_totpService = ServiceContainer.Resolve<ITotpService>("totpService");
PageTitle = AppResources.Authenticator;
Items = new ExtendedObservableCollection<AuthenticatorPageListItem>();
}
#endregion
#region Methods
public async Task CopyAsync()
{
//await _clipboardService.CopyTextAsync(Password);
//_platformUtilsService.ShowToast("success", null,
// string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
}
public async Task LoadAsync()
{
var authed = await _userService.IsAuthenticatedAsync();
if (!authed)
{
return;
}
if (await _vaultTimeoutService.IsLockedAsync())
{
return;
}
try
{
await LoadDataAsync();
}
finally
{
ShowList = true;
Refreshing = false;
}
}
private async Task LoadDataAsync()
{
var _allCiphers = await _cipherService.GetAllDecryptedAsync();
_allCiphers = _allCiphers.Where(c => c.Type == Core.Enums.CipherType.Login && c.Login.Totp != null).ToList();
var filteredCiphers = _allCiphers.Select(c => new AuthenticatorPageListItem(c, WebsiteIconsEnabled)).ToList();
Items.ResetWithRange(filteredCiphers);
foreach (AuthenticatorPageListItem item in Items)
{
item.TotpUpdateCodeAsync();
}
//await TotpUpdateCodeAsync();
// var interval = _totpService.GetTimeInterval(Cipher.Login.Totp);
// await TotpTickAsync(interval);
// _totpInterval = DateTime.UtcNow;
Device.StartTimer(new TimeSpan(0, 0, 1), () =>
{
foreach(AuthenticatorPageListItem item in Items)
{
item.TotpTickAsync();
}
return true;
});
//}
//private async Task TotpTickAsync(int intervalSeconds)
//{
// var epoc = CoreHelpers.EpocUtcNow() / 1000;
// var mod = epoc % intervalSeconds;
// var totpSec = intervalSeconds - mod;
// TotpSec = totpSec.ToString();
// TotpLow = totpSec < 7;
// if (mod == 0)
// {
// await TotpUpdateCodeAsync();
// }
}
#endregion
#region Properties
public ExtendedObservableCollection<AuthenticatorPageListItem> Items { get; set; }
public Command RefreshCommand { get; set; }
public bool ShowList
{
get => _showList;
set => SetProperty(ref _showList, value);
}
public bool Refreshing
{
get => _refreshing;
set => SetProperty(ref _refreshing, value);
}
public bool WebsiteIconsEnabled
{
get => _websiteIconsEnabled;
set => SetProperty(ref _websiteIconsEnabled, value);
}
public bool Loaded
{
get => _loaded;
set => SetProperty(ref _loaded, value);
}
//public long TotpSec
//{
// get => _totpSec;
// set => SetProperty(ref _totpSec, value);
//}
#endregion
}
}

View File

@@ -37,11 +37,14 @@ namespace Bit.App.Pages
bool cancelled = false;
try
{
// PrefersEphemeralWebBrowserSession should be false to allow access to the hCaptcha accessibility
// cookie set in the default browser
// https://www.hcaptcha.com/accessibility
var options = new WebAuthenticatorOptions
{
Url = new Uri(url),
CallbackUrl = new Uri(callbackUri),
PrefersEphemeralWebBrowserSession = true,
PrefersEphemeralWebBrowserSession = false,
};
authResult = await WebAuthenticator.AuthenticateAsync(options);
}

View File

@@ -183,52 +183,68 @@
<Label
Text="A-Z"
StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand" />
HorizontalOptions="StartAndExpand"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n UppercaseAtoZ}"/>
<Switch
IsToggled="{Binding Uppercase}"
IsEnabled="{Binding EnforcedPolicyOptions.UseUppercase,
Converter={StaticResource inverseBool}}"
StyleClass="box-value"
HorizontalOptions="End" />
HorizontalOptions="End"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n UppercaseAtoZ}"/>
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="a-z"
StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand" />
HorizontalOptions="StartAndExpand"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n LowercaseAtoZ}"/>
<Switch
IsToggled="{Binding Lowercase}"
IsEnabled="{Binding EnforcedPolicyOptions.UseLowercase,
Converter={StaticResource inverseBool}}"
StyleClass="box-value"
HorizontalOptions="End" />
HorizontalOptions="End"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n LowercaseAtoZ}" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="0-9"
StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand" />
HorizontalOptions="StartAndExpand"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n NumbersZeroToNine}"/>
<Switch
IsToggled="{Binding Number}"
IsEnabled="{Binding EnforcedPolicyOptions.UseNumbers,
Converter={StaticResource inverseBool}}"
StyleClass="box-value"
HorizontalOptions="End" />
HorizontalOptions="End"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n NumbersZeroToNine}"/>
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="!@#$%^&amp;*"
StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand" />
HorizontalOptions="StartAndExpand"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n SpecialCharacters}"/>
<Switch
IsToggled="{Binding Special}"
IsEnabled="{Binding EnforcedPolicyOptions.UseSpecial,
Converter={StaticResource inverseBool}}"
StyleClass="box-value"
HorizontalOptions="End" />
HorizontalOptions="End"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n SpecialCharacters}"/>
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-stepper">
@@ -277,7 +293,7 @@
StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding AvoidAmbiguous}"
IsToggled="{Binding AvoidAmbiguousChars}"
StyleClass="box-value"
HorizontalOptions="End" />
</StackLayout>

View File

@@ -23,7 +23,7 @@ namespace Bit.App.Pages
private bool _lowercase;
private bool _number;
private bool _special;
private bool _avoidAmbiguous;
private bool _allowAmbiguousChars;
private int _minNumber;
private int _minSpecial;
private int _length = 5;
@@ -130,19 +130,29 @@ namespace Bit.App.Pages
}
}
public bool AvoidAmbiguous
public bool AllowAmbiguousChars
{
get => _avoidAmbiguous;
get => _allowAmbiguousChars;
set
{
if (SetProperty(ref _avoidAmbiguous, value))
if (SetProperty(ref _allowAmbiguousChars, value,
additionalPropertyNames: new string[]
{
nameof(AvoidAmbiguousChars)
}))
{
_options.Ambiguous = !value;
_options.AllowAmbiguousChar = value;
var task = SaveOptionsAsync();
}
}
}
public bool AvoidAmbiguousChars
{
get => !AllowAmbiguousChars;
set => AllowAmbiguousChars = !value;
}
public int MinNumber
{
get => _minNumber;
@@ -315,7 +325,7 @@ namespace Bit.App.Pages
private void LoadFromOptions()
{
AvoidAmbiguous = !_options.Ambiguous.GetValueOrDefault();
AllowAmbiguousChars = _options.AllowAmbiguousChar.GetValueOrDefault();
TypeSelectedIndex = _options.Type == "passphrase" ? 1 : 0;
IsPassword = TypeSelectedIndex == 0;
MinNumber = _options.MinNumber.GetValueOrDefault();
@@ -333,7 +343,7 @@ namespace Bit.App.Pages
private void SetOptions()
{
_options.Ambiguous = !AvoidAmbiguous;
_options.AllowAmbiguousChar = AllowAmbiguousChars;
_options.Type = TypeSelectedIndex == 1 ? "passphrase" : "password";
_options.MinNumber = MinNumber;
_options.MinSpecial = MinSpecial;

View File

@@ -2,6 +2,7 @@
<pages:BaseContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
x:Class="Bit.App.Pages.SendAddEditPage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities"
@@ -121,6 +122,7 @@
Clicked="FileType_Clicked"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n File}"
AutomationProperties.HelpText="{Binding FileTypeAccessibilityLabel}"
Grid.Column="0">
</Button>
<Button
@@ -132,6 +134,7 @@
Clicked="TextType_Clicked"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Text}"
AutomationProperties.HelpText="{Binding TextTypeAccessibilityLabel}"
Grid.Column="1">
</Button>
</Grid>
@@ -250,28 +253,31 @@
</StackLayout>
<StackLayout
Orientation="Horizontal"
Spacing="0">
Spacing="0"
xct:TouchEffect.Command="{Binding ToggleOptionsCommand}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{Binding OptionsAccessilibityText}">
<Button
Text="{u:I18n Options}"
x:Name="_btnOptions"
StyleClass="box-row-button"
TextColor="{DynamicResource PrimaryColor}"
Margin="0"
Clicked="ToggleOptions_Clicked"/>
AutomationProperties.IsInAccessibleTree="False"/>
<controls:IconButton
x:Name="_btnOptionsUp"
Text="{Binding Source={x:Static core:BitwardenIcons.ChevronUp}}"
StyleClass="box-row-button"
TextColor="{DynamicResource PrimaryColor}"
Clicked="ToggleOptions_Clicked"
IsVisible="{Binding ShowOptions}" />
IsVisible="{Binding ShowOptions}"
AutomationProperties.IsInAccessibleTree="False"/>
<controls:IconButton
x:Name="_btnOptionsDown"
Text="{Binding Source={x:Static core:BitwardenIcons.AngleDown}}"
StyleClass="box-row-button"
TextColor="{DynamicResource PrimaryColor}"
Clicked="ToggleOptions_Clicked"
IsVisible="{Binding ShowOptions, Converter={StaticResource inverseBool}}" />
IsVisible="{Binding ShowOptions, Converter={StaticResource inverseBool}}"
AutomationProperties.IsInAccessibleTree="False"/>
</StackLayout>
<StackLayout IsVisible="{Binding ShowOptions}">
<StackLayout
@@ -438,7 +444,8 @@
Command="{Binding TogglePasswordCommand}"
Margin="10,0,0,0"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
</StackLayout>
<Label
Text="{u:I18n PasswordInfo}"

View File

@@ -209,11 +209,6 @@ namespace Bit.App.Pages
}
}
private void ToggleOptions_Clicked(object sender, EventArgs e)
{
_vm.ToggleOptions();
}
private void ClearExpirationDate_Clicked(object sender, EventArgs e)
{
if (DoOnce())

View File

@@ -44,6 +44,8 @@ namespace Bit.App.Pages
{
nameof(IsText),
nameof(IsFile),
nameof(FileTypeAccessibilityLabel),
nameof(TextTypeAccessibilityLabel)
};
private bool _disableHideEmail;
private bool _sendOptionsPolicyInEffect;
@@ -59,6 +61,7 @@ namespace Bit.App.Pages
_logger = ServiceContainer.Resolve<ILogger>("logger");
TogglePasswordCommand = new Command(TogglePassword);
ToggleOptionsCommand = new Command(ToggleOptions);
TypeOptions = new List<KeyValuePair<string, SendType>>
{
@@ -89,6 +92,7 @@ namespace Bit.App.Pages
}
public Command TogglePasswordCommand { get; set; }
public Command ToggleOptionsCommand { get; set; }
public string SendId { get; set; }
public int SegmentedButtonHeight { get; set; }
public int SegmentedButtonFontSize { get; set; }
@@ -102,6 +106,7 @@ namespace Bit.App.Pages
public bool DisableHideEmailControl { get; set; }
public bool IsAddFromShare { get; set; }
public string ShareOnSaveText => CopyInsteadOfShareAfterSaving ? AppResources.CopySendLinkOnSave : AppResources.ShareOnSave;
public string OptionsAccessilibityText => ShowOptions ? AppResources.OptionsExpanded : AppResources.OptionsCollapsed;
public List<KeyValuePair<string, SendType>> TypeOptions { get; }
public List<KeyValuePair<string, string>> DeletionTypeOptions { get; }
public List<KeyValuePair<string, string>> ExpirationTypeOptions { get; }
@@ -134,7 +139,11 @@ namespace Bit.App.Pages
public bool ShowOptions
{
get => _showOptions;
set => SetProperty(ref _showOptions, value);
set => SetProperty(ref _showOptions, value,
additionalPropertyNames: new[]
{
nameof(OptionsAccessilibityText)
});
}
public int ExpirationDateTypeSelectedIndex
{
@@ -211,7 +220,8 @@ namespace Bit.App.Pages
set => SetProperty(ref _showPassword, value,
additionalPropertyNames: new[]
{
nameof(ShowPasswordIcon)
nameof(ShowPasswordIcon),
nameof(PasswordVisibilityAccessibilityText)
});
}
public bool DisableHideEmail
@@ -231,6 +241,9 @@ namespace Bit.App.Pages
public bool ShowDeletionCustomPickers => EditMode || DeletionDateTypeSelectedIndex == 6;
public bool ShowExpirationCustomPickers => EditMode || ExpirationDateTypeSelectedIndex == 7;
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public string FileTypeAccessibilityLabel => IsFile ? AppResources.FileTypeIsSelected : AppResources.FileTypeIsNotSelected;
public string TextTypeAccessibilityLabel => IsText ? AppResources.TextTypeIsSelected : AppResources.TextTypeIsNotSelected;
public async Task InitAsync()
{

View File

@@ -105,6 +105,7 @@
Grid.Column="1"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
IsVisible="{Binding UseOTPVerification, Converter={StaticResource inverseBool}}"/>
<Label
Text="{u:I18n ConfirmYourIdentity}"

View File

@@ -109,7 +109,11 @@ namespace Bit.App.Pages
{
get => _showPassword;
set => SetProperty(ref _showPassword, value,
additionalPropertyNames: new string[] { nameof(ShowPasswordIcon) });
additionalPropertyNames: new string[]
{
nameof(ShowPasswordIcon),
nameof(PasswordVisibilityAccessibilityText),
});
}
public bool UseOTPVerification
@@ -139,6 +143,7 @@ namespace Bit.App.Pages
public Command TogglePasswordCommand { get; }
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public void TogglePassword()
{

View File

@@ -167,6 +167,10 @@ namespace Bit.App.Pages
{
await _vm.UpdatePinAsync();
}
else if (item.Name == AppResources.ReportCrashLogs)
{
await _vm.LoggerReportingAsync();
}
else
{
var biometricName = AppResources.Biometrics;

View File

@@ -7,10 +7,10 @@ using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms;
using ZXing.Client.Result;
namespace Bit.App.Pages
{
@@ -29,7 +29,7 @@ namespace Bit.App.Pages
private readonly ILocalizeService _localizeService;
private readonly IKeyConnectorService _keyConnectorService;
private readonly IClipboardService _clipboardService;
private readonly ILogger _loggerService;
private const int CustomVaultTimeoutValue = -100;
private bool _supportsBiometric;
@@ -39,6 +39,7 @@ namespace Bit.App.Pages
private string _vaultTimeoutDisplayValue;
private string _vaultTimeoutActionDisplayValue;
private bool _showChangeMasterPassword;
private bool _reportLoggingEnabled;
private List<KeyValuePair<string, int?>> _vaultTimeouts =
new List<KeyValuePair<string, int?>>
@@ -79,6 +80,7 @@ namespace Bit.App.Pages
_localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService");
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
_loggerService = ServiceContainer.Resolve<ILogger>("logger");
GroupedItems = new ObservableRangeCollection<ISettingsPageListItem>();
PageTitle = AppResources.Settings;
@@ -123,7 +125,7 @@ namespace Bit.App.Pages
_showChangeMasterPassword = IncludeLinksWithSubscriptionInfo() &&
!await _keyConnectorService.GetUsesKeyConnector();
_reportLoggingEnabled = await _loggerService.IsEnabled();
BuildList();
}
@@ -286,6 +288,26 @@ namespace Bit.App.Pages
}
}
public async Task LoggerReportingAsync()
{
var options = new[]
{
CreateSelectableOption(AppResources.Yes, _reportLoggingEnabled),
CreateSelectableOption(AppResources.No, !_reportLoggingEnabled),
};
var selection = await Page.DisplayActionSheet(AppResources.ReportCrashLogsDescription, AppResources.Cancel, null, options);
if (selection == null || selection == AppResources.Cancel)
{
return;
}
await _loggerService.SetEnabled(CompareSelection(selection, AppResources.Yes));
_reportLoggingEnabled = await _loggerService.IsEnabled();
BuildList();
}
public async Task VaultTimeoutActionAsync()
{
var options = _vaultTimeoutActions.Select(o =>
@@ -494,11 +516,19 @@ namespace Bit.App.Pages
toolsItems.Add(new SettingsPageListItem { Name = AppResources.LearnOrg });
toolsItems.Add(new SettingsPageListItem { Name = AppResources.WebVault });
}
var otherItems = new List<SettingsPageListItem>
{
new SettingsPageListItem { Name = AppResources.Options },
new SettingsPageListItem { Name = AppResources.About },
new SettingsPageListItem { Name = AppResources.HelpAndFeedback },
#if !FDROID
new SettingsPageListItem
{
Name = AppResources.ReportCrashLogs,
SubLabel = _reportLoggingEnabled ? AppResources.Enabled : AppResources.Disabled,
},
#endif
new SettingsPageListItem { Name = AppResources.RateTheApp },
new SettingsPageListItem { Name = AppResources.DeleteAccount }
};
@@ -576,5 +606,9 @@ namespace Bit.App.Pages
{
return _vaultTimeouts.FirstOrDefault(o => o.Key == key).Value;
}
private string CreateSelectableOption(string option, bool selected) => selected ? $"✓ {option}" : option;
private bool CompareSelection(string selection, string compareTo) => selection == compareTo || selection == $"✓ {compareTo}";
}
}

View File

@@ -1,4 +1,6 @@
using Bit.App.Effects;
using System;
using System.Threading.Tasks;
using Bit.App.Effects;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.Core.Abstractions;
@@ -10,15 +12,19 @@ namespace Bit.App.Pages
{
public class TabsPage : TabbedPage
{
private readonly IBroadcasterService _broadcasterService;
private readonly IMessagingService _messagingService;
private readonly IKeyConnectorService _keyConnectorService;
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
private NavigationPage _groupingsPage;
private NavigationPage _authenticatorPage;
private NavigationPage _sendGroupingsPage;
private NavigationPage _generatorPage;
public TabsPage(AppOptions appOptions = null, PreviousPageInfo previousPage = null)
{
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
@@ -27,8 +33,16 @@ namespace Bit.App.Pages
Title = AppResources.MyVault,
IconImageSource = "lock.png"
};
Children.Add(_groupingsPage);
_authenticatorPage = new NavigationPage(new AuthenticatorPage(true, null, this))
{
Title = AppResources.Authenticator,
IconImageSource = "info.png"
};
Children.Add(_authenticatorPage);
_sendGroupingsPage = new NavigationPage(new SendGroupingsPage(true, null, null, appOptions))
{
Title = AppResources.Send,
@@ -78,12 +92,26 @@ namespace Bit.App.Pages
protected override async void OnAppearing()
{
base.OnAppearing();
_broadcasterService.Subscribe(nameof(TabsPage), async (message) =>
{
if (message.Command == "syncCompleted")
{
Device.BeginInvokeOnMainThread(async () => await UpdateVaultButtonTitleAsync());
}
});
await UpdateVaultButtonTitleAsync();
if (await _keyConnectorService.UserNeedsMigration())
{
_messagingService.Send("convertAccountToKeyConnector");
}
}
protected override void OnDisappearing()
{
base.OnDisappearing();
_broadcasterService.Unsubscribe(nameof(TabsPage));
}
public void ResetToVaultPage()
{
CurrentPage = _groupingsPage;
@@ -131,5 +159,19 @@ namespace Bit.App.Pages
groupingsPage.HideAccountSwitchingOverlayAsync().FireAndForget();
}
}
private async Task UpdateVaultButtonTitleAsync()
{
try
{
var policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
var isShowingVaultFilter = await policyService.ShouldShowVaultFilterAsync();
_groupingsPage.Title = isShowingVaultFilter ? AppResources.Vaults : AppResources.MyVault;
}
catch (Exception ex)
{
_logger.Value.Exception(ex);
}
}
}
}

View File

@@ -162,6 +162,7 @@
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
IsVisible="{Binding Cipher.ViewPassword}" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"

View File

@@ -249,7 +249,8 @@ namespace Bit.App.Pages
set => SetProperty(ref _showPassword, value,
additionalPropertyNames: new string[]
{
nameof(ShowPasswordIcon)
nameof(ShowPasswordIcon),
nameof(PasswordVisibilityAccessibilityText)
});
}
public bool ShowCardNumber
@@ -298,6 +299,7 @@ namespace Bit.App.Pages
public int TotpColumnSpan => Cipher.ViewPassword ? 1 : 2;
public bool AllowPersonal { get; set; }
public bool PasswordPrompt => Cipher.Reprompt != CipherRepromptType.None;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public void Init()
{

View File

@@ -33,7 +33,9 @@
Text="&#xe5c4;"
VerticalOptions="CenterAndExpand"
Clicked="BackButton_Clicked"
x:Name="_backButton" />
x:Name="_backButton"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n TapToGoBack}"/>
<controls:ExtendedSearchBar
x:Name="_searchBar"
HorizontalOptions="FillAndExpand"

View File

@@ -6,6 +6,7 @@
xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:effects="clr-namespace:Bit.App.Effects"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
x:DataType="pages:GroupingsPageViewModel"
Title="{Binding PageTitle}"
x:Name="_page">
@@ -106,6 +107,30 @@
GroupTemplate="{StaticResource groupTemplate}" />
<StackLayout x:Key="mainLayout" x:Name="_mainLayout">
<StackLayout
IsVisible="{Binding ShowVaultFilter}"
Orientation="Horizontal"
HorizontalOptions="FillAndExpand"
Margin="0,5,0,0">
<Label
Text="{Binding VaultFilterDescription}"
LineBreakMode="TailTruncation"
Margin="10,0"
StyleClass="text-md, text-muted"
VerticalOptions="Center"
HorizontalOptions="StartAndExpand"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Filter}" />
<controls:MiButton
Text="{Binding Source={x:Static core:BitwardenIcons.ViewCellMenu}}"
StyleClass="list-row-button, list-row-button-platform, btn-disabled"
Command="{Binding VaultFilterCommand}"
VerticalOptions="Center"
HorizontalOptions="End"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Filter}" />
</StackLayout>
<StackLayout
VerticalOptions="CenterAndExpand"
Padding="20, 0"

View File

@@ -27,8 +27,8 @@ namespace Bit.App.Pages
private PreviousPageInfo _previousPage;
public GroupingsPage(bool mainPage, CipherType? type = null, string folderId = null,
string collectionId = null, string pageTitle = null, PreviousPageInfo previousPage = null,
bool deleted = false)
string collectionId = null, string pageTitle = null, string vaultFilterSelection = null,
PreviousPageInfo previousPage = null, bool deleted = false)
{
_pageName = string.Concat(nameof(GroupingsPage), "_", DateTime.UtcNow.Ticks);
InitializeComponent();
@@ -52,6 +52,10 @@ namespace Bit.App.Pages
{
_vm.PageTitle = pageTitle;
}
if (vaultFilterSelection != null)
{
_vm.VaultFilterDescription = vaultFilterSelection;
}
if (Device.RuntimePlatform == Device.iOS)
{

View File

@@ -2,6 +2,7 @@
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;
@@ -29,7 +30,10 @@ 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>();
@@ -46,6 +50,8 @@ namespace Bit.App.Pages
private readonly IMessagingService _messagingService;
private readonly IStateService _stateService;
private readonly IPasswordRepromptService _passwordRepromptService;
private readonly IOrganizationService _organizationService;
private readonly IPolicyService _policyService;
private readonly ILogger _logger;
public GroupingsPageViewModel()
@@ -60,10 +66,11 @@ namespace Bit.App.Pages
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
_organizationService = ServiceContainer.Resolve<IOrganizationService>("organizationService");
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
_logger = ServiceContainer.Resolve<ILogger>("logger");
Loading = true;
PageTitle = AppResources.MyVault;
GroupedItems = new ObservableRangeCollection<IGroupingsPageListItem>();
RefreshCommand = new Command(async () =>
{
@@ -71,6 +78,9 @@ 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)
{
@@ -87,8 +97,9 @@ namespace Bit.App.Pages
public bool HasCiphers { get; set; }
public bool HasFolders { get; set; }
public bool HasCollections { get; set; }
public bool ShowNoFolderCiphers => (NoFolderCiphers?.Count ?? int.MaxValue) < NoFolderListSize &&
(!Collections?.Any() ?? true);
public bool ShowNoFolderCipherGroup => NoFolderCiphers != null
&& NoFolderCiphers.Count < NoFolderListSize
&& (Collections is null || !Collections.Any());
public List<CipherView> Ciphers { get; set; }
public List<CipherView> FavoriteCiphers { get; set; }
public List<CipherView> NoFolderCiphers { get; set; }
@@ -142,12 +153,30 @@ 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()
@@ -172,6 +201,17 @@ namespace Bit.App.Pages
return;
}
_organizations = await _organizationService.GetAllAsync();
if (MainPage)
{
ShowVaultFilter = await _policyService.ShouldShowVaultFilterAsync();
if (ShowVaultFilter && _vaultFilterSelection == null)
{
_vaultFilterSelection = AppResources.AllVaults;
}
PageTitle = ShowVaultFilter ? AppResources.Vaults : AppResources.MyVault;
}
_doingLoad = true;
LoadedOnce = true;
ShowNoData = false;
@@ -185,9 +225,9 @@ namespace Bit.App.Pages
try
{
await LoadDataAsync();
if (ShowNoFolderCiphers && (NestedFolders?.Any() ?? false))
if (ShowNoFolderCipherGroup && (NestedFolders?.Any() ?? false))
{
// Remove "No Folder" from folder listing
// Remove "No Folder" folder from folders group
NestedFolders = NestedFolders.GetRange(0, NestedFolders.Count - 1);
}
@@ -262,7 +302,7 @@ namespace Bit.App.Pages
groupedItems.Add(new GroupingsPageListGroup(ciphersListItems, AppResources.Items,
ciphersListItems.Count, uppercaseGroupNames, !MainPage && !groupedItems.Any()));
}
if (ShowNoFolderCiphers)
if (ShowNoFolderCipherGroup)
{
var noFolderCiphersListItems = NoFolderCiphers.Select(
c => new GroupingsPageListItem { Cipher = c }).ToList();
@@ -294,6 +334,12 @@ namespace Bit.App.Pages
items.AddRange(itemGroup);
}
if (Device.RuntimePlatform == Device.iOS)
{
// HACK: [PS-536] Fix to avoid blank list after back navigation on unlocking with previous page info
// because of update to XF v5.0.0.2401
GroupedItems.Clear();
}
GroupedItems.ReplaceRange(items);
}
else
@@ -316,6 +362,12 @@ namespace Bit.App.Pages
if (groupedItems.Any())
{
if (Device.RuntimePlatform == Device.iOS)
{
// HACK: [PS-536] Fix to avoid blank list after back navigation on unlocking with previous page info
// because of update to XF v5.0.0.2401
GroupedItems.Clear();
}
GroupedItems.ReplaceRange(new List<IGroupingsPageListItem> { new GroupingsPageHeaderListItem(groupedItems[0].Name, groupedItems[0].ItemCount) });
GroupedItems.AddRange(items);
}
@@ -342,6 +394,25 @@ namespace Bit.App.Pages
SyncRefreshing = false;
}
public async Task VaultFilterOptionsAsync()
{
var options = new List<string> { AppResources.AllVaults, AppResources.MyVault };
if (_organizations.Any())
{
options.AddRange(_organizations.Select(o => o.Name));
}
var selection = await Page.DisplayActionSheet(AppResources.FilterByVault, AppResources.Cancel, null,
options.ToArray());
if (selection == AppResources.Cancel ||
(_vaultFilterSelection == null && selection == AppResources.AllVaults) ||
(_vaultFilterSelection != null && _vaultFilterSelection == selection))
{
return;
}
VaultFilterDescription = selection;
await LoadAsync();
}
public async Task SelectCipherAsync(CipherView cipher)
{
var page = new ViewPage(cipher.Id);
@@ -368,25 +439,26 @@ namespace Bit.App.Pages
default:
break;
}
var page = new GroupingsPage(false, type, null, null, title);
var page = new GroupingsPage(false, type, null, null, title, _vaultFilterSelection);
await Page.Navigation.PushAsync(page);
}
public async Task SelectFolderAsync(FolderView folder)
{
var page = new GroupingsPage(false, null, folder.Id ?? "none", null, folder.Name);
var page = new GroupingsPage(false, null, folder.Id ?? "none", null, folder.Name, _vaultFilterSelection);
await Page.Navigation.PushAsync(page);
}
public async Task SelectCollectionAsync(Core.Models.View.CollectionView collection)
{
var page = new GroupingsPage(false, null, null, collection.Id, collection.Name);
var page = new GroupingsPage(false, null, null, collection.Id, collection.Name, _vaultFilterSelection);
await Page.Navigation.PushAsync(page);
}
public async Task SelectTrashAsync()
{
var page = new GroupingsPage(false, null, null, null, AppResources.Trash, null, true);
var page = new GroupingsPage(false, null, null, null, AppResources.Trash, _vaultFilterSelection, null,
true);
await Page.Navigation.PushAsync(page);
}
@@ -424,8 +496,8 @@ namespace Bit.App.Pages
private async Task LoadDataAsync()
{
var orgId = await FillAllCiphersAndGetOrgIdIfNeededAsync();
NoDataText = AppResources.NoItems;
_allCiphers = await _cipherService.GetAllDecryptedAsync();
HasCiphers = _allCiphers.Any();
FavoriteCiphers?.Clear();
NoFolderCiphers?.Clear();
@@ -439,12 +511,11 @@ namespace Bit.App.Pages
if (MainPage)
{
Folders = await _folderService.GetAllDecryptedAsync();
NestedFolders = await _folderService.GetAllNestedAsync();
await FillFoldersAndCollectionsAsync(orgId);
NestedFolders = await _folderService.GetAllNestedAsync(Folders);
HasFolders = NestedFolders.Any(f => f.Node?.Id != null);
Collections = await _collectionService.GetAllDecryptedAsync();
NestedCollections = await _collectionService.GetAllNestedAsync(Collections);
HasCollections = NestedCollections.Any();
NestedCollections = Collections != null ? await _collectionService.GetAllNestedAsync(Collections) : null;
HasCollections = NestedCollections?.Any() ?? false;
}
else
{
@@ -564,6 +635,63 @@ 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)
{
var decFolders = await _folderService.GetAllDecryptedAsync();
var decCollections = await _collectionService.GetAllDecryptedAsync();
if (IsVaultFilterMyVault)
{
Folders = BuildFolders(decFolders);
Collections = null;
}
else if (IsVaultFilterOrgVault && !string.IsNullOrWhiteSpace(orgId))
{
Folders = BuildFolders(decFolders);
Collections = decCollections?.Where(c => c.OrganizationId == orgId).ToList();
}
else
{
Folders = decFolders;
Collections = decCollections;
}
}
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 List<FolderView> BuildFolders(List<FolderView> decFolders)
{
var folders = decFolders.Where(f => _allCiphers.Any(c => c.FolderId == f.Id)).ToList();
return folders.Any() ? folders : null;
}
private async void CipherOptionsAsync(CipherView cipher)
{
if ((Page as BaseContentPage).DoOnce())

View File

@@ -41,8 +41,11 @@ namespace Bit.App.Pages
{
var cipher = await _cipherService.GetAsync(CipherId);
var decCipher = await cipher.DecryptAsync();
History.ResetWithRange(decCipher.PasswordHistory ?? new List<PasswordHistoryView>());
ShowNoData = History.Count == 0;
Device.BeginInvokeOnMainThread(() =>
{
History.ResetWithRange(decCipher.PasswordHistory ?? new List<PasswordHistoryView>());
ShowNoData = History.Count == 0;
});
}
private async void CopyAsync(PasswordHistoryView ph)

View File

@@ -145,6 +145,7 @@
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
IsVisible="{Binding Cipher.ViewPassword}" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"

View File

@@ -120,7 +120,8 @@ namespace Bit.App.Pages
set => SetProperty(ref _showPassword, value,
additionalPropertyNames: new string[]
{
nameof(ShowPasswordIcon)
nameof(ShowPasswordIcon),
nameof(PasswordVisibilityAccessibilityText)
});
}
public bool ShowCardNumber
@@ -213,6 +214,7 @@ namespace Bit.App.Pages
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string ShowCardNumberIcon => ShowCardNumber ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string ShowCardCodeIcon => ShowCardCode ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public string TotpCodeFormatted
{
get => _totpCodeFormatted;
@@ -754,12 +756,12 @@ namespace Bit.App.Pages
{
if (IsBooleanType)
{
return _field.Value == "true" ? "" : "";
return _field.Value == "true" ? BitwardenIcons.Square : BitwardenIcons.CheckSquare;
}
else if (IsLinkedType)
{
var i18nKey = _cipher.LinkedFieldI18nKey(Field.LinkedId.GetValueOrDefault());
return " " + _i18nService.T(i18nKey);
return BitwardenIcons.Link + _i18nService.T(i18nKey);
}
else
{

View File

@@ -353,6 +353,12 @@ namespace Bit.App.Resources {
}
}
public static string Authenticator {
get {
return ResourceManager.GetString("Authenticator", resourceCulture);
}
}
public static string Name {
get {
return ResourceManager.GetString("Name", resourceCulture);
@@ -3299,6 +3305,12 @@ namespace Bit.App.Resources {
}
}
public static string Text {
get {
return ResourceManager.GetString("Text", resourceCulture);
}
}
public static string TypeText {
get {
return ResourceManager.GetString("TypeText", resourceCulture);
@@ -3329,6 +3341,30 @@ namespace Bit.App.Resources {
}
}
public static string FileTypeIsSelected {
get {
return ResourceManager.GetString("FileTypeIsSelected", resourceCulture);
}
}
public static string FileTypeIsNotSelected {
get {
return ResourceManager.GetString("FileTypeIsNotSelected", resourceCulture);
}
}
public static string TextTypeIsSelected {
get {
return ResourceManager.GetString("TextTypeIsSelected", resourceCulture);
}
}
public static string TextTypeIsNotSelected {
get {
return ResourceManager.GetString("TextTypeIsNotSelected", resourceCulture);
}
}
public static string DeletionDate {
get {
return ResourceManager.GetString("DeletionDate", resourceCulture);
@@ -3892,5 +3928,101 @@ namespace Bit.App.Resources {
return ResourceManager.GetString("EnterTheVerificationCodeThatWasSentToYourEmail", resourceCulture);
}
}
public static string ReportCrashLogs {
get {
return ResourceManager.GetString("ReportCrashLogs", resourceCulture);
}
}
public static string ReportCrashLogsDescription {
get {
return ResourceManager.GetString("ReportCrashLogsDescription", resourceCulture);
}
}
public static string OptionsExpanded {
get {
return ResourceManager.GetString("OptionsExpanded", resourceCulture);
}
}
public static string OptionsCollapsed {
get {
return ResourceManager.GetString("OptionsCollapsed", resourceCulture);
}
}
public static string UppercaseAtoZ {
get {
return ResourceManager.GetString("UppercaseAtoZ", resourceCulture);
}
}
public static string LowercaseAtoZ {
get {
return ResourceManager.GetString("LowercaseAtoZ", resourceCulture);
}
}
public static string NumbersZeroToNine {
get {
return ResourceManager.GetString("NumbersZeroToNine", resourceCulture);
}
}
public static string SpecialCharacters {
get {
return ResourceManager.GetString("SpecialCharacters", resourceCulture);
}
}
public static string TapToGoBack {
get {
return ResourceManager.GetString("TapToGoBack", resourceCulture);
}
}
public static string PasswordIsVisibleTapToHide {
get {
return ResourceManager.GetString("PasswordIsVisibleTapToHide", resourceCulture);
}
}
public static string PasswordIsNotVisibleTapToShow {
get {
return ResourceManager.GetString("PasswordIsNotVisibleTapToShow", resourceCulture);
}
}
public static string FilterByVault {
get {
return ResourceManager.GetString("FilterByVault", resourceCulture);
}
}
public static string AllVaults {
get {
return ResourceManager.GetString("AllVaults", resourceCulture);
}
}
public static string Vaults {
get {
return ResourceManager.GetString("Vaults", resourceCulture);
}
}
public static string VaultFilterDescription {
get {
return ResourceManager.GetString("VaultFilterDescription", resourceCulture);
}
}
public static string All {
get {
return ResourceManager.GetString("All", resourceCulture);
}
}
}
}

View File

@@ -299,6 +299,10 @@
<value>My Vault</value>
<comment>The title for the vault page.</comment>
</data>
<data name="Authenticator" xml:space="preserve">
<value>Authenticator</value>
<comment>Authenticator TOTP feature</comment>
</data>
<data name="Name" xml:space="preserve">
<value>Name</value>
<comment>Label for an entity name.</comment>
@@ -1861,6 +1865,9 @@
<value>A friendly name to describe this Send.</value>
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
</data>
<data name="Text" xml:space="preserve">
<value>Text</value>
</data>
<data name="TypeText" xml:space="preserve">
<value>Text</value>
</data>
@@ -1877,6 +1884,18 @@
<data name="TypeFileInfo" xml:space="preserve">
<value>The file you want to send.</value>
</data>
<data name="FileTypeIsSelected" xml:space="preserve">
<value>File type is selected.</value>
</data>
<data name="FileTypeIsNotSelected" xml:space="preserve">
<value>File type is not selected, tap to select.</value>
</data>
<data name="TextTypeIsSelected" xml:space="preserve">
<value>Text type is selected.</value>
</data>
<data name="TextTypeIsNotSelected" xml:space="preserve">
<value>Text type is not selected, tap to select.</value>
</data>
<data name="DeletionDate" xml:space="preserve">
<value>Deletion Date</value>
</data>
@@ -2181,4 +2200,52 @@
<data name="EnterTheVerificationCodeThatWasSentToYourEmail" xml:space="preserve">
<value>Enter the verification code that was sent to your email</value>
</data>
<data name="ReportCrashLogs" xml:space="preserve">
<value>Report crash logs</value>
</data>
<data name="ReportCrashLogsDescription" xml:space="preserve">
<value>Help Bitwarden improve app stability by allowing crash reports.</value>
</data>
<data name="OptionsExpanded" xml:space="preserve">
<value>Options are expanded, tap to collapse.</value>
</data>
<data name="OptionsCollapsed" xml:space="preserve">
<value>Options are collapsed, tap to expand.</value>
</data>
<data name="UppercaseAtoZ" xml:space="preserve">
<value>Uppercase (A to Z)</value>
</data>
<data name="LowercaseAtoZ" xml:space="preserve">
<value>Lowercase (A to Z)</value>
</data>
<data name="NumbersZeroToNine" xml:space="preserve">
<value>Numbers (0 to 9)</value>
</data>
<data name="SpecialCharacters" xml:space="preserve">
<value>Special Characters (!@#$%^&amp;*)</value>
</data>
<data name="TapToGoBack" xml:space="preserve">
<value>Tap to go back</value>
</data>
<data name="PasswordIsVisibleTapToHide" xml:space="preserve">
<value>Password is visible, tap to hide.</value>
</data>
<data name="PasswordIsNotVisibleTapToShow" xml:space="preserve">
<value>Password is not visible, tap to show.</value>
</data>
<data name="FilterByVault" xml:space="preserve">
<value>Filter items by vault</value>
</data>
<data name="AllVaults" xml:space="preserve">
<value>All Vaults</value>
</data>
<data name="Vaults" xml:space="preserve">
<value>Vaults</value>
</data>
<data name="VaultFilterDescription" xml:space="preserve">
<value>Vault: {0}</value>
</data>
<data name="All" xml:space="preserve">
<value>All</value>
</data>
</root>

View File

@@ -15,7 +15,7 @@ namespace Bit.Core.Abstractions
Task<Folder> EncryptAsync(FolderView model, SymmetricCryptoKey key = null);
Task<List<Folder>> GetAllAsync();
Task<List<FolderView>> GetAllDecryptedAsync();
Task<List<TreeNode<FolderView>>> GetAllNestedAsync();
Task<List<TreeNode<FolderView>>> GetAllNestedAsync(List<FolderView> folders = null);
Task<Folder> GetAsync(string id);
Task<TreeNode<FolderView>> GetNestedAsync(string id);
Task ReplaceAsync(Dictionary<string, FolderData> folders);

View File

@@ -1,11 +1,29 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
namespace Bit.Core.Abstractions
{
public interface ILogger
{
/// <summary>
/// Place necessary code to initiaze logger
/// </summary>
/// <returns></returns>
Task InitAsync();
/// <summary>
/// Returns if the current logger is enable or disable.
/// </summary>
/// <returns></returns>
Task<bool> IsEnabled();
/// <summary>
/// Changes the state of the current logger. Setting state enabled to false will block logging.
/// </summary>
Task SetEnabled(bool value);
/// <summary>
/// Logs something that is not in itself an exception, e.g. a wrong flow or value that needs to be reported
/// and looked into.

View File

@@ -20,5 +20,6 @@ namespace Bit.Core.Abstractions
string orgId);
Task<bool> PolicyAppliesToUser(PolicyType policyType, Func<Policy, bool> policyFilter = null, string userId = null);
int? GetPolicyInt(Policy policy, string key);
Task<bool> ShouldShowVaultFilterAsync();
}
}

View File

@@ -111,5 +111,6 @@
public const string EyeSlash = "\xe96d";
public const string File = "\xe96e";
public const string Paste = "\xe96f";
public const string ViewCellMenu = "\xe5d3";
}
}

View File

@@ -9,7 +9,7 @@
if (defaultOptions)
{
Length = 14;
Ambiguous = false;
AllowAmbiguousChar = true;
Number = true;
MinNumber = 1;
Uppercase = true;
@@ -27,7 +27,7 @@
}
public int? Length { get; set; }
public bool? Ambiguous { get; set; }
public bool? AllowAmbiguousChar { get; set; }
public bool? Number { get; set; }
public int? MinNumber { get; set; }
public bool? Uppercase { get; set; }
@@ -45,7 +45,7 @@
public void Merge(PasswordGenerationOptions defaults)
{
Length = Length ?? defaults.Length;
Ambiguous = Ambiguous ?? defaults.Ambiguous;
AllowAmbiguousChar = AllowAmbiguousChar ?? defaults.AllowAmbiguousChar;
Number = Number ?? defaults.Number;
MinNumber = MinNumber ?? defaults.MinNumber;
Uppercase = Uppercase ?? defaults.Uppercase;

View File

@@ -4,5 +4,6 @@
{
public string Email { get; set; }
public string MasterPasswordHash { get; set; }
public string DeviceIdentifier { get; set; }
}
}

View File

@@ -346,6 +346,7 @@ namespace Bit.Core.Services
TwoFactorProvidersData = response.TwoFactorResponse.TwoFactorProviders2;
result.TwoFactorProviders = response.TwoFactorResponse.TwoFactorProviders2;
CaptchaToken = response.TwoFactorResponse.CaptchaToken;
await _tokenService.ClearTwoFactorTokenAsync(email);
return result;
}

View File

@@ -107,9 +107,12 @@ namespace Bit.Core.Services
return _decryptedFolderCache;
}
public async Task<List<TreeNode<FolderView>>> GetAllNestedAsync()
public async Task<List<TreeNode<FolderView>>> GetAllNestedAsync(List<FolderView> folders = null)
{
var folders = await GetAllDecryptedAsync();
if (folders == null)
{
folders = await GetAllDecryptedAsync();
}
var nodes = new List<TreeNode<FolderView>>();
foreach (var f in folders)
{

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
namespace Bit.Core.Services
@@ -45,6 +46,12 @@ namespace Bit.Core.Services
}
public void Exception(Exception ex) => Debug.WriteLine(ex);
public Task InitAsync() => Task.CompletedTask;
public Task<bool> IsEnabled() => Task.FromResult(true);
public Task SetEnabled(bool value) => Task.CompletedTask;
}
}
#endif

View File

@@ -5,13 +5,24 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Microsoft.AppCenter;
using Microsoft.AppCenter.Crashes;
using Newtonsoft.Json;
namespace Bit.Core.Services
{
public class Logger : ILogger
{
private const string iOSAppSecret = "51f96ae5-68ba-45f6-99a1-8ad9f63046c3";
private const string DroidAppSecret = "d3834185-b4a6-4347-9047-b86c65293d42";
private string _userId;
private string _appId;
private bool _isInitialised = false;
static ILogger _instance;
public static ILogger Instance
{
@@ -29,6 +40,60 @@ namespace Bit.Core.Services
{
}
public string Description
{
get
{
return JsonConvert.SerializeObject(new
{
AppId = _appId,
UserId = _userId
}, Formatting.Indented);
}
}
public async Task InitAsync()
{
if (_isInitialised)
{
return;
}
var device = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService").GetDevice();
_userId = await ServiceContainer.Resolve<IStateService>("stateService").GetActiveUserIdAsync();
_appId = await ServiceContainer.Resolve<IAppIdService>("appIdService").GetAppIdAsync();
switch (device)
{
case Enums.DeviceType.Android:
AppCenter.Start(DroidAppSecret, typeof(Crashes));
break;
case Enums.DeviceType.iOS:
AppCenter.Start(iOSAppSecret, typeof(Crashes));
break;
default:
throw new AppCenterException("Cannot start AppCenter. Device type is not configured.");
}
AppCenter.SetUserId(_userId);
Crashes.GetErrorAttachments = (ErrorReport report) =>
{
return new ErrorAttachmentLog[]
{
ErrorAttachmentLog.AttachmentWithText(Description, "crshdesc.txt"),
};
};
_isInitialised = true;
}
public async Task<bool> IsEnabled() => await AppCenter.IsEnabledAsync();
public async Task SetEnabled(bool value) => await AppCenter.SetEnabledAsync(value);
public void Error(string message,
IDictionary<string, string> extraData = null,
[CallerMemberName] string memberName = "",

View File

@@ -1,9 +1,6 @@
using System;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
#if !FDROID
using Microsoft.AppCenter.Crashes;
#endif
namespace Bit.Core.Services
{
@@ -25,8 +22,9 @@ namespace Bit.Core.Services
#if !FDROID
// just in case the caller throws the exception in a moment where the logger can't be resolved
// we need to track the error as well
Crashes.TrackError(ex);
Microsoft.AppCenter.Crashes.Crashes.TrackError(ex);
#endif
}
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
namespace Bit.Core.Services
@@ -17,5 +18,11 @@ namespace Bit.Core.Services
public void Exception(Exception ex)
{
}
public Task InitAsync() => Task.CompletedTask;
public Task<bool> IsEnabled() => Task.FromResult(false);
public Task SetEnabled(bool value) => Task.CompletedTask;
}
}

View File

@@ -93,7 +93,7 @@ namespace Bit.Core.Services
// Build out other character sets
var allCharSet = string.Empty;
var lowercaseCharSet = LowercaseCharSet;
if (options.Ambiguous.GetValueOrDefault())
if (options.AllowAmbiguousChar.GetValueOrDefault())
{
lowercaseCharSet = string.Concat(lowercaseCharSet, "l");
}
@@ -103,7 +103,7 @@ namespace Bit.Core.Services
}
var uppercaseCharSet = UppercaseCharSet;
if (options.Ambiguous.GetValueOrDefault())
if (options.AllowAmbiguousChar.GetValueOrDefault())
{
uppercaseCharSet = string.Concat(uppercaseCharSet, "IO");
}
@@ -113,7 +113,7 @@ namespace Bit.Core.Services
}
var numberCharSet = NumberCharSet;
if (options.Ambiguous.GetValueOrDefault())
if (options.AllowAmbiguousChar.GetValueOrDefault())
{
numberCharSet = string.Concat(numberCharSet, "01");
}

View File

@@ -193,7 +193,8 @@ namespace Bit.Core.Services
return new Tuple<ResetPasswordPolicyOptions, bool>(resetPasswordPolicyOptions, policy != null);
}
public async Task<bool> PolicyAppliesToUser(PolicyType policyType, Func<Policy, bool> policyFilter, string userId = null)
public async Task<bool> PolicyAppliesToUser(PolicyType policyType, Func<Policy, bool> policyFilter = null,
string userId = null)
{
var policies = await GetAll(policyType, userId);
if (policies == null)
@@ -246,6 +247,13 @@ namespace Bit.Core.Services
return null;
}
public async Task<bool> ShouldShowVaultFilterAsync()
{
var organizations = await _organizationService.GetAllAsync();
var personalOwnershipPolicyApplies = await PolicyAppliesToUser(PolicyType.PersonalOwnership);
return (organizations?.Any() ?? false) && !personalOwnershipPolicyApplies;
}
private bool? GetPolicyBool(Policy policy, string key)
{
if (policy.Data.ContainsKey(key))

View File

@@ -74,28 +74,34 @@ namespace Bit.Core.Services
CancellationToken ct = default, bool deleted = false)
{
ct.ThrowIfCancellationRequested();
var matchedCiphers = new List<CipherView>();
var lowPriorityMatchedCiphers = new List<CipherView>();
query = query.Trim().ToLower();
return ciphers.Where(c =>
foreach (var c in ciphers)
{
ct.ThrowIfCancellationRequested();
if (c.Name?.ToLower().Contains(query) ?? false)
{
return true;
matchedCiphers.Add(c);
}
if (query.Length >= 8 && c.Id.StartsWith(query))
else if (query.Length >= 8 && c.Id.StartsWith(query))
{
return true;
lowPriorityMatchedCiphers.Add(c);
}
if (c.SubTitle?.ToLower().Contains(query) ?? false)
else if (c.SubTitle?.ToLower().Contains(query) ?? false)
{
return true;
lowPriorityMatchedCiphers.Add(c);
}
if (c.Login?.Uri?.ToLower()?.Contains(query) ?? false)
else if (c.Login?.Uri?.ToLower()?.Contains(query) ?? false)
{
return true;
lowPriorityMatchedCiphers.Add(c);
}
return false;
}).ToList();
}
ct.ThrowIfCancellationRequested();
matchedCiphers.AddRange(lowPriorityMatchedCiphers);
return matchedCiphers;
}
public async Task<List<SendView>> SearchSendsAsync(string query, Func<SendView, bool> filter = null,
@@ -133,25 +139,31 @@ namespace Bit.Core.Services
public List<SendView> SearchSendsBasic(List<SendView> sends, string query, CancellationToken ct = default,
bool deleted = false)
{
var matchedSends = new List<SendView>();
var lowPriorityMatchSends = new List<SendView>();
ct.ThrowIfCancellationRequested();
query = query.Trim().ToLower();
return sends.Where(s =>
foreach (var s in sends)
{
ct.ThrowIfCancellationRequested();
if (s.Name?.ToLower().Contains(query) ?? false)
{
return true;
matchedSends.Add(s);
}
if (s.Text?.Text?.ToLower().Contains(query) ?? false)
else if (s.Text?.Text?.ToLower().Contains(query) ?? false)
{
return true;
lowPriorityMatchSends.Add(s);
}
if (s.File?.FileName?.ToLower()?.Contains(query) ?? false)
else if (s.File?.FileName?.ToLower()?.Contains(query) ?? false)
{
return true;
lowPriorityMatchSends.Add(s);
}
return false;
}).ToList();
}
ct.ThrowIfCancellationRequested();
matchedSends.AddRange(lowPriorityMatchSends);
return matchedSends;
}
}
}

View File

@@ -1372,10 +1372,6 @@ namespace Bit.Core.Services
await SetEncryptedPasswordGenerationHistoryAsync(null, userId);
await SetEncryptedSendsAsync(null, userId);
await SetSettingsAsync(null, userId);
if (!string.IsNullOrWhiteSpace(email))
{
await SetTwoFactorTokenAsync(null, email);
}
if (userInitiated)
{

View File

@@ -20,7 +20,6 @@ namespace Bit.iOS.Autofill
public partial class CredentialProviderViewController : ASCredentialProviderViewController
{
private Context _context;
private bool _initedAppCenter;
private NFCNdefReaderSession _nfcSession = null;
private Core.NFCReaderDelegate _nfcDelegate = null;
@@ -330,11 +329,7 @@ namespace Bit.iOS.Autofill
var messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
ServiceContainer.Init(deviceActionService.DeviceUserAgent,
Bit.Core.Constants.iOSAutoFillClearCiphersCacheKey, Bit.Core.Constants.iOSAllClearCipherCacheKeys);
if (!_initedAppCenter)
{
iOSCoreHelpers.RegisterAppCenter();
_initedAppCenter = true;
}
iOSCoreHelpers.InitLogger();
iOSCoreHelpers.Bootstrap();
var app = new App.App(new AppOptions { IosExtension = true });
ThemeManager.SetTheme(app.Resources);

View File

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

View File

@@ -101,7 +101,7 @@ namespace Bit.iOS.Core.Controllers
MinNumbersCell.Value = options.MinNumber.GetValueOrDefault(1);
MinSpecialCell.Value = options.MinSpecial.GetValueOrDefault(1);
LengthCell.Value = options.Length.GetValueOrDefault(14);
AmbiguousCell.Switch.On = options.Ambiguous.GetValueOrDefault();
AmbiguousCell.Switch.On = !options.AllowAmbiguousChar.GetValueOrDefault();
NumWordsCell.Value = options.NumWords.GetValueOrDefault(3);
WordSeparatorCell.TextField.Text = options.WordSeparator ?? "";
@@ -219,7 +219,7 @@ namespace Bit.iOS.Core.Controllers
Special = SpecialCell.Switch.On,
MinSpecial = MinSpecialCell.Value,
MinNumber = MinNumbersCell.Value,
Ambiguous = AmbiguousCell.Switch.On,
AllowAmbiguousChar = !AmbiguousCell.Switch.On,
NumWords = NumWordsCell.Value,
WordSeparator = WordSeparatorCell.TextField.Text,
Capitalize = CapitalizeCell.Switch.On,

View File

@@ -1,56 +0,0 @@
using Bit.Core.Abstractions;
using System.Threading.Tasks;
using Microsoft.AppCenter;
using Microsoft.AppCenter.Crashes;
using Newtonsoft.Json;
namespace Bit.iOS.Core.Utilities
{
public class AppCenterHelper
{
private const string AppSecret = "51f96ae5-68ba-45f6-99a1-8ad9f63046c3";
private readonly IAppIdService _appIdService;
private readonly IStateService _stateService;
private string _userId;
private string _appId;
public AppCenterHelper(
IAppIdService appIdService,
IStateService stateService)
{
_appIdService = appIdService;
_stateService = stateService;
}
public async Task InitAsync()
{
_userId = await _stateService.GetActiveUserIdAsync();
_appId = await _appIdService.GetAppIdAsync();
AppCenter.Start(AppSecret, typeof(Crashes));
AppCenter.SetUserId(_userId);
Crashes.GetErrorAttachments = (ErrorReport report) =>
{
return new ErrorAttachmentLog[]
{
ErrorAttachmentLog.AttachmentWithText(Description, "crshdesc.txt"),
};
};
}
public string Description
{
get
{
return JsonConvert.SerializeObject(new
{
AppId = _appId,
UserId = _userId
}, Formatting.Indented);
}
}
}
}

View File

@@ -25,14 +25,9 @@ namespace Bit.iOS.Core.Utilities
public static string AppGroupId = "group.com.8bit.bitwarden";
public static string AccessGroup = "LTZ2PFU5D6.com.8bit.bitwarden";
public static void RegisterAppCenter()
public static void InitLogger()
{
#if !DEBUG
var appCenterHelper = new AppCenterHelper(
ServiceContainer.Resolve<IAppIdService>("appIdService"),
ServiceContainer.Resolve<IStateService>("stateService"));
var appCenterTask = appCenterHelper.InitAsync();
#endif
ServiceContainer.Resolve<ILogger>("logger").InitAsync();
}
public static void RegisterLocalServices()

View File

@@ -172,7 +172,6 @@
<Compile Include="Services\DeviceActionService.cs" />
<Compile Include="Utilities\ASHelpers.cs" />
<Compile Include="Utilities\Dialogs.cs" />
<Compile Include="Utilities\AppCenterHelper.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Services\CryptoPrimitiveService.cs" />
<Compile Include="Services\KeyChainStorageService.cs" />

View File

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

View File

@@ -26,7 +26,6 @@ namespace Bit.iOS.Extension
public partial class LoadingViewController : ExtendedUIViewController
{
private Context _context = new Context();
private bool _initedAppCenter;
private NFCNdefReaderSession _nfcSession = null;
private Core.NFCReaderDelegate _nfcDelegate = null;
@@ -408,11 +407,7 @@ namespace Bit.iOS.Extension
var messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
ServiceContainer.Init(deviceActionService.DeviceUserAgent,
Bit.Core.Constants.iOSExtensionClearCiphersCacheKey, Bit.Core.Constants.iOSAllClearCipherCacheKeys);
if (!_initedAppCenter)
{
iOSCoreHelpers.RegisterAppCenter();
_initedAppCenter = true;
}
iOSCoreHelpers.InitLogger();
iOSCoreHelpers.Bootstrap();
var app = new App.App(new AppOptions { IosExtension = true });
ThemeManager.SetTheme(app.Resources);

View File

@@ -9,6 +9,8 @@ using MobileCoreServices;
using Bit.iOS.Core.Controllers;
using Bit.App.Resources;
using Bit.iOS.Core.Views;
using Bit.App.Abstractions;
using Bit.Core.Utilities;
namespace Bit.iOS.Extension
{
@@ -18,10 +20,12 @@ namespace Bit.iOS.Extension
: base(handle)
{
DismissModalAction = Cancel;
PasswordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
}
public Context Context { get; set; }
public LoadingViewController LoadingController { get; set; }
public IPasswordRepromptService PasswordRepromptService { get; private set; }
public async override void ViewDidLoad()
{
@@ -104,7 +108,7 @@ namespace Bit.iOS.Extension
_controller = controller;
}
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
public async override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
tableView.DeselectRow(indexPath, true);
tableView.EndEditing(true);
@@ -122,6 +126,11 @@ namespace Bit.iOS.Extension
return;
}
if (item.Reprompt != Bit.Core.Enums.CipherRepromptType.None && !await _controller.PasswordRepromptService.ShowPasswordPromptAsync())
{
return;
}
if (_controller.CanAutoFill() && !string.IsNullOrWhiteSpace(item.Password))
{
string totp = null;

View File

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

View File

@@ -9,6 +9,7 @@ using Bit.App.Pages;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Bit.iOS.Core;
using Bit.iOS.Core.Controllers;
@@ -17,7 +18,6 @@ using Bit.iOS.Core.Views;
using Bit.iOS.ShareExtension.Models;
using CoreNFC;
using Foundation;
using Microsoft.AppCenter.Crashes;
using MobileCoreServices;
using UIKit;
using Xamarin.Forms;
@@ -27,7 +27,6 @@ namespace Bit.iOS.ShareExtension
public partial class LoadingViewController : ExtendedUIViewController
{
private Context _context = new Context();
private bool _initedAppCenter;
private NFCNdefReaderSession _nfcSession = null;
private Core.NFCReaderDelegate _nfcDelegate = null;
@@ -99,7 +98,7 @@ namespace Bit.iOS.ShareExtension
}
catch (Exception ex)
{
Crashes.TrackError(ex);
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
}
@@ -216,11 +215,7 @@ namespace Bit.iOS.ShareExtension
var messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
ServiceContainer.Init(_deviceActionService.Value.DeviceUserAgent,
Bit.Core.Constants.iOSShareExtensionClearCiphersCacheKey, Bit.Core.Constants.iOSAllClearCipherCacheKeys);
if (!_initedAppCenter)
{
iOSCoreHelpers.RegisterAppCenter();
_initedAppCenter = true;
}
iOSCoreHelpers.InitLogger();
iOSCoreHelpers.Bootstrap();
var app = new App.App(new AppOptions { IosExtension = true });

View File

@@ -294,7 +294,7 @@ namespace Bit.iOS
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
ServiceContainer.Init(deviceActionService.DeviceUserAgent, Constants.ClearCiphersCacheKey,
Constants.iOSAllClearCipherCacheKeys);
iOSCoreHelpers.RegisterAppCenter();
iOSCoreHelpers.InitLogger();
_pushHandler = new iOSPushNotificationHandler(
ServiceContainer.Resolve<IPushNotificationListenerService>("pushNotificationListenerService"));
_nfcDelegate = new Core.NFCReaderDelegate((success, message) =>

View File

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