2021-12-10 21:41:36 +01:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using Bit.App.Abstractions;
|
2019-04-10 05:24:03 +02:00
|
|
|
|
using Bit.App.Models;
|
2019-05-16 22:31:58 +02:00
|
|
|
|
using Bit.App.Resources;
|
2019-04-10 16:49:24 +02:00
|
|
|
|
using Bit.Core.Abstractions;
|
2022-02-08 17:43:40 +01:00
|
|
|
|
using Bit.Core.Enums;
|
2019-05-16 22:31:58 +02:00
|
|
|
|
using Plugin.Fingerprint;
|
|
|
|
|
using Plugin.Fingerprint.Abstractions;
|
2019-04-10 05:24:03 +02:00
|
|
|
|
using Xamarin.Essentials;
|
|
|
|
|
using Xamarin.Forms;
|
|
|
|
|
|
|
|
|
|
namespace Bit.App.Services
|
|
|
|
|
{
|
2019-04-10 16:49:24 +02:00
|
|
|
|
public class MobilePlatformUtilsService : IPlatformUtilsService
|
2021-12-10 21:41:36 +01:00
|
|
|
|
{
|
2019-04-10 05:24:03 +02:00
|
|
|
|
private static readonly Random _random = new Random();
|
|
|
|
|
|
|
|
|
|
private const int DialogPromiseExpiration = 600000; // 10 minutes
|
|
|
|
|
|
|
|
|
|
private readonly IDeviceActionService _deviceActionService;
|
2019-04-18 19:15:46 +02:00
|
|
|
|
private readonly IMessagingService _messagingService;
|
|
|
|
|
private readonly IBroadcasterService _broadcasterService;
|
2021-12-10 21:41:36 +01:00
|
|
|
|
|
2019-04-10 05:24:03 +02:00
|
|
|
|
private readonly Dictionary<int, Tuple<TaskCompletionSource<bool>, DateTime>> _showDialogResolves =
|
|
|
|
|
new Dictionary<int, Tuple<TaskCompletionSource<bool>, DateTime>>();
|
|
|
|
|
|
2019-04-18 19:15:46 +02:00
|
|
|
|
public MobilePlatformUtilsService(
|
|
|
|
|
IDeviceActionService deviceActionService,
|
|
|
|
|
IMessagingService messagingService,
|
|
|
|
|
IBroadcasterService broadcasterService)
|
2019-04-10 05:24:03 +02:00
|
|
|
|
{
|
|
|
|
|
_deviceActionService = deviceActionService;
|
2019-04-18 19:15:46 +02:00
|
|
|
|
_messagingService = messagingService;
|
|
|
|
|
_broadcasterService = broadcasterService;
|
2019-04-11 20:28:38 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Init()
|
|
|
|
|
{
|
2019-05-10 19:22:25 +02:00
|
|
|
|
_broadcasterService.Subscribe(nameof(MobilePlatformUtilsService), (message) =>
|
2019-04-18 19:15:46 +02:00
|
|
|
|
{
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (message.Command == "showDialogResolve")
|
2019-04-10 05:24:03 +02:00
|
|
|
|
{
|
2019-04-19 18:33:57 +02:00
|
|
|
|
var details = message.Data as Tuple<int, bool>;
|
|
|
|
|
var dialogId = details.Item1;
|
|
|
|
|
var confirmed = details.Item2;
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (_showDialogResolves.ContainsKey(dialogId))
|
2019-04-19 18:33:57 +02:00
|
|
|
|
{
|
|
|
|
|
var resolveObj = _showDialogResolves[dialogId].Item1;
|
|
|
|
|
resolveObj.TrySetResult(confirmed);
|
|
|
|
|
}
|
2019-04-10 05:24:03 +02:00
|
|
|
|
|
2019-04-19 18:33:57 +02:00
|
|
|
|
// Clean up old tasks
|
|
|
|
|
var deleteIds = new HashSet<int>();
|
2020-03-28 14:16:28 +01:00
|
|
|
|
foreach (var item in _showDialogResolves)
|
2019-04-10 05:24:03 +02:00
|
|
|
|
{
|
2019-04-19 18:33:57 +02:00
|
|
|
|
var age = DateTime.UtcNow - item.Value.Item2;
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (age.TotalMilliseconds > DialogPromiseExpiration)
|
2019-04-19 18:33:57 +02:00
|
|
|
|
{
|
|
|
|
|
deleteIds.Add(item.Key);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-03-28 14:16:28 +01:00
|
|
|
|
foreach (var id in deleteIds)
|
2019-04-19 18:33:57 +02:00
|
|
|
|
{
|
|
|
|
|
_showDialogResolves.Remove(id);
|
2019-04-10 05:24:03 +02:00
|
|
|
|
}
|
2019-04-18 19:15:46 +02:00
|
|
|
|
}
|
|
|
|
|
});
|
2019-04-10 05:24:03 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Core.Enums.DeviceType GetDevice()
|
|
|
|
|
{
|
2019-04-19 15:11:17 +02:00
|
|
|
|
return _deviceActionService.DeviceType;
|
2019-04-10 05:24:03 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string GetDeviceString()
|
|
|
|
|
{
|
|
|
|
|
return DeviceInfo.Model;
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-08 17:43:40 +01:00
|
|
|
|
public ClientType GetClientType()
|
|
|
|
|
{
|
|
|
|
|
return ClientType.Mobile;
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-10 05:24:03 +02:00
|
|
|
|
public bool IsViewOpen()
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void LaunchUri(string uri, Dictionary<string, object> options = null)
|
|
|
|
|
{
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if ((uri.StartsWith("http://") || uri.StartsWith("https://")) &&
|
2019-06-11 17:00:34 +02:00
|
|
|
|
Uri.TryCreate(uri, UriKind.Absolute, out var parsedUri))
|
2019-04-10 05:24:03 +02:00
|
|
|
|
{
|
2019-06-14 14:05:28 +02:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
Browser.OpenAsync(uri, BrowserLaunchMode.External);
|
|
|
|
|
}
|
2020-03-28 14:16:28 +01:00
|
|
|
|
catch (FeatureNotSupportedException) { }
|
2019-04-10 05:24:03 +02:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var launched = false;
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (GetDevice() == Core.Enums.DeviceType.Android && uri.StartsWith("androidapp://"))
|
2019-04-10 05:24:03 +02:00
|
|
|
|
{
|
|
|
|
|
launched = _deviceActionService.LaunchApp(uri);
|
|
|
|
|
}
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (!launched && (options?.ContainsKey("page") ?? false))
|
2019-04-10 05:24:03 +02:00
|
|
|
|
{
|
|
|
|
|
(options["page"] as Page).DisplayAlert(null, "", ""); // TODO
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void SaveFile()
|
|
|
|
|
{
|
|
|
|
|
// TODO
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string GetApplicationVersion()
|
|
|
|
|
{
|
|
|
|
|
return AppInfo.VersionString;
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-18 15:45:31 +02:00
|
|
|
|
public bool SupportsDuo()
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-30 18:44:12 +02:00
|
|
|
|
public bool SupportsFido2()
|
2019-04-10 05:24:03 +02:00
|
|
|
|
{
|
2021-08-30 18:44:12 +02:00
|
|
|
|
return _deviceActionService.SupportsFido2();
|
2019-04-10 05:24:03 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void ShowToast(string type, string title, string text, Dictionary<string, object> options = null)
|
|
|
|
|
{
|
|
|
|
|
ShowToast(type, title, new string[] { text }, options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void ShowToast(string type, string title, string[] text, Dictionary<string, object> options = null)
|
|
|
|
|
{
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (text.Length > 0)
|
2019-04-10 05:24:03 +02:00
|
|
|
|
{
|
|
|
|
|
var longDuration = options != null && options.ContainsKey("longDuration") ?
|
|
|
|
|
(bool)options["longDuration"] : false;
|
|
|
|
|
_deviceActionService.Toast(text[0], longDuration);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Task<bool> ShowDialogAsync(string text, string title = null, string confirmText = null,
|
|
|
|
|
string cancelText = null, string type = null)
|
|
|
|
|
{
|
|
|
|
|
var dialogId = 0;
|
2020-03-28 14:16:28 +01:00
|
|
|
|
lock (_random)
|
2019-04-10 05:24:03 +02:00
|
|
|
|
{
|
|
|
|
|
dialogId = _random.Next(0, int.MaxValue);
|
|
|
|
|
}
|
2019-04-18 19:15:46 +02:00
|
|
|
|
_messagingService.Send("showDialog", new DialogDetails
|
2019-04-10 05:24:03 +02:00
|
|
|
|
{
|
|
|
|
|
Text = text,
|
|
|
|
|
Title = title,
|
|
|
|
|
ConfirmText = confirmText,
|
|
|
|
|
CancelText = cancelText,
|
|
|
|
|
Type = type,
|
|
|
|
|
DialogId = dialogId
|
|
|
|
|
});
|
|
|
|
|
var tcs = new TaskCompletionSource<bool>();
|
|
|
|
|
_showDialogResolves.Add(dialogId, new Tuple<TaskCompletionSource<bool>, DateTime>(tcs, DateTime.UtcNow));
|
|
|
|
|
return tcs.Task;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-21 15:13:54 +02:00
|
|
|
|
public async Task<bool> ShowPasswordDialogAsync(string title, string body, Func<string, Task<bool>> validator)
|
2021-11-24 20:09:39 +01:00
|
|
|
|
{
|
|
|
|
|
return (await ShowPasswordDialogAndGetItAsync(title, body, validator)).valid;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task<(string password, bool valid)> ShowPasswordDialogAndGetItAsync(string title, string body, Func<string, Task<bool>> validator)
|
2021-05-21 15:13:54 +02:00
|
|
|
|
{
|
|
|
|
|
var password = await _deviceActionService.DisplayPromptAync(AppResources.PasswordConfirmation,
|
|
|
|
|
AppResources.PasswordConfirmationDesc, null, AppResources.Submit, AppResources.Cancel, password: true);
|
|
|
|
|
|
2021-06-10 17:57:18 +02:00
|
|
|
|
if (password == null)
|
|
|
|
|
{
|
2021-11-24 20:09:39 +01:00
|
|
|
|
return (password, false);
|
2021-06-10 17:57:18 +02:00
|
|
|
|
}
|
|
|
|
|
|
2021-05-21 15:13:54 +02:00
|
|
|
|
var valid = await validator(password);
|
|
|
|
|
|
|
|
|
|
if (!valid)
|
|
|
|
|
{
|
|
|
|
|
await ShowDialogAsync(AppResources.InvalidMasterPassword, null, AppResources.Ok);
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-24 20:09:39 +01:00
|
|
|
|
return (password, valid);
|
2021-05-21 15:13:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-04-10 05:24:03 +02:00
|
|
|
|
public bool IsDev()
|
|
|
|
|
{
|
|
|
|
|
return Core.Utilities.CoreHelpers.InDebugMode();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool IsSelfHost()
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task<string> ReadFromClipboardAsync(Dictionary<string, object> options = null)
|
|
|
|
|
{
|
|
|
|
|
return await Clipboard.GetTextAsync();
|
|
|
|
|
}
|
2019-05-16 22:31:58 +02:00
|
|
|
|
|
2019-10-23 15:11:48 +02:00
|
|
|
|
public async Task<bool> SupportsBiometricAsync()
|
2019-05-16 22:31:58 +02:00
|
|
|
|
{
|
2020-06-08 14:25:13 +02:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
return await CrossFingerprint.Current.IsAvailableAsync();
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2019-05-16 22:31:58 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-10-23 15:11:48 +02:00
|
|
|
|
public async Task<bool> AuthenticateBiometricAsync(string text = null, string fallbackText = null,
|
2019-05-17 15:42:20 +02:00
|
|
|
|
Action fallback = null)
|
2019-05-16 22:31:58 +02:00
|
|
|
|
{
|
2020-06-08 14:25:13 +02:00
|
|
|
|
try
|
2019-05-16 22:31:58 +02:00
|
|
|
|
{
|
2020-06-08 14:25:13 +02:00
|
|
|
|
if (text == null)
|
2019-05-17 15:42:20 +02:00
|
|
|
|
{
|
2020-06-08 14:25:13 +02:00
|
|
|
|
text = AppResources.BiometricsDirection;
|
|
|
|
|
if (Device.RuntimePlatform == Device.iOS)
|
2019-10-23 15:11:48 +02:00
|
|
|
|
{
|
|
|
|
|
var supportsFace = await _deviceActionService.SupportsFaceBiometricAsync();
|
|
|
|
|
text = supportsFace ? AppResources.FaceIDDirection : AppResources.FingerprintDirection;
|
|
|
|
|
}
|
2019-05-17 15:42:20 +02:00
|
|
|
|
}
|
2020-06-08 14:25:13 +02:00
|
|
|
|
var biometricRequest = new AuthenticationRequestConfiguration(AppResources.Bitwarden, text)
|
|
|
|
|
{
|
|
|
|
|
CancelTitle = AppResources.Cancel,
|
|
|
|
|
FallbackTitle = fallbackText
|
|
|
|
|
};
|
|
|
|
|
var result = await CrossFingerprint.Current.AuthenticateAsync(biometricRequest);
|
|
|
|
|
if (result.Authenticated)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (result.Status == FingerprintAuthenticationResultStatus.FallbackRequested)
|
|
|
|
|
{
|
|
|
|
|
fallback?.Invoke();
|
|
|
|
|
}
|
2019-05-16 22:31:58 +02:00
|
|
|
|
}
|
2020-06-08 14:25:13 +02:00
|
|
|
|
catch { }
|
|
|
|
|
return false;
|
2019-05-16 22:31:58 +02:00
|
|
|
|
}
|
2020-12-14 21:29:30 +01:00
|
|
|
|
|
|
|
|
|
public long GetActiveTime()
|
|
|
|
|
{
|
|
|
|
|
return _deviceActionService.GetActiveTime();
|
|
|
|
|
}
|
2019-04-10 05:24:03 +02:00
|
|
|
|
}
|
|
|
|
|
}
|