1
0
mirror of https://github.com/bitwarden/mobile.git synced 2024-11-21 11:25:56 +01:00

initial commit of android credential provider service (wip)

This commit is contained in:
mpbw2 2023-12-20 18:04:21 -05:00
parent 7d79b98bf2
commit 6011b63958
No known key found for this signature in database
23 changed files with 475 additions and 1 deletions

View File

@ -15,6 +15,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.ShareExtension", "src\i
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Autofill", "src\iOS.Autofill\iOS.Autofill.csproj", "{83449CC4-1F76-4CFE-92B1-D2E13A62506F}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Autofill", "src\iOS.Autofill\iOS.Autofill.csproj", "{83449CC4-1F76-4CFE-92B1-D2E13A62506F}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.AndroidX.Credentials", "src\Xamarin.AndroidX.Credentials\Xamarin.AndroidX.Credentials.csproj", "{1201A3B6-EF37-420D-A8AD-2C322F6D7B2C}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU

View File

@ -119,6 +119,7 @@
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'"> <ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.18" /> <PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.18" />
<PackageReference Include="Xamarin.AndroidX.Activity.Ktx" Version="1.7.2.1" /> <PackageReference Include="Xamarin.AndroidX.Activity.Ktx" Version="1.7.2.1" />
<ProjectReference Include="..\Xamarin.AndroidX.Credentials\Xamarin.AndroidX.Credentials.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android' AND !$(DefineConstants.Contains(FDROID))"> <ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android' AND !$(DefineConstants.Contains(FDROID))">
<PackageReference Include="Xamarin.GooglePlayServices.SafetyNet" Version="118.0.1.5" /> <PackageReference Include="Xamarin.GooglePlayServices.SafetyNet" Version="118.0.1.5" />

View File

@ -0,0 +1,9 @@
namespace Bit.Droid.Autofill
{
public class CredentialProviderConstants
{
public const string CredentialProviderCipherId = "credentialProviderCipherId";
public const string CredentialDataIntentExtra = "CREDENTIAL_DATA";
public const string CredentialIdIntentExtra = "credId";
}
}

View File

@ -0,0 +1,65 @@
using System.Threading.Tasks;
using Android.App;
using Android.Content.PM;
using Android.OS;
using AndroidX.Credentials.Provider;
using AndroidX.Credentials.WebAuthn;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Bit.App.Droid.Utilities;
namespace Bit.Droid.Autofill
{
[Activity(
NoHistory = true,
LaunchMode = LaunchMode.SingleTop)]
public class CredentialProviderSelectionActivity : MauiAppCompatActivity
{
protected override void OnCreate(Bundle bundle)
{
Intent?.Validate();
base.OnCreate(bundle);
var cipherId = Intent?.GetStringExtra(CredentialProviderConstants.CredentialProviderCipherId);
if (string.IsNullOrEmpty(cipherId))
{
SetResult(Result.Canceled);
Finish();
return;
}
GetCipherAndPerformPasskeyAuthAsync(cipherId).FireAndForget();
}
private async Task GetCipherAndPerformPasskeyAuthAsync(string cipherId)
{
// TODO this is a work in progress
// https://developer.android.com/training/sign-in/credential-provider#passkeys-implement
var getRequest = PendingIntentHandler.RetrieveProviderGetCredentialRequest(Intent);
// var publicKeyRequest = getRequest?.CredentialOptions as PublicKeyCredentialRequestOptions;
var requestInfo = Intent.GetBundleExtra(CredentialProviderConstants.CredentialDataIntentExtra);
var credIdEnc = requestInfo?.GetString(CredentialProviderConstants.CredentialIdIntentExtra);
var cipherService = ServiceContainer.Resolve<ICipherService>();
var cipher = await cipherService.GetAsync(cipherId);
var decCipher = await cipher.DecryptAsync();
var passkey = decCipher.Login.Fido2Credentials.Find(f => f.CredentialId == credIdEnc);
var credId = Convert.FromBase64String(credIdEnc);
// var privateKey = Convert.FromBase64String(passkey.PrivateKey);
// var uid = Convert.FromBase64String(passkey.uid);
var origin = getRequest?.CallingAppInfo.Origin;
var packageName = getRequest?.CallingAppInfo.PackageName;
// --- continue WIP here (save TOTP copy as last step) ---
// Copy TOTP if needed
var autofillHandler = ServiceContainer.Resolve<IAutofillHandler>();
autofillHandler.Autofill(decCipher);
}
}
}

View File

@ -0,0 +1,147 @@
using Android;
using Android.App;
using Android.Content;
using Android.Graphics.Drawables;
using Android.OS;
using Android.Runtime;
using AndroidX.Credentials.Provider;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using AndroidX.Credentials.Exceptions;
using AndroidX.Credentials.WebAuthn;
using Bit.Core.Models.View;
using Resource = Microsoft.Maui.Resource;
namespace Bit.Droid.Autofill
{
[Service(Permission = Manifest.Permission.BindCredentialProviderService, Label = "Bitwarden", Exported = true)]
[IntentFilter(new string[] { "android.service.credentials.CredentialProviderService" })]
[MetaData("android.credentials.provider", Resource = "@xml/provider")]
[Register("com.x8bit.bitwarden.Autofill.CredentialProviderService")]
public class CredentialProviderService : AndroidX.Credentials.Provider.CredentialProviderService
{
private const string GetPasskeyIntentAction = "PACKAGE_NAME.GET_PASSKEY";
private const int UniqueRequestCode = 94556023;
private ICipherService _cipherService;
private IUserVerificationService _userVerificationService;
private IVaultTimeoutService _vaultTimeoutService;
private LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
public override async void OnBeginCreateCredentialRequest(BeginCreateCredentialRequest request,
CancellationSignal cancellationSignal, IOutcomeReceiver callback) => throw new NotImplementedException();
public override async void OnBeginGetCredentialRequest(BeginGetCredentialRequest request,
CancellationSignal cancellationSignal, IOutcomeReceiver callback)
{
try
{
_vaultTimeoutService ??= ServiceContainer.Resolve<IVaultTimeoutService>();
await _vaultTimeoutService.CheckVaultTimeoutAsync();
var locked = await _vaultTimeoutService.IsLockedAsync();
if (!locked)
{
var response = await ProcessGetCredentialsRequestAsync(request);
callback.OnResult(response);
}
// TODO handle auth/unlock account flow
}
catch (GetCredentialException e)
{
_logger.Value.Exception(e);
callback.OnError(e.ErrorMessage ?? "Error getting credentials");
}
catch (Exception e)
{
_logger.Value.Exception(e);
throw;
}
}
private async Task<BeginGetCredentialResponse> ProcessGetCredentialsRequestAsync(
BeginGetCredentialRequest request)
{
IList<CredentialEntry> credentialEntries = null;
foreach (var option in request.BeginGetCredentialOptions)
{
var credentialOption = option as BeginGetPublicKeyCredentialOption;
if (credentialOption != null)
{
credentialEntries ??= new List<CredentialEntry>();
((List<CredentialEntry>)credentialEntries).AddRange(
await PopulatePasskeyDataAsync(request.CallingAppInfo, credentialOption));
}
}
if (credentialEntries == null)
{
return new BeginGetCredentialResponse();
}
return new BeginGetCredentialResponse.Builder()
.SetCredentialEntries(credentialEntries)
.Build();
}
private async Task<List<CredentialEntry>> PopulatePasskeyDataAsync(CallingAppInfo callingAppInfo,
BeginGetPublicKeyCredentialOption option)
{
var packageName = callingAppInfo.PackageName;
var origin = callingAppInfo.Origin;
var signingInfo = callingAppInfo.SigningInfo;
var request = new PublicKeyCredentialRequestOptions(option.RequestJson);
var passkeyEntries = new List<CredentialEntry>();
_cipherService ??= ServiceContainer.Resolve<ICipherService>();
var ciphers = await _cipherService.GetAllDecryptedForUrlAsync(origin);
if (ciphers == null)
{
return passkeyEntries;
}
var passkeyCiphers = ciphers.Where(cipher => cipher.HasFido2Credential).ToList();
if (!passkeyCiphers.Any())
{
return passkeyEntries;
}
foreach (var cipher in passkeyCiphers)
{
var passkeyEntry = GetPasskey(cipher, option);
passkeyEntries.Add(passkeyEntry);
}
return passkeyEntries;
}
private PublicKeyCredentialEntry GetPasskey(CipherView cipher, BeginGetPublicKeyCredentialOption option)
{
var credDataBundle = new Bundle();
credDataBundle.PutString(CredentialProviderConstants.CredentialIdIntentExtra,
cipher.Login.MainFido2Credential.CredentialId);
var intent = new Intent(ApplicationContext, typeof(CredentialProviderSelectionActivity))
.SetAction(GetPasskeyIntentAction).SetPackage(Constants.PACKAGE_NAME);
intent.PutExtra(CredentialProviderConstants.CredentialDataIntentExtra, credDataBundle);
intent.PutExtra(CredentialProviderConstants.CredentialProviderCipherId, cipher.Id);
var pendingIntent = PendingIntent.GetActivity(ApplicationContext, UniqueRequestCode, intent,
PendingIntentFlags.Mutable | PendingIntentFlags.UpdateCurrent);
return new PublicKeyCredentialEntry.Builder(
ApplicationContext,
cipher.Login.Username ?? "No username",
pendingIntent,
option)
.SetDisplayName(cipher.Name)
.SetIcon(Icon.CreateWithResource(ApplicationContext, Resource.Drawable.icon))
.Build();
}
public override void OnClearCredentialStateRequest(ProviderClearCredentialStateRequest request,
CancellationSignal cancellationSignal, IOutcomeReceiver callback) => throw new NotImplementedException();
}
}

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<credential-provider xmlns:android="http://schemas.android.com/apk/res/android">
<capabilities>
<capability name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" />
</capabilities>
</credential-provider>

View File

@ -37,6 +37,23 @@ namespace Bit.Droid.Services
_eventService = eventService; _eventService = eventService;
} }
public bool CredentialProviderServiceEnabled()
{
if (Build.VERSION.SdkInt < BuildVersionCodes.UpsideDownCake)
{
return false;
}
try
{
// TODO - find a way to programmatically check if the credential provider service is enabled
return false;
}
catch
{
return false;
}
}
public bool AutofillServiceEnabled() public bool AutofillServiceEnabled()
{ {
if (Build.VERSION.SdkInt < BuildVersionCodes.O) if (Build.VERSION.SdkInt < BuildVersionCodes.O)
@ -163,7 +180,14 @@ namespace Bit.Droid.Services
return Accessibility.AccessibilityHelpers.OverlayPermitted(); return Accessibility.AccessibilityHelpers.OverlayPermitted();
} }
public void DisableCredentialProviderService()
{
try
{
// TODO - find a way to programmatically disable the provider service, or take the user to the settings page where they can do it
}
catch { }
}
public void DisableAutofillService() public void DisableAutofillService()
{ {

View File

@ -11,6 +11,7 @@ using Android.Text.Method;
using Android.Views; using Android.Views;
using Android.Views.InputMethods; using Android.Views.InputMethods;
using Android.Widget; using Android.Widget;
using AndroidX.Credentials;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.Core.Resources.Localization; using Bit.Core.Resources.Localization;
using Bit.App.Utilities; using Bit.App.Utilities;
@ -490,6 +491,27 @@ namespace Bit.Droid.Services
} }
} }
public void OpenCredentialProviderSettings()
{
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
try
{
var pendingIntent = CredentialManager.Create(activity).CreateSettingsPendingIntent();
pendingIntent.Send();
}
catch (ActivityNotFoundException)
{
var alertBuilder = new AlertDialog.Builder(activity);
alertBuilder.SetMessage(AppResources.BitwardenCredentialProviderGoToSettings);
alertBuilder.SetCancelable(true);
alertBuilder.SetPositiveButton(AppResources.Ok, (sender, args) =>
{
(sender as AlertDialog)?.Cancel();
});
alertBuilder.Create().Show();
}
}
public void OpenAccessibilitySettings() public void OpenAccessibilitySettings()
{ {
try try
@ -548,6 +570,8 @@ namespace Bit.Droid.Services
return true; return true;
} }
public bool SupportsCredentialProviderService() => Build.VERSION.SdkInt >= BuildVersionCodes.UpsideDownCake;
public bool SupportsAutofillServices() => Build.VERSION.SdkInt >= BuildVersionCodes.O; public bool SupportsAutofillServices() => Build.VERSION.SdkInt >= BuildVersionCodes.O;
public bool SupportsInlineAutofill() => Build.VERSION.SdkInt >= BuildVersionCodes.R; public bool SupportsInlineAutofill() => Build.VERSION.SdkInt >= BuildVersionCodes.R;

View File

@ -4,6 +4,7 @@ namespace Bit.Core.Abstractions
{ {
public interface IAutofillHandler public interface IAutofillHandler
{ {
bool CredentialProviderServiceEnabled();
bool AutofillServicesEnabled(); bool AutofillServicesEnabled();
bool SupportsAutofillService(); bool SupportsAutofillService();
void Autofill(CipherView cipher); void Autofill(CipherView cipher);
@ -11,6 +12,7 @@ namespace Bit.Core.Abstractions
bool AutofillAccessibilityServiceRunning(); bool AutofillAccessibilityServiceRunning();
bool AutofillAccessibilityOverlayPermitted(); bool AutofillAccessibilityOverlayPermitted();
bool AutofillServiceEnabled(); bool AutofillServiceEnabled();
void DisableCredentialProviderService();
void DisableAutofillService(); void DisableAutofillService();
} }
} }

View File

@ -28,6 +28,7 @@ namespace Bit.App.Abstractions
bool SupportsNfc(); bool SupportsNfc();
bool SupportsCamera(); bool SupportsCamera();
bool SupportsFido2(); bool SupportsFido2();
bool SupportsCredentialProviderService();
bool SupportsAutofillServices(); bool SupportsAutofillServices();
bool SupportsInlineAutofill(); bool SupportsInlineAutofill();
bool SupportsDrawOver(); bool SupportsDrawOver();
@ -36,6 +37,7 @@ namespace Bit.App.Abstractions
void RateApp(); void RateApp();
void OpenAccessibilitySettings(); void OpenAccessibilitySettings();
void OpenAccessibilityOverlayPermissionSettings(); void OpenAccessibilityOverlayPermissionSettings();
void OpenCredentialProviderSettings();
void OpenAutofillSettings(); void OpenAutofillSettings();
long GetActiveTime(); long GetActiveTime();
void CloseMainApp(); void CloseMainApp();

View File

@ -49,6 +49,7 @@
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'"> <ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.18" /> <PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.18" />
<PackageReference Include="Xamarin.AndroidX.Activity.Ktx" Version="1.7.2.1" /> <PackageReference Include="Xamarin.AndroidX.Activity.Ktx" Version="1.7.2.1" />
<ProjectReference Include="..\Xamarin.AndroidX.Credentials\Xamarin.AndroidX.Credentials.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android' AND !$(DefineConstants.Contains(FDROID))"> <ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android' AND !$(DefineConstants.Contains(FDROID))">
<PackageReference Include="Xamarin.GooglePlayServices.SafetyNet" Version="118.0.1.5" /> <PackageReference Include="Xamarin.GooglePlayServices.SafetyNet" Version="118.0.1.5" />

View File

@ -19,6 +19,15 @@
Text="{u:I18n Autofill}" Text="{u:I18n Autofill}"
StyleClass="settings-header" /> StyleClass="settings-header" />
<controls:SwitchItemView
Title="{u:I18n CredentialProviderService}"
Subtitle="{u:I18n CredentialProviderServiceExplanationLong}"
IsVisible="{Binding SupportsCredentialProviderService}"
IsToggled="{Binding UseCredentialProviderService}"
AutomationId="CredentialProviderServiceSwitch"
StyleClass="settings-item-view"
HorizontalOptions="FillAndExpand" />
<controls:SwitchItemView <controls:SwitchItemView
Title="{u:I18n AutofillServices}" Title="{u:I18n AutofillServices}"
Subtitle="{u:I18n AutofillServicesExplanationLong}" Subtitle="{u:I18n AutofillServicesExplanationLong}"

View File

@ -6,12 +6,27 @@ namespace Bit.App.Pages
{ {
public partial class AutofillSettingsPageViewModel public partial class AutofillSettingsPageViewModel
{ {
private bool _useCredentialProviderService;
private bool _useAutofillServices; private bool _useAutofillServices;
private bool _useInlineAutofill; private bool _useInlineAutofill;
private bool _useAccessibility; private bool _useAccessibility;
private bool _useDrawOver; private bool _useDrawOver;
private bool _askToAddLogin; private bool _askToAddLogin;
public bool SupportsCredentialProviderService => DeviceInfo.Platform == DevicePlatform.Android && _deviceActionService.SupportsCredentialProviderService();
public bool UseCredentialProviderService
{
get => _useCredentialProviderService;
set
{
if (SetProperty(ref _useCredentialProviderService, value))
{
((ICommand)ToggleUseCredentialProviderServiceCommand).Execute(null);
}
}
}
public bool SupportsAndroidAutofillServices => DeviceInfo.Platform == DevicePlatform.Android && _deviceActionService.SupportsAutofillServices(); public bool SupportsAndroidAutofillServices => DeviceInfo.Platform == DevicePlatform.Android && _deviceActionService.SupportsAutofillServices();
public bool UseAutofillServices public bool UseAutofillServices
@ -84,6 +99,7 @@ namespace Bit.App.Pages
} }
} }
public AsyncRelayCommand ToggleUseCredentialProviderServiceCommand { get; private set; }
public AsyncRelayCommand ToggleUseAutofillServicesCommand { get; private set; } public AsyncRelayCommand ToggleUseAutofillServicesCommand { get; private set; }
public AsyncRelayCommand ToggleUseInlineAutofillCommand { get; private set; } public AsyncRelayCommand ToggleUseInlineAutofillCommand { get; private set; }
public AsyncRelayCommand ToggleUseAccessibilityCommand { get; private set; } public AsyncRelayCommand ToggleUseAccessibilityCommand { get; private set; }
@ -93,6 +109,7 @@ namespace Bit.App.Pages
private void InitAndroidCommands() private void InitAndroidCommands()
{ {
ToggleUseCredentialProviderServiceCommand = CreateDefaultAsyncRelayCommand(() => MainThread.InvokeOnMainThreadAsync(() => ToggleUseCredentialProviderService()), () => _inited, allowsMultipleExecutions: false);
ToggleUseAutofillServicesCommand = CreateDefaultAsyncRelayCommand(() => MainThread.InvokeOnMainThreadAsync(() => ToggleUseAutofillServices()), () => _inited, allowsMultipleExecutions: false); ToggleUseAutofillServicesCommand = CreateDefaultAsyncRelayCommand(() => MainThread.InvokeOnMainThreadAsync(() => ToggleUseAutofillServices()), () => _inited, allowsMultipleExecutions: false);
ToggleUseInlineAutofillCommand = CreateDefaultAsyncRelayCommand(() => MainThread.InvokeOnMainThreadAsync(() => ToggleUseInlineAutofillEnabledAsync()), () => _inited, allowsMultipleExecutions: false); ToggleUseInlineAutofillCommand = CreateDefaultAsyncRelayCommand(() => MainThread.InvokeOnMainThreadAsync(() => ToggleUseInlineAutofillEnabledAsync()), () => _inited, allowsMultipleExecutions: false);
ToggleUseAccessibilityCommand = CreateDefaultAsyncRelayCommand(ToggleUseAccessibilityAsync, () => _inited, allowsMultipleExecutions: false); ToggleUseAccessibilityCommand = CreateDefaultAsyncRelayCommand(ToggleUseAccessibilityAsync, () => _inited, allowsMultipleExecutions: false);
@ -115,6 +132,9 @@ namespace Bit.App.Pages
private async Task UpdateAndroidAutofillSettingsAsync() private async Task UpdateAndroidAutofillSettingsAsync()
{ {
// TODO - uncomment once _autofillHandler.CredentialProviderServiceEnabled() returns a real value
// _useCredentialProviderService =
// SupportsCredentialProviderService && _autofillHandler.CredentialProviderServiceEnabled();
_useAutofillServices = _useAutofillServices =
_autofillHandler.SupportsAutofillService() && _autofillHandler.AutofillServiceEnabled(); _autofillHandler.SupportsAutofillService() && _autofillHandler.AutofillServiceEnabled();
_useAccessibility = _autofillHandler.AutofillAccessibilityServiceRunning(); _useAccessibility = _autofillHandler.AutofillAccessibilityServiceRunning();
@ -123,6 +143,7 @@ namespace Bit.App.Pages
await MainThread.InvokeOnMainThreadAsync(() => await MainThread.InvokeOnMainThreadAsync(() =>
{ {
TriggerPropertyChanged(nameof(UseCredentialProviderService));
TriggerPropertyChanged(nameof(UseAutofillServices)); TriggerPropertyChanged(nameof(UseAutofillServices));
TriggerPropertyChanged(nameof(UseAccessibility)); TriggerPropertyChanged(nameof(UseAccessibility));
TriggerPropertyChanged(nameof(UseDrawOver)); TriggerPropertyChanged(nameof(UseDrawOver));
@ -130,6 +151,18 @@ namespace Bit.App.Pages
}); });
} }
private void ToggleUseCredentialProviderService()
{
if (UseCredentialProviderService)
{
_deviceActionService.OpenCredentialProviderSettings();
}
else
{
_autofillHandler.DisableCredentialProviderService();
}
}
private void ToggleUseAutofillServices() private void ToggleUseAutofillServices()
{ {
if (UseAutofillServices) if (UseAutofillServices)

View File

@ -166,6 +166,15 @@ namespace Bit.Core.Resources.Localization {
} }
} }
/// <summary>
/// Looks up a localized string similar to Credential Provider service
/// </summary>
public static string CredentialProviderService {
get {
return ResourceManager.GetString("CredentialProviderService", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Bitwarden needs attention - See &quot;Auto-fill Accessibility Service&quot; from Bitwarden settings. /// Looks up a localized string similar to Bitwarden needs attention - See &quot;Auto-fill Accessibility Service&quot; from Bitwarden settings.
/// </summary> /// </summary>
@ -7667,6 +7676,15 @@ namespace Bit.Core.Resources.Localization {
} }
} }
/// <summary>
/// Looks up a localized string similar to We were unable to automatically open the Android credential provider settings menu for you. You can navigate to the credential provider settings menu manually from Android Settings &gt; System &gt; Passwords &amp; accounts &gt; Passwords, passkeys and data services.
/// </summary>
public static string BitwardenCredentialProviderGoToSettings {
get {
return ResourceManager.GetString("BitwardenCredentialProviderGoToSettings", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Word separator. /// Looks up a localized string similar to Word separator.
/// </summary> /// </summary>
@ -7685,6 +7703,15 @@ namespace Bit.Core.Resources.Localization {
} }
} }
/// <summary>
/// Looks up a localized string similar to The Android Credential Provider is used for managing passkeys for use with websites and other apps on your device.
/// </summary>
public static string CredentialProviderServiceExplanationLong {
get {
return ResourceManager.GetString("CredentialProviderServiceExplanationLong", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to {0} hours and one minute. /// Looks up a localized string similar to {0} hours and one minute.
/// </summary> /// </summary>

View File

@ -1191,6 +1191,9 @@ Scanning will happen automatically.</value>
<data name="WindowsHello" xml:space="preserve"> <data name="WindowsHello" xml:space="preserve">
<value>Windows Hello</value> <value>Windows Hello</value>
</data> </data>
<data name="BitwardenCredentialProviderGoToSettings" xml:space="preserve">
<value>We were unable to automatically open the Android credential provider settings menu for you. You can navigate to the credential provider settings menu manually from Android Settings &gt; System &gt; Passwords &amp; accounts &gt; Passwords, passkeys and data services.</value>
</data>
<data name="BitwardenAutofillGoToSettings" xml:space="preserve"> <data name="BitwardenAutofillGoToSettings" xml:space="preserve">
<value>We were unable to automatically open the Android autofill settings menu for you. You can navigate to the autofill settings menu manually from Android Settings &gt; System &gt; Languages and input &gt; Advanced &gt; Autofill service.</value> <value>We were unable to automatically open the Android autofill settings menu for you. You can navigate to the autofill settings menu manually from Android Settings &gt; System &gt; Languages and input &gt; Advanced &gt; Autofill service.</value>
</data> </data>
@ -1816,6 +1819,9 @@ Scanning will happen automatically.</value>
<data name="AccessibilityDrawOverPermissionAlert" xml:space="preserve"> <data name="AccessibilityDrawOverPermissionAlert" xml:space="preserve">
<value>Bitwarden needs attention - Turn on "Draw-Over" in "Auto-fill Services" from Bitwarden Settings</value> <value>Bitwarden needs attention - Turn on "Draw-Over" in "Auto-fill Services" from Bitwarden Settings</value>
</data> </data>
<data name="CredentialProviderService" xml:space="preserve">
<value>Credential Provider service</value>
</data>
<data name="AutofillServices" xml:space="preserve"> <data name="AutofillServices" xml:space="preserve">
<value>Auto-fill services</value> <value>Auto-fill services</value>
</data> </data>
@ -2799,6 +2805,9 @@ Do you want to switch to this account?</value>
<data name="XHours" xml:space="preserve"> <data name="XHours" xml:space="preserve">
<value>{0} hours</value> <value>{0} hours</value>
</data> </data>
<data name="CredentialProviderServiceExplanationLong" xml:space="preserve">
<value>The Android Credential Provider is used for managing passkeys for use with websites and other apps on your device.</value>
</data>
<data name="AutofillServicesExplanationLong" xml:space="preserve"> <data name="AutofillServicesExplanationLong" xml:space="preserve">
<value>The Android Autofill Framework is used to assist in filling login information into other apps on your device.</value> <value>The Android Autofill Framework is used to assist in filling login information into other apps on your device.</value>
</data> </data>

View File

@ -0,0 +1,48 @@
Additions allow you to add arbitrary C# to the generated classes
before they are compiled. This can be helpful for providing convenience
methods or adding pure C# classes.
== Adding Methods to Generated Classes ==
Let's say the library being bound has a Rectangle class with a constructor
that takes an x and y position, and a width and length size. It will look like
this:
public partial class Rectangle
{
public Rectangle (int x, int y, int width, int height)
{
// JNI bindings
}
}
Imagine we want to add a constructor to this class that takes a Point and
Size structure instead of 4 ints. We can add a new file called Rectangle.cs
with a partial class containing our new method:
public partial class Rectangle
{
public Rectangle (Point location, Size size) :
this (location.X, location.Y, size.Width, size.Height)
{
}
}
At compile time, the additions class will be added to the generated class
and the final assembly will a Rectangle class with both constructors.
== Adding C# Classes ==
Another thing that can be done is adding fully C# managed classes to the
generated library. In the above example, let's assume that there isn't a
Point class available in Java or our library. The one we create doesn't need
to interact with Java, so we'll create it like a normal class in C#.
By adding a Point.cs file with this class, it will end up in the binding library:
public class Point
{
public int X { get; set; }
public int Y { get; set; }
}

View File

@ -0,0 +1,15 @@
<enum-field-mappings>
<!--
This example converts the constants Fragment_id, Fragment_name,
and Fragment_tag from android.support.v4.app.FragmentActivity.FragmentTag
to an enum called Android.Support.V4.App.FragmentTagType with values
Id, Name, and Tag.
<mapping jni-class="android/support/v4/app/FragmentActivity$FragmentTag" clr-enum-type="Android.Support.V4.App.FragmentTagType">
<field jni-name="Fragment_name" clr-name="Name" value="0" />
<field jni-name="Fragment_id" clr-name="Id" value="1" />
<field jni-name="Fragment_tag" clr-name="Tag" value="2" />
</mapping>
-->
</enum-field-mappings>

View File

@ -0,0 +1,14 @@
<enum-method-mappings>
<!--
This example changes the Java method:
android.support.v4.app.Fragment.SavedState.writeToParcel (int flags)
to be:
android.support.v4.app.Fragment.SavedState.writeToParcel (Android.OS.ParcelableWriteFlags flags)
when bound in C#.
<mapping jni-class="android/support/v4/app/Fragment.SavedState">
<method jni-name="writeToParcel" parameter="flags" clr-enum-type="Android.OS.ParcelableWriteFlags" />
</mapping>
-->
</enum-method-mappings>

View File

@ -0,0 +1,12 @@
<metadata>
<attr path="/api/package[@name='androidx.credentials']" name="managedName">AndroidX.Credentials</attr>
<attr path="/api/package[@name='androidx.credentials.provider']" name="managedName">AndroidX.Credentials.Provider</attr>
<attr path="/api/package[@name='androidx.credentials.exceptions']" name="managedName">AndroidX.Credentials.Exceptions</attr>
<attr path="/api/package[@name='androidx.credentials.webauthn']" name="managedName">AndroidX.Credentials.WebAuthn</attr>
<!-- fix companions -->
<attr path="/api/package/class[substring(@name,string-length(@name)-9)='.Companion']" name="managedName">CompanionStatic</attr>
<remove-node path="/api/package/class[substring(@name,string-length(@name)-9)='.Companion' and count(method)=0 and count(field)=0]" />
<attr path="/api/package/class[substring(@name,string-length(@name)-7)='.Default']" name="managedName">DefaultStatic</attr>
<remove-node path="/api/package/class[substring(@name,string-length(@name)-7)='.Default' and count(method)=0 and count(field)=0]" />
</metadata>

View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-android</TargetFramework>
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
<!--<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">11.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion>-->
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>XamarinBinding.AndroidX.Credentials</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Xamarin.Kotlin.StdLib" Version="1.9.10.1" />
</ItemGroup>
</Project>

Binary file not shown.

View File

@ -11,9 +11,11 @@ namespace Bit.iOS.Core.Services
{ {
public bool SupportsAutofillService() => false; public bool SupportsAutofillService() => false;
public bool AutofillServiceEnabled() => false; public bool AutofillServiceEnabled() => false;
public void DisableCredentialProviderService() => throw new NotImplementedException();
public void Autofill(CipherView cipher) => throw new NotImplementedException(); public void Autofill(CipherView cipher) => throw new NotImplementedException();
public bool AutofillAccessibilityOverlayPermitted() => false; public bool AutofillAccessibilityOverlayPermitted() => false;
public bool AutofillAccessibilityServiceRunning() => false; public bool AutofillAccessibilityServiceRunning() => false;
public bool CredentialProviderServiceEnabled() => throw new NotImplementedException();
public bool AutofillServicesEnabled() => false; public bool AutofillServicesEnabled() => false;
public void CloseAutofill() => throw new NotImplementedException(); public void CloseAutofill() => throw new NotImplementedException();
public void DisableAutofillService() => throw new NotImplementedException(); public void DisableAutofillService() => throw new NotImplementedException();

View File

@ -301,6 +301,8 @@ namespace Bit.iOS.Core.Services
throw new NotImplementedException(); throw new NotImplementedException();
} }
public void OpenCredentialProviderSettings() => throw new NotImplementedException();
public void OpenAutofillSettings() public void OpenAutofillSettings()
{ {
throw new NotImplementedException(); throw new NotImplementedException();
@ -339,6 +341,8 @@ namespace Bit.iOS.Core.Services
return false; return false;
} }
public bool SupportsCredentialProviderService() => throw new NotImplementedException();
public bool SupportsAutofillServices() => UIDevice.CurrentDevice.CheckSystemVersion(12, 0); public bool SupportsAutofillServices() => UIDevice.CurrentDevice.CheckSystemVersion(12, 0);
public bool SupportsInlineAutofill() => false; public bool SupportsInlineAutofill() => false;
public bool SupportsDrawOver() => false; public bool SupportsDrawOver() => false;