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>
This commit is contained in:
André Filipe da Silva Bispo 2022-05-18 17:59:19 +01:00 committed by GitHub
parent bcbc2738ca
commit 22c746543a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 172 additions and 177 deletions

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,7 +145,6 @@
<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" />

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

@ -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" />

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

@ -3892,5 +3892,17 @@ 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);
}
}
}
}

View File

@ -2181,4 +2181,10 @@
<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>
</root>

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

@ -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

@ -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

@ -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

@ -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,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) =>