diff --git a/src/iOS.Autofill/ILoginListViewController.cs b/src/iOS.Autofill/ILoginListViewController.cs new file mode 100644 index 000000000..8f515d3f3 --- /dev/null +++ b/src/iOS.Autofill/ILoginListViewController.cs @@ -0,0 +1,10 @@ +using Bit.iOS.Autofill.Models; + +namespace Bit.iOS.Autofill +{ + public interface ILoginListViewController + { + Context Context { get; } + CredentialProviderViewController CPViewController { get; } + } +} diff --git a/src/iOS.Autofill/LoginListViewController.cs b/src/iOS.Autofill/LoginListViewController.cs index 8652e5b1e..473f9e2a9 100644 --- a/src/iOS.Autofill/LoginListViewController.cs +++ b/src/iOS.Autofill/LoginListViewController.cs @@ -1,7 +1,6 @@ using System; using System.Linq; using System.Threading.Tasks; -using Bit.App.Abstractions; using Bit.App.Controls; using Bit.Core.Abstractions; using Bit.Core.Resources.Localization; @@ -19,7 +18,7 @@ using UIKit; namespace Bit.iOS.Autofill { - public partial class LoginListViewController : ExtendedUIViewController + public partial class LoginListViewController : ExtendedUIViewController, ILoginListViewController { internal const string HEADER_SECTION_IDENTIFIER = "headerSectionId"; @@ -30,12 +29,10 @@ namespace Bit.iOS.Autofill : base(handle) { DismissModalAction = Cancel; - PasswordRepromptService = ServiceContainer.Resolve("passwordRepromptService"); } public Context Context { get; set; } public CredentialProviderViewController CPViewController { get; set; } - public IPasswordRepromptService PasswordRepromptService { get; private set; } AccountSwitchingOverlayView _accountSwitchingOverlayView; AccountSwitchingOverlayHelper _accountSwitchingOverlayHelper; @@ -254,20 +251,14 @@ namespace Bit.iOS.Autofill base.Dispose(disposing); } - public class TableSource : ExtensionTableSource + public class TableSource : BaseLoginListTableSource { - private readonly LoginListViewController _controller; - - private readonly LazyResolve _platformUtilsService = new LazyResolve(); - private readonly LazyResolve _passwordRepromptService = new LazyResolve(); - public TableSource(LoginListViewController controller) - : base(controller.Context, controller) + : base(controller) { - _controller = controller; } - private Context Context => (Context)_context; + protected override string LoginAddSegue => SegueConstants.ADD_LOGIN; public override async Task LoadAsync(bool urlFilter = true, string searchFilter = null) { @@ -277,7 +268,7 @@ namespace Bit.iOS.Autofill if (Context.IsCreatingPasskey && !Items.Any()) { - _controller?.OnEmptyList(); + Controller?.OnEmptyList(); } } catch (Exception ex) @@ -316,62 +307,6 @@ namespace Bit.iOS.Autofill return base.RowsInSection(tableview, section); } - - public async override void RowSelected(UITableView tableView, NSIndexPath indexPath) - { - try - { - if (Context.IsCreatingPasskey) - { - await SelectRowForPasskeyCreationAsync(tableView, indexPath); - return; - } - - await AutofillHelpers.TableRowSelectedAsync(tableView, indexPath, this, - _controller.CPViewController, _controller, _controller.PasswordRepromptService, "loginAddSegue"); - } - catch (Exception ex) - { - LoggerHelper.LogEvenIfCantBeResolved(ex); - } - } - - private async Task SelectRowForPasskeyCreationAsync(UITableView tableView, NSIndexPath indexPath) - { - tableView.DeselectRow(indexPath, true); - tableView.EndEditing(true); - - var item = Items.ElementAt(indexPath.Row); - if (item is null) - { - await _platformUtilsService.Value.ShowDialogAsync(AppResources.GenericErrorMessage, AppResources.AnErrorHasOccurred); - return; - } - - if (item.CipherView.Login.HasFido2Credentials - && - !await _platformUtilsService.Value.ShowDialogAsync( - AppResources.ThisItemAlreadyContainsAPasskeyAreYouSureYouWantToOverwriteTheCurrentPasskey, - AppResources.OverwritePasskey, - AppResources.Yes, - AppResources.No)) - { - return; - } - - if (!await _passwordRepromptService.Value.PromptAndCheckPasswordIfNeededAsync(item.Reprompt)) - { - return; - } - - // TODO: Check user verification - - Context.ConfirmNewCredentialTcs.SetResult(new Fido2ConfirmNewCredentialResult - { - CipherId = item.Id, - UserVerified = true - }); - } } } } diff --git a/src/iOS.Autofill/LoginSearchViewController.cs b/src/iOS.Autofill/LoginSearchViewController.cs index dbb2105b4..edd1369e8 100644 --- a/src/iOS.Autofill/LoginSearchViewController.cs +++ b/src/iOS.Autofill/LoginSearchViewController.cs @@ -12,19 +12,17 @@ using Bit.Core.Utilities; namespace Bit.iOS.Autofill { - public partial class LoginSearchViewController : ExtendedUITableViewController + public partial class LoginSearchViewController : ExtendedUITableViewController, ILoginListViewController { public LoginSearchViewController(IntPtr handle) : base(handle) { DismissModalAction = Cancel; - PasswordRepromptService = ServiceContainer.Resolve("passwordRepromptService"); } public Context Context { get; set; } public CredentialProviderViewController CPViewController { get; set; } public bool FromList { get; set; } - public IPasswordRepromptService PasswordRepromptService { get; private set; } public async override void ViewDidLoad() { @@ -61,13 +59,13 @@ namespace Bit.iOS.Autofill } else { - CPViewController.CompleteRequest(); + CPViewController.CancelRequest(AuthenticationServices.ASExtensionErrorCode.UserCanceled); } } partial void AddBarButton_Activated(UIBarButtonItem sender) { - PerformSegue("loginAddFromSearchSegue", this); + PerformSegue(SegueConstants.ADD_LOGIN_FROM_SEARCH, this); } public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender) @@ -93,24 +91,14 @@ namespace Bit.iOS.Autofill }); } - public class TableSource : ExtensionTableSource + public class TableSource : BaseLoginListTableSource { - private Context _context; - private LoginSearchViewController _controller; - public TableSource(LoginSearchViewController controller) - : base(controller.Context, controller) + : base(controller) { - _context = controller.Context; - _controller = controller; } - public async override void RowSelected(UITableView tableView, NSIndexPath indexPath) - { - await AutofillHelpers.TableRowSelectedAsync(tableView, indexPath, this, - _controller.CPViewController, _controller, _controller.PasswordRepromptService, - "loginAddFromSearchSegue"); - } + protected override string LoginAddSegue => SegueConstants.ADD_LOGIN_FROM_SEARCH; } } } diff --git a/src/iOS.Autofill/SegueConstants.cs b/src/iOS.Autofill/SegueConstants.cs index d9c5f95c2..50530fb6a 100644 --- a/src/iOS.Autofill/SegueConstants.cs +++ b/src/iOS.Autofill/SegueConstants.cs @@ -8,5 +8,6 @@ public const string SETUP = "setupSegue"; public const string ADD_LOGIN = "loginAddSegue"; public const string LOGIN_SEARCH_FROM_LIST = "loginSearchFromListSegue"; + public const string ADD_LOGIN_FROM_SEARCH = "loginAddFromSearchSegue"; } } diff --git a/src/iOS.Autofill/Utilities/BaseLoginListTableSource.cs b/src/iOS.Autofill/Utilities/BaseLoginListTableSource.cs new file mode 100644 index 000000000..bfd9804b9 --- /dev/null +++ b/src/iOS.Autofill/Utilities/BaseLoginListTableSource.cs @@ -0,0 +1,91 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Bit.App.Abstractions; +using Bit.Core.Abstractions; +using Bit.Core.Resources.Localization; +using Bit.Core.Services; +using Bit.Core.Utilities; +using Bit.iOS.Autofill.Models; +using Bit.iOS.Core.Views; +using Foundation; +using UIKit; + +namespace Bit.iOS.Autofill.Utilities +{ + public abstract class BaseLoginListTableSource : ExtensionTableSource + where T : UIViewController, ILoginListViewController + { + private IPasswordRepromptService _passwordRepromptService; + private readonly LazyResolve _platformUtilsService = new LazyResolve(); + + public BaseLoginListTableSource(T controller) + : base(controller.Context, controller) + { + _controller = controller; + _passwordRepromptService = ServiceContainer.Resolve(); + } + + protected Context Context => (Context)_context; + protected T Controller => (T)_controller; + + protected abstract string LoginAddSegue { get; } + + public async override void RowSelected(UITableView tableView, NSIndexPath indexPath) + { + try + { + if (Context.IsCreatingPasskey) + { + await SelectRowForPasskeyCreationAsync(tableView, indexPath); + return; + } + + await AutofillHelpers.TableRowSelectedAsync(tableView, indexPath, this, + Controller.CPViewController, Controller, _passwordRepromptService, LoginAddSegue); + } + catch (Exception ex) + { + LoggerHelper.LogEvenIfCantBeResolved(ex); + } + } + + private async Task SelectRowForPasskeyCreationAsync(UITableView tableView, NSIndexPath indexPath) + { + tableView.DeselectRow(indexPath, true); + tableView.EndEditing(true); + + var item = Items.ElementAt(indexPath.Row); + if (item is null) + { + await _platformUtilsService.Value.ShowDialogAsync(AppResources.GenericErrorMessage, AppResources.AnErrorHasOccurred); + return; + } + + if (item.CipherView.Login.HasFido2Credentials + && + !await _platformUtilsService.Value.ShowDialogAsync( + AppResources.ThisItemAlreadyContainsAPasskeyAreYouSureYouWantToOverwriteTheCurrentPasskey, + AppResources.OverwritePasskey, + AppResources.Yes, + AppResources.No)) + { + return; + } + + if (!await _passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(item.Reprompt)) + { + return; + } + + // TODO: Check user verification + + Context.ConfirmNewCredentialTcs.SetResult(new Fido2ConfirmNewCredentialResult + { + CipherId = item.Id, + UserVerified = true + }); + } + } +} + diff --git a/src/iOS.Autofill/iOS.Autofill.csproj b/src/iOS.Autofill/iOS.Autofill.csproj index dbebb72b6..c858537dc 100644 --- a/src/iOS.Autofill/iOS.Autofill.csproj +++ b/src/iOS.Autofill/iOS.Autofill.csproj @@ -88,6 +88,8 @@ + + diff --git a/src/iOS.Core/Views/ExtensionTableSource.cs b/src/iOS.Core/Views/ExtensionTableSource.cs index 01ac2e248..b44c00449 100644 --- a/src/iOS.Core/Views/ExtensionTableSource.cs +++ b/src/iOS.Core/Views/ExtensionTableSource.cs @@ -21,7 +21,7 @@ namespace Bit.iOS.Core.Views protected IStateService _stateService; protected ISearchService _searchService; protected AppExtensionContext _context; - private UIViewController _controller; + protected UIViewController _controller; public ExtensionTableSource(AppExtensionContext context, UIViewController controller) {