2019-08-27 20:55:15 +02:00
|
|
|
|
using AuthenticationServices;
|
2019-09-04 17:52:32 +02:00
|
|
|
|
using Bit.App.Abstractions;
|
2019-06-28 14:21:44 +02:00
|
|
|
|
using Bit.App.Resources;
|
|
|
|
|
using Bit.Core.Abstractions;
|
|
|
|
|
using Bit.Core.Utilities;
|
|
|
|
|
using Bit.iOS.Autofill.Models;
|
|
|
|
|
using Bit.iOS.Core.Utilities;
|
|
|
|
|
using Foundation;
|
|
|
|
|
using System;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using UIKit;
|
|
|
|
|
|
|
|
|
|
namespace Bit.iOS.Autofill
|
|
|
|
|
{
|
|
|
|
|
public partial class CredentialProviderViewController : ASCredentialProviderViewController
|
|
|
|
|
{
|
|
|
|
|
private Context _context;
|
2019-07-03 22:49:47 +02:00
|
|
|
|
private bool _initedHockeyApp;
|
2019-06-28 14:21:44 +02:00
|
|
|
|
|
|
|
|
|
public CredentialProviderViewController(IntPtr handle)
|
|
|
|
|
: base(handle)
|
2019-10-01 03:17:53 +02:00
|
|
|
|
{
|
|
|
|
|
ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
|
|
|
|
}
|
2019-06-28 14:21:44 +02:00
|
|
|
|
|
|
|
|
|
public override void ViewDidLoad()
|
|
|
|
|
{
|
|
|
|
|
InitApp();
|
|
|
|
|
base.ViewDidLoad();
|
2019-06-28 18:30:48 +02:00
|
|
|
|
Logo.Image = new UIImage(ThemeHelpers.LightTheme ? "logo.png" : "logo_white.png");
|
|
|
|
|
View.BackgroundColor = ThemeHelpers.SplashBackgroundColor;
|
2019-06-28 14:21:44 +02:00
|
|
|
|
_context = new Context
|
|
|
|
|
{
|
|
|
|
|
ExtContext = ExtensionContext
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void PrepareCredentialList(ASCredentialServiceIdentifier[] serviceIdentifiers)
|
|
|
|
|
{
|
2019-07-22 14:22:02 +02:00
|
|
|
|
InitAppIfNeeded();
|
2019-06-28 14:21:44 +02:00
|
|
|
|
_context.ServiceIdentifiers = serviceIdentifiers;
|
|
|
|
|
if(serviceIdentifiers.Length > 0)
|
|
|
|
|
{
|
|
|
|
|
var uri = serviceIdentifiers[0].Identifier;
|
|
|
|
|
if(serviceIdentifiers[0].Type == ASCredentialServiceIdentifierType.Domain)
|
|
|
|
|
{
|
|
|
|
|
uri = string.Concat("https://", uri);
|
|
|
|
|
}
|
|
|
|
|
_context.UrlString = uri;
|
|
|
|
|
}
|
|
|
|
|
if(!CheckAuthed())
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if(IsLocked())
|
|
|
|
|
{
|
|
|
|
|
PerformSegue("lockPasswordSegue", this);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if(_context.ServiceIdentifiers == null || _context.ServiceIdentifiers.Length == 0)
|
|
|
|
|
{
|
|
|
|
|
PerformSegue("loginSearchSegue", this);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
PerformSegue("loginListSegue", this);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void ProvideCredentialWithoutUserInteraction(ASPasswordCredentialIdentity credentialIdentity)
|
|
|
|
|
{
|
2019-07-22 14:22:02 +02:00
|
|
|
|
InitAppIfNeeded();
|
2019-06-28 14:21:44 +02:00
|
|
|
|
if(!IsAuthed() || IsLocked())
|
|
|
|
|
{
|
|
|
|
|
var err = new NSError(new NSString("ASExtensionErrorDomain"),
|
|
|
|
|
Convert.ToInt32(ASExtensionErrorCode.UserInteractionRequired), null);
|
|
|
|
|
ExtensionContext.CancelRequest(err);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
_context.CredentialIdentity = credentialIdentity;
|
|
|
|
|
ProvideCredentialAsync().GetAwaiter().GetResult();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void PrepareInterfaceToProvideCredential(ASPasswordCredentialIdentity credentialIdentity)
|
|
|
|
|
{
|
2019-07-22 14:22:02 +02:00
|
|
|
|
InitAppIfNeeded();
|
2019-06-28 14:21:44 +02:00
|
|
|
|
if(!CheckAuthed())
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
_context.CredentialIdentity = credentialIdentity;
|
|
|
|
|
CheckLock(async () => await ProvideCredentialAsync());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void PrepareInterfaceForExtensionConfiguration()
|
|
|
|
|
{
|
2019-07-22 14:22:02 +02:00
|
|
|
|
InitAppIfNeeded();
|
2019-06-28 14:21:44 +02:00
|
|
|
|
_context.Configuring = true;
|
|
|
|
|
if(!CheckAuthed())
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
CheckLock(() => PerformSegue("setupSegue", this));
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-22 21:50:59 +02:00
|
|
|
|
public void CompleteRequest(string id = null, string username = null,
|
|
|
|
|
string password = null, string totp = null)
|
2019-06-28 14:21:44 +02:00
|
|
|
|
{
|
|
|
|
|
if((_context?.Configuring ?? true) && string.IsNullOrWhiteSpace(password))
|
|
|
|
|
{
|
2019-07-23 03:35:05 +02:00
|
|
|
|
ServiceContainer.Reset();
|
2019-06-28 14:21:44 +02:00
|
|
|
|
ExtensionContext?.CompleteExtensionConfigurationRequest();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(_context == null || string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
|
|
|
|
|
{
|
2019-07-23 03:35:05 +02:00
|
|
|
|
ServiceContainer.Reset();
|
2019-06-28 14:21:44 +02:00
|
|
|
|
var err = new NSError(new NSString("ASExtensionErrorDomain"),
|
|
|
|
|
Convert.ToInt32(ASExtensionErrorCode.UserCanceled), null);
|
|
|
|
|
NSRunLoop.Main.BeginInvokeOnMainThread(() => ExtensionContext?.CancelRequest(err));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!string.IsNullOrWhiteSpace(totp))
|
|
|
|
|
{
|
|
|
|
|
UIPasteboard.General.String = totp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var cred = new ASPasswordCredential(username, password);
|
2019-07-23 03:35:05 +02:00
|
|
|
|
NSRunLoop.Main.BeginInvokeOnMainThread(async () =>
|
|
|
|
|
{
|
2019-07-24 16:42:13 +02:00
|
|
|
|
if(!string.IsNullOrWhiteSpace(id))
|
|
|
|
|
{
|
|
|
|
|
var eventService = ServiceContainer.Resolve<IEventService>("eventService");
|
|
|
|
|
await eventService.CollectAsync(Bit.Core.Enums.EventType.Cipher_ClientAutofilled, id);
|
|
|
|
|
}
|
2019-07-23 03:35:05 +02:00
|
|
|
|
ServiceContainer.Reset();
|
|
|
|
|
ExtensionContext?.CompleteRequest(cred, null);
|
|
|
|
|
});
|
2019-06-28 14:21:44 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender)
|
|
|
|
|
{
|
2019-06-28 14:57:08 +02:00
|
|
|
|
if(segue.DestinationViewController is UINavigationController navController)
|
2019-06-28 14:21:44 +02:00
|
|
|
|
{
|
2019-06-28 14:57:08 +02:00
|
|
|
|
if(navController.TopViewController is LoginListViewController listLoginController)
|
2019-06-28 14:21:44 +02:00
|
|
|
|
{
|
|
|
|
|
listLoginController.Context = _context;
|
|
|
|
|
listLoginController.CPViewController = this;
|
|
|
|
|
}
|
2019-06-28 14:57:08 +02:00
|
|
|
|
else if(navController.TopViewController is LoginSearchViewController listSearchController)
|
2019-06-28 14:21:44 +02:00
|
|
|
|
{
|
|
|
|
|
listSearchController.Context = _context;
|
|
|
|
|
listSearchController.CPViewController = this;
|
|
|
|
|
}
|
2019-06-28 14:57:08 +02:00
|
|
|
|
else if(navController.TopViewController is LockPasswordViewController passwordViewController)
|
2019-06-28 14:21:44 +02:00
|
|
|
|
{
|
|
|
|
|
passwordViewController.CPViewController = this;
|
|
|
|
|
}
|
2019-06-28 14:57:08 +02:00
|
|
|
|
else if(navController.TopViewController is SetupViewController setupViewController)
|
2019-06-28 14:21:44 +02:00
|
|
|
|
{
|
|
|
|
|
setupViewController.CPViewController = this;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void DismissLockAndContinue()
|
|
|
|
|
{
|
|
|
|
|
DismissViewController(false, async () =>
|
|
|
|
|
{
|
|
|
|
|
if(_context.CredentialIdentity != null)
|
|
|
|
|
{
|
|
|
|
|
await ProvideCredentialAsync();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if(_context.Configuring)
|
|
|
|
|
{
|
|
|
|
|
PerformSegue("setupSegue", this);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if(_context.ServiceIdentifiers == null || _context.ServiceIdentifiers.Length == 0)
|
|
|
|
|
{
|
|
|
|
|
PerformSegue("loginSearchSegue", this);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
PerformSegue("loginListSegue", this);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task ProvideCredentialAsync()
|
|
|
|
|
{
|
2019-08-27 20:55:15 +02:00
|
|
|
|
var cipherService = ServiceContainer.Resolve<ICipherService>("cipherService", true);
|
|
|
|
|
var cipher = await cipherService?.GetAsync(_context.CredentialIdentity.RecordIdentifier);
|
2019-06-28 14:21:44 +02:00
|
|
|
|
if(cipher == null || cipher.Type != Bit.Core.Enums.CipherType.Login)
|
|
|
|
|
{
|
|
|
|
|
var err = new NSError(new NSString("ASExtensionErrorDomain"),
|
|
|
|
|
Convert.ToInt32(ASExtensionErrorCode.CredentialIdentityNotFound), null);
|
|
|
|
|
ExtensionContext.CancelRequest(err);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
|
|
|
|
var decCipher = await cipher.DecryptAsync();
|
|
|
|
|
string totpCode = null;
|
|
|
|
|
var disableTotpCopy = await storageService.GetAsync<bool?>(Bit.Core.Constants.DisableAutoTotpCopyKey);
|
|
|
|
|
if(!disableTotpCopy.GetValueOrDefault(false))
|
|
|
|
|
{
|
|
|
|
|
var userService = ServiceContainer.Resolve<IUserService>("userService");
|
|
|
|
|
var canAccessPremiumAsync = await userService.CanAccessPremiumAsync();
|
|
|
|
|
if(!string.IsNullOrWhiteSpace(decCipher.Login.Totp) &&
|
|
|
|
|
(canAccessPremiumAsync || cipher.OrganizationUseTotp))
|
|
|
|
|
{
|
|
|
|
|
var totpService = ServiceContainer.Resolve<ITotpService>("totpService");
|
|
|
|
|
totpCode = await totpService.GetCodeAsync(decCipher.Login.Totp);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-22 21:50:59 +02:00
|
|
|
|
CompleteRequest(decCipher.Id, decCipher.Login.Username, decCipher.Login.Password, totpCode);
|
2019-06-28 14:21:44 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void CheckLock(Action notLockedAction)
|
|
|
|
|
{
|
|
|
|
|
if(IsLocked())
|
|
|
|
|
{
|
|
|
|
|
PerformSegue("lockPasswordSegue", this);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
notLockedAction();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool CheckAuthed()
|
|
|
|
|
{
|
|
|
|
|
if(!IsAuthed())
|
|
|
|
|
{
|
|
|
|
|
var alert = Dialogs.CreateAlert(null, AppResources.MustLogInMainAppAutofill, AppResources.Ok, (a) =>
|
|
|
|
|
{
|
|
|
|
|
CompleteRequest();
|
|
|
|
|
});
|
|
|
|
|
PresentViewController(alert, true, null);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool IsLocked()
|
|
|
|
|
{
|
|
|
|
|
var lockService = ServiceContainer.Resolve<ILockService>("lockService");
|
|
|
|
|
return lockService.IsLockedAsync().GetAwaiter().GetResult();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool IsAuthed()
|
|
|
|
|
{
|
|
|
|
|
var userService = ServiceContainer.Resolve<IUserService>("userService");
|
|
|
|
|
return userService.IsAuthenticatedAsync().GetAwaiter().GetResult();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void InitApp()
|
|
|
|
|
{
|
|
|
|
|
if(ServiceContainer.RegisteredServices.Count > 0)
|
|
|
|
|
{
|
2019-07-03 02:45:54 +02:00
|
|
|
|
ServiceContainer.Reset();
|
2019-06-28 14:21:44 +02:00
|
|
|
|
}
|
|
|
|
|
iOSCoreHelpers.RegisterLocalServices();
|
2019-09-04 17:52:32 +02:00
|
|
|
|
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
|
|
|
|
ServiceContainer.Init(deviceActionService.DeviceUserAgent);
|
2019-07-03 22:49:47 +02:00
|
|
|
|
if(!_initedHockeyApp)
|
|
|
|
|
{
|
|
|
|
|
iOSCoreHelpers.RegisterHockeyApp();
|
|
|
|
|
_initedHockeyApp = true;
|
|
|
|
|
}
|
2019-06-28 14:21:44 +02:00
|
|
|
|
iOSCoreHelpers.Bootstrap();
|
2019-07-03 02:45:54 +02:00
|
|
|
|
iOSCoreHelpers.AppearanceAdjustments();
|
2019-06-28 14:21:44 +02:00
|
|
|
|
}
|
2019-07-22 14:22:02 +02:00
|
|
|
|
|
|
|
|
|
private void InitAppIfNeeded()
|
|
|
|
|
{
|
|
|
|
|
if(ServiceContainer.RegisteredServices == null || ServiceContainer.RegisteredServices.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
InitApp();
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-06-28 14:21:44 +02:00
|
|
|
|
}
|
2019-08-27 20:55:15 +02:00
|
|
|
|
}
|