Compare commits

..

60 Commits

Author SHA1 Message Date
Kyle Spearrin
7117f00480 bump build number 2017-05-03 22:35:45 -04:00
Kyle Spearrin
d4f37343a2 added required NSPhotoLibraryUsageDescription key 2017-05-03 22:25:00 -04:00
Kyle Spearrin
71e15e9cab remove CFBundleExecutable again 2017-05-03 22:01:20 -04:00
Kyle Spearrin
eadf00feba testing add back CFBundleExecutable 2017-05-03 15:37:32 -04:00
Kyle Spearrin
95f28fad2b remove CFBundleExecutable 2017-05-03 15:11:21 -04:00
Kyle Spearrin
9753137a72 version bump 2017-05-03 11:43:56 -04:00
Kyle Spearrin
e4f7436dfb do not animate autofill on mobile
- Animate seems to now cause a JS error when autofilling on iOS, which
stops the password from autofilling. Turn the option off.
2017-05-03 09:53:40 -04:00
Kyle Spearrin
d39211310d make notes taller. autofocus name field on add 2017-05-02 19:41:57 -04:00
Kyle Spearrin
0a6fb3ec0a Comment out test nodes 2017-04-30 17:17:40 -04:00
Kyle Spearrin
5232cf7cec remove beta tag from autofill in tools listing 2017-04-28 12:27:03 -04:00
Kyle Spearrin
6e16ffe05f autofill listing page name for android app fixes 2017-04-28 12:25:29 -04:00
Kyle Spearrin
2d6895aeea android app match fixes 2017-04-28 12:14:53 -04:00
Kyle Spearrin
b5311e1448 moved locked sets to AppSettingsService 2017-04-28 11:34:02 -04:00
Kyle Spearrin
cc63eb383d check that now is > LastActivity Date 2017-04-28 11:19:43 -04:00
Kyle Spearrin
01736ca685 Lock Screen Fixes
- Move settings to AppSettingsService
- Update activity on page disappaearing
- Always check if app is currently locked before updating last activity
date
2017-04-28 11:07:26 -04:00
Kyle Spearrin
be47bb7263 (none) => No folder 2017-04-27 16:46:50 -04:00
Kyle Spearrin
bcb7d88ed7 Double HMAC comparison to prevent timing attacks 2017-04-27 12:14:45 -04:00
Kyle Spearrin
cf58c1b4b5 only fetch keys if there are some orgs 2017-04-26 11:58:52 -04:00
Kyle Spearrin
70c57928e7 Compat. - no header for AesCbc256_B64 cipherstring 2017-04-26 11:28:03 -04:00
Kyle Spearrin
c8219b29c0 encrypted private key and org keys at rest 2017-04-25 16:05:13 -04:00
Kyle Spearrin
15a9f80430 Tools share cell 2017-04-25 14:48:42 -04:00
Kyle Spearrin
0684dfe869 only parse list as URL if dict case fails 2017-04-24 22:22:12 -04:00
Kyle Spearrin
83a89566ac parse url for older extension 2017-04-24 18:05:23 -04:00
Kyle Spearrin
04f486b003 process string for firefox browsers 2017-04-24 17:40:17 -04:00
Kyle Spearrin
f135c92434 add new dependencies to extention container 2017-04-24 16:56:34 -04:00
Kyle Spearrin
78b095d01a new share icon size/color 2017-04-24 16:05:16 -04:00
Kyle Spearrin
1b8bd494e2 disable GA exception reporting 2017-04-24 16:04:54 -04:00
Kyle Spearrin
481925ac78 added share icons to ios project 2017-04-24 15:17:11 -04:00
Kyle Spearrin
4854b2b1c0 share icon on vault listing 2017-04-24 15:00:55 -04:00
Kyle Spearrin
2d7b33459e remove refs to "google" 2017-04-24 14:26:16 -04:00
Kyle Spearrin
27e0c7421b rename CryptoKey to SymmetricCryptoKey 2017-04-22 14:37:01 -04:00
Kyle Spearrin
b26c3d050c sync org keys and refactors 2017-04-21 22:33:09 -04:00
Kyle Spearrin
439370e25a new push notification changes and syncing 2017-04-21 14:57:23 -04:00
Kyle Spearrin
1be4f6e20c add support for rsa oaep sha1 enc type 2017-04-21 13:40:29 -04:00
Kyle Spearrin
2714c7cce9 Added more api uris. lock screen bug in ios ext 2017-04-21 09:12:30 -04:00
Kyle Spearrin
952935de23 copy notes when tapped 2017-04-20 16:07:34 -04:00
Kyle Spearrin
bdb8b5ea39 fix crypto tests 2017-04-20 15:53:17 -04:00
Kyle Spearrin
3ad4e28a2c no inline out 2017-04-20 15:49:25 -04:00
Kyle Spearrin
48d0d068d1 try inline out again 2017-04-20 15:44:51 -04:00
Kyle Spearrin
56e166d61a build engine doesnt like inline out params 2017-04-20 15:38:31 -04:00
Kyle Spearrin
672d753adf update libs 2017-04-20 14:54:39 -04:00
Kyle Spearrin
0d9ba92db4 null check on key retrievals 2017-04-20 14:23:58 -04:00
Kyle Spearrin
8cf25d3602 remove old, unnecessary refs for client handler 2017-04-20 14:23:40 -04:00
Kyle Spearrin
a6bc44dc10 No need for custom handler anymore - xam bug fixed 2017-04-20 14:22:11 -04:00
Kyle Spearrin
408d66ee74 update xamarin forms. empty string section titles
There appears to be a bug regression introduced with the new xamarin
forms that removes headers if there is no title. Hack to fix it for now
is to include a empty string header title.
2017-04-20 14:18:16 -04:00
Kyle Spearrin
b136bb74b8 encrypt with org key if needed 2017-04-20 11:40:39 -04:00
Kyle Spearrin
18b2b6f447 set org keys on login and decrypt org ciphers 2017-04-20 11:23:30 -04:00
Kyle Spearrin
490d1775a2 sync folders & added org id for ciphers/logins 2017-04-20 10:47:14 -04:00
Kyle Spearrin
458de2d2e0 set private key on login 2017-04-20 10:29:18 -04:00
Kyle Spearrin
51ae3fc62f clear keys on logout 2017-04-20 10:20:50 -04:00
Kyle Spearrin
58c5c55d09 extend crypto service for org keys 2017-04-20 10:20:24 -04:00
Kyle Spearrin
4c2bcb9e6b IsNullOrWhiteSpace InitializationVector 2017-04-20 00:10:36 -04:00
Kyle Spearrin
498379bb7e privatekey, rsa decryption, org key management 2017-04-20 00:06:11 -04:00
Kyle Spearrin
e7f3b115a4 refactor for enc type header and cryptokey 2017-04-19 23:16:09 -04:00
Kyle Spearrin
0ebfe85d8e centralize login code into auth service 2017-04-19 22:04:43 -04:00
Kyle Spearrin
8e29a990cb Remove userid from Ga service, not being used 2017-04-19 21:05:03 -04:00
Kyle Spearrin
a960ccd786 IP for desktop from emulator 2017-04-19 20:57:40 -04:00
Kyle Spearrin
6b86e836d7 update target framework 2017-04-19 17:11:06 -04:00
Shan
fb35b9b10a Fixes #8 Disable Google Analytics (#55)
* Opt-out of Google Analytics

* Move OptOut to Other in Settings

* Change OptOut Order and Resource key
2017-03-30 18:22:14 -04:00
Kyle Spearrin
a45773e1ca Update README.md 2017-03-21 18:12:21 -04:00
127 changed files with 2191 additions and 1746 deletions

View File

@@ -1,4 +1,4 @@
[![appveyor build](https://ci.appveyor.com/api/projects/status/github/bitwarden/mobile?branch=master&svg=true)] (https://ci.appveyor.com/project/bitwarden/mobile)
[![appveyor build](https://ci.appveyor.com/api/projects/status/github/bitwarden/mobile?branch=master&svg=true)](https://ci.appveyor.com/project/bitwarden/mobile)
[![Join the chat at https://gitter.im/bitwarden/Lobby](https://badges.gitter.im/bitwarden/Lobby.svg)](https://gitter.im/bitwarden/Lobby)
# bitwarden mobile

View File

@@ -17,7 +17,7 @@
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
<AndroidUseLatestPlatformSdk>True</AndroidUseLatestPlatformSdk>
<TargetFrameworkVersion>v6.0</TargetFrameworkVersion>
<TargetFrameworkVersion>v7.1</TargetFrameworkVersion>
<AndroidSupportedAbis>armeabi,armeabi-v7a,x86</AndroidSupportedAbis>
<AndroidStoreUncompressedFileExtensions />
<MandroidI18n />
@@ -76,12 +76,10 @@
<Private>True</Private>
</Reference>
<Reference Include="Acr.UserDialogs, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Acr.UserDialogs.6.3.3\lib\MonoAndroid10\Acr.UserDialogs.dll</HintPath>
<Private>True</Private>
<HintPath>..\..\packages\Acr.UserDialogs.6.3.10\lib\MonoAndroid10\Acr.UserDialogs.dll</HintPath>
</Reference>
<Reference Include="Acr.UserDialogs.Interface, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Acr.UserDialogs.6.3.3\lib\MonoAndroid10\Acr.UserDialogs.Interface.dll</HintPath>
<Private>True</Private>
<HintPath>..\..\packages\Acr.UserDialogs.6.3.10\lib\MonoAndroid10\Acr.UserDialogs.Interface.dll</HintPath>
</Reference>
<Reference Include="AndHUD, Version=1.2.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\AndHUD.1.2.0\lib\MonoAndroid\AndHUD.dll</HintPath>
@@ -92,32 +90,25 @@
<Private>True</Private>
</Reference>
<Reference Include="FFImageLoading, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xamarin.FFImageLoading.2.2.8\lib\MonoAndroid10\FFImageLoading.dll</HintPath>
<Private>True</Private>
<HintPath>..\..\packages\Xamarin.FFImageLoading.2.2.9\lib\MonoAndroid10\FFImageLoading.dll</HintPath>
</Reference>
<Reference Include="FFImageLoading.Forms, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xamarin.FFImageLoading.Forms.2.2.8\lib\MonoAndroid10\FFImageLoading.Forms.dll</HintPath>
<Private>True</Private>
<HintPath>..\..\packages\Xamarin.FFImageLoading.Forms.2.2.9\lib\MonoAndroid10\FFImageLoading.Forms.dll</HintPath>
</Reference>
<Reference Include="FFImageLoading.Forms.Droid, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xamarin.FFImageLoading.Forms.2.2.8\lib\MonoAndroid10\FFImageLoading.Forms.Droid.dll</HintPath>
<Private>True</Private>
<HintPath>..\..\packages\Xamarin.FFImageLoading.Forms.2.2.9\lib\MonoAndroid10\FFImageLoading.Forms.Droid.dll</HintPath>
</Reference>
<Reference Include="FFImageLoading.Platform, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xamarin.FFImageLoading.2.2.8\lib\MonoAndroid10\FFImageLoading.Platform.dll</HintPath>
<Private>True</Private>
<HintPath>..\..\packages\Xamarin.FFImageLoading.2.2.9\lib\MonoAndroid10\FFImageLoading.Platform.dll</HintPath>
</Reference>
<Reference Include="FormsViewGroup, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xamarin.Forms.2.3.3.180\lib\MonoAndroid10\FormsViewGroup.dll</HintPath>
<Private>True</Private>
<HintPath>..\..\packages\Xamarin.Forms.2.3.4.231\lib\MonoAndroid10\FormsViewGroup.dll</HintPath>
</Reference>
<Reference Include="HockeySDK, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\HockeySDK.Xamarin.4.1.0\lib\MonoAndroid403\HockeySDK.dll</HintPath>
<Private>True</Private>
<HintPath>..\..\packages\HockeySDK.Xamarin.4.1.2\lib\MonoAndroid403\HockeySDK.dll</HintPath>
</Reference>
<Reference Include="HockeySDK.AndroidBindings, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\HockeySDK.Xamarin.4.1.0\lib\MonoAndroid403\HockeySDK.AndroidBindings.dll</HintPath>
<Private>True</Private>
<HintPath>..\..\packages\HockeySDK.Xamarin.4.1.2\lib\MonoAndroid403\HockeySDK.AndroidBindings.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Practices.ServiceLocation, Version=1.3.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\CommonServiceLocator.1.3\lib\portable-net4+sl5+netcore45+wpa81+wp8\Microsoft.Practices.ServiceLocation.dll</HintPath>
@@ -153,13 +144,11 @@
<HintPath>..\..\packages\PInvoke.Windows.Core.0.3.152\lib\portable-net45+win+wpa81+MonoAndroid10+xamarinios10+MonoTouch10\PInvoke.Windows.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Plugin.Connectivity, Version=2.2.12.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xam.Plugin.Connectivity.2.2.12\lib\MonoAndroid10\Plugin.Connectivity.dll</HintPath>
<Private>True</Private>
<Reference Include="Plugin.Connectivity, Version=2.3.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xam.Plugin.Connectivity.2.3.0\lib\MonoAndroid10\Plugin.Connectivity.dll</HintPath>
</Reference>
<Reference Include="Plugin.Connectivity.Abstractions, Version=2.2.12.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xam.Plugin.Connectivity.2.2.12\lib\MonoAndroid10\Plugin.Connectivity.Abstractions.dll</HintPath>
<Private>True</Private>
<Reference Include="Plugin.Connectivity.Abstractions, Version=2.3.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xam.Plugin.Connectivity.2.3.0\lib\MonoAndroid10\Plugin.Connectivity.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Plugin.CurrentActivity, Version=1.0.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Plugin.CurrentActivity.1.0.1\lib\MonoAndroid10\Plugin.CurrentActivity.dll</HintPath>
@@ -173,13 +162,11 @@
<HintPath>..\..\packages\Plugin.Fingerprint.1.2.0\lib\MonoAndroid\Plugin.Fingerprint.Abstractions.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Plugin.Settings, Version=2.5.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xam.Plugins.Settings.2.5.1.0\lib\MonoAndroid10\Plugin.Settings.dll</HintPath>
<Private>True</Private>
<Reference Include="Plugin.Settings, Version=2.5.4.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xam.Plugins.Settings.2.5.4\lib\MonoAndroid10\Plugin.Settings.dll</HintPath>
</Reference>
<Reference Include="Plugin.Settings.Abstractions, Version=2.5.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xam.Plugins.Settings.2.5.1.0\lib\MonoAndroid10\Plugin.Settings.Abstractions.dll</HintPath>
<Private>True</Private>
<Reference Include="Plugin.Settings.Abstractions, Version=2.5.4.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xam.Plugins.Settings.2.5.4\lib\MonoAndroid10\Plugin.Settings.Abstractions.dll</HintPath>
</Reference>
<Reference Include="PushNotification.Plugin, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xam.Plugin.PushNotification.1.2.4\lib\MonoAndroid10\PushNotification.Plugin.dll</HintPath>
@@ -269,20 +256,16 @@
<Private>True</Private>
</Reference>
<Reference Include="Xamarin.Forms.Core, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xamarin.Forms.2.3.3.180\lib\MonoAndroid10\Xamarin.Forms.Core.dll</HintPath>
<Private>True</Private>
<HintPath>..\..\packages\Xamarin.Forms.2.3.4.231\lib\MonoAndroid10\Xamarin.Forms.Core.dll</HintPath>
</Reference>
<Reference Include="Xamarin.Forms.Platform, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xamarin.Forms.2.3.3.180\lib\MonoAndroid10\Xamarin.Forms.Platform.dll</HintPath>
<Private>True</Private>
<HintPath>..\..\packages\Xamarin.Forms.2.3.4.231\lib\MonoAndroid10\Xamarin.Forms.Platform.dll</HintPath>
</Reference>
<Reference Include="Xamarin.Forms.Platform.Android, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xamarin.Forms.2.3.3.180\lib\MonoAndroid10\Xamarin.Forms.Platform.Android.dll</HintPath>
<Private>True</Private>
<HintPath>..\..\packages\Xamarin.Forms.2.3.4.231\lib\MonoAndroid10\Xamarin.Forms.Platform.Android.dll</HintPath>
</Reference>
<Reference Include="Xamarin.Forms.Xaml, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xamarin.Forms.2.3.3.180\lib\MonoAndroid10\Xamarin.Forms.Xaml.dll</HintPath>
<Private>True</Private>
<HintPath>..\..\packages\Xamarin.Forms.2.3.4.231\lib\MonoAndroid10\Xamarin.Forms.Xaml.dll</HintPath>
</Reference>
<Reference Include="Xamarin.GooglePlayServices.Analytics, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xamarin.GooglePlayServices.Analytics.29.0.0.2\lib\MonoAndroid41\Xamarin.GooglePlayServices.Analytics.dll</HintPath>
@@ -321,7 +304,6 @@
<Compile Include="Controls\ExtendedButtonRenderer.cs" />
<Compile Include="Controls\ExtendedTabbedPageRenderer.cs" />
<Compile Include="Controls\ExtendedTableViewRenderer.cs" />
<Compile Include="CustomAndroidClientHandler.cs" />
<Compile Include="HockeyAppCrashManagerListener.cs" />
<Compile Include="AutofillService.cs" />
<Compile Include="Controls\ExtendedEditorRenderer.cs" />
@@ -762,9 +744,6 @@
<ItemGroup>
<AndroidResource Include="Resources\drawable\icon.png" />
</ItemGroup>
<ItemGroup>
<AndroidEnvironment Include="EnvironmentVariables.txt" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\notification_sm.png" />
</ItemGroup>
@@ -846,6 +825,36 @@
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxxhdpi\search.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\share.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\share.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\share.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\share.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxxhdpi\share.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\share_tools.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\share_tools.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\share_tools.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\share_tools.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxxhdpi\share_tools.png" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<Import Project="..\..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\build\Xamarin.Android.Support.Vector.Drawable.targets" Condition="Exists('..\..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\build\Xamarin.Android.Support.Vector.Drawable.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
@@ -854,10 +863,10 @@
</PropertyGroup>
<Error Condition="!Exists('..\..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\build\Xamarin.Android.Support.Vector.Drawable.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\build\Xamarin.Android.Support.Vector.Drawable.targets'))" />
<Error Condition="!Exists('..\..\packages\Xamarin.GooglePlayServices.Basement.29.0.0.2\build\Xamarin.GooglePlayServices.Basement.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Xamarin.GooglePlayServices.Basement.29.0.0.2\build\Xamarin.GooglePlayServices.Basement.targets'))" />
<Error Condition="!Exists('..\..\packages\Xamarin.Forms.2.3.3.180\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Xamarin.Forms.2.3.3.180\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets'))" />
<Error Condition="!Exists('..\..\packages\Xamarin.Forms.2.3.4.231\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Xamarin.Forms.2.3.4.231\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets'))" />
</Target>
<Import Project="..\..\packages\Xamarin.GooglePlayServices.Basement.29.0.0.2\build\Xamarin.GooglePlayServices.Basement.targets" Condition="Exists('..\..\packages\Xamarin.GooglePlayServices.Basement.29.0.0.2\build\Xamarin.GooglePlayServices.Basement.targets')" />
<Import Project="..\..\packages\Xamarin.Forms.2.3.3.180\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets" Condition="Exists('..\..\packages\Xamarin.Forms.2.3.3.180\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets')" />
<Import Project="..\..\packages\Xamarin.Forms.2.3.4.231\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets" Condition="Exists('..\..\packages\Xamarin.Forms.2.3.4.231\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

View File

@@ -63,9 +63,11 @@ namespace Bit.Android
return;
}
/*
var testNodes = GetWindowNodes(root, e, n => n.ViewIdResourceName != null && n.Text != null, false);
var testNodesData = testNodes.Select(n => new { id = n.ViewIdResourceName, text = n.Text });
testNodes.Dispose();
*/
var notificationManager = (NotificationManager)GetSystemService(NotificationService);
switch(e.EventType)

View File

@@ -1,774 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Android.Runtime;
using Java.IO;
using Java.Net;
using Java.Security;
using Java.Security.Cert;
using Javax.Net.Ssl;
namespace Xamarin.Android.Net
{
/// <summary>
/// A custom implementation of <see cref="System.Net.Http.HttpClientHandler"/> which internally uses <see cref="Java.Net.HttpURLConnection"/>
/// (or its HTTPS incarnation) to send HTTP requests.
/// </summary>
/// <remarks>
/// <para>Instance of this class is used to configure <see cref="System.Net.Http.HttpClient"/> instance
/// in the following way:
///
/// <example>
/// var handler = new AndroidClientHandler {
/// UseCookies = true,
/// AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip,
/// };
///
/// var httpClient = new HttpClient (handler);
/// var response = httpClient.GetAsync ("http://example.com")?.Result as AndroidHttpResponseMessage;
/// </example></para>
/// <para>
/// The class supports pre-authentication of requests albeit in a slightly "manual" way. Namely, whenever a request to a server requiring authentication
/// is made and no authentication credentials are provided in the <see cref="PreAuthenticationData"/> property (which is usually the case on the first
/// request), the <see cref="RequestNeedsAuthorization"/> property will return <c>true</c> and the <see cref="RequestedAuthentication"/> property will
/// contain all the authentication information gathered from the server. The application must then fill in the blanks (i.e. the credentials) and re-send
/// the request configured to perform pre-authentication. The reason for this manual process is that the underlying Java HTTP client API supports only a
/// single, VM-wide, authentication handler which cannot be configured to handle credentials for several requests. AndroidClientHandler, therefore, implements
/// the authentication in managed .NET code. Message handler supports both Basic and Digest authentication. If an authentication scheme that's not supported
/// by AndroidClientHandler is requested by the server, the application can provide its own authentication module (<see cref="AuthenticationData"/>,
/// <see cref="PreAuthenticationData"/>) to handle the protocol authorization.</para>
/// <para>AndroidClientHandler also supports requests to servers with "invalid" (e.g. self-signed) SSL certificates. Since this process is a bit convoluted using
/// the Java APIs, AndroidClientHandler defines two ways to handle the situation. First, easier, is to store the necessary certificates (either CA or server certificates)
/// in the <see cref="TrustedCerts"/> collection or, after deriving a custom class from AndroidClientHandler, by overriding one or more methods provided for this purpose
/// (<see cref="ConfigureTrustManagerFactory"/>, <see cref="ConfigureKeyManagerFactory"/> and <see cref="ConfigureKeyStore"/>). The former method should be sufficient
/// for most use cases, the latter allows the application to provide fully customized key store, trust manager and key manager, if needed. Note that the instance of
/// AndroidClientHandler configured to accept an "invalid" certificate from the particular server will most likely fail to validate certificates from other servers (even
/// if they use a certificate with a fully validated trust chain) unless you store the CA certificates from your Android system in <see cref="TrustedCerts"/> along with
/// the self-signed certificate(s).</para>
/// </remarks>
public class CustomAndroidClientHandler : HttpClientHandler
{
sealed class RequestRedirectionState
{
public Uri NewUrl;
public int RedirectCounter;
public HttpMethod Method;
}
internal const string LOG_APP = "monodroid-net";
const string GZIP_ENCODING = "gzip";
const string DEFLATE_ENCODING = "deflate";
const string IDENTITY_ENCODING = "identity";
static readonly HashSet<string> known_content_headers = new HashSet<string>(StringComparer.OrdinalIgnoreCase) {
"Allow",
"Content-Disposition",
"Content-Encoding",
"Content-Language",
"Content-Length",
"Content-Location",
"Content-MD5",
"Content-Range",
"Content-Type",
"Expires",
"Last-Modified"
};
static readonly List<IAndroidAuthenticationModule> authModules = new List<IAndroidAuthenticationModule> {
new AuthModuleBasic (),
// COMMENTED OUT: Kyle
//new AuthModuleDigest ()
};
bool disposed;
// Now all hail Java developers! Get this... HttpURLClient defaults to accepting AND
// uncompressing the gzip content encoding UNLESS you set the Accept-Encoding header to ANY
// value. So if we set it to 'gzip' below we WILL get gzipped stream but HttpURLClient will NOT
// uncompress it any longer, doh. And they don't support 'deflate' so we need to handle it ourselves.
bool decompress_here;
/// <summary>
/// <para>
/// Gets or sets the pre authentication data for the request. This property must be set by the application
/// before the request is made. Generally the value can be taken from <see cref="RequestedAuthentication"/>
/// after the initial request, without any authentication data, receives the authorization request from the
/// server. The application must then store credentials in instance of <see cref="AuthenticationData"/> and
/// assign the instance to this propery before retrying the request.
/// </para>
/// <para>
/// The property is never set by AndroidClientHandler.
/// </para>
/// </summary>
/// <value>The pre authentication data.</value>
public AuthenticationData PreAuthenticationData { get; set; }
/// <summary>
/// If the website requires authentication, this property will contain data about each scheme supported
/// by the server after the response. Note that unauthorized request will return a valid response - you
/// need to check the status code and and (re)configure AndroidClientHandler instance accordingly by providing
/// both the credentials and the authentication scheme by setting the <see cref="PreAuthenticationData"/>
/// property. If AndroidClientHandler is not able to detect the kind of authentication scheme it will store an
/// instance of <see cref="AuthenticationData"/> with its <see cref="AuthenticationData.Scheme"/> property
/// set to <c>AuthenticationScheme.Unsupported</c> and the application will be responsible for providing an
/// instance of <see cref="IAndroidAuthenticationModule"/> which handles this kind of authorization scheme
/// (<see cref="AuthenticationData.AuthModule"/>
/// </summary>
public IList<AuthenticationData> RequestedAuthentication { get; private set; }
/// <summary>
/// Server authentication response indicates that the request to authorize comes from a proxy if this property is <c>true</c>.
/// All the instances of <see cref="AuthenticationData"/> stored in the <see cref="RequestedAuthentication"/> property will
/// have their <see cref="AuthenticationData.UseProxyAuthentication"/> preset to the same value as this property.
/// </summary>
public bool ProxyAuthenticationRequested { get; private set; }
/// <summary>
/// If <c>true</c> then the server requested authorization and the application must use information
/// found in <see cref="RequestedAuthentication"/> to set the value of <see cref="PreAuthenticationData"/>
/// </summary>
public bool RequestNeedsAuthorization
{
get { return RequestedAuthentication?.Count > 0; }
}
/// <summary>
/// <para>
/// If the request is to the server protected with a self-signed (or otherwise untrusted) SSL certificate, the request will
/// fail security chain verification unless the application provides either the CA certificate of the entity which issued the
/// server's certificate or, alternatively, provides the server public key. Whichever the case, the certificate(s) must be stored
/// in this property in order for AndroidClientHandler to configure the request to accept the server certificate.</para>
/// <para>AndroidClientHandler uses a custom <see cref="KeyStore"/> and <see cref="TrustManagerFactory"/> to configure the connection.
/// If, however, the application requires finer control over the SSL configuration (e.g. it implements its own TrustManager) then
/// it should leave this property empty and instead derive a custom class from AndroidClientHandler and override, as needed, the
/// <see cref="ConfigureTrustManagerFactory"/>, <see cref="ConfigureKeyManagerFactory"/> and <see cref="ConfigureKeyStore"/> methods
/// instead</para>
/// </summary>
/// <value>The trusted certs.</value>
public IList<Certificate> TrustedCerts { get; set; }
protected override void Dispose(bool disposing)
{
disposed = true;
base.Dispose(disposing);
}
protected void AssertSelf()
{
if(!disposed)
return;
throw new ObjectDisposedException(nameof(AndroidClientHandler));
}
string EncodeUrl(Uri url)
{
if(url == null)
return String.Empty;
if(String.IsNullOrEmpty(url.Query))
return Uri.EscapeUriString(url.ToString());
// UriBuilder takes care of encoding everything properly
var bldr = new UriBuilder(url);
if(url.IsDefaultPort)
bldr.Port = -1; // Avoids adding :80 or :443 to the host name in the result
// bldr.Uri.ToString () would ruin the good job UriBuilder did
return bldr.ToString();
}
/// <summary>
/// Creates, configures and processes an asynchronous request to the indicated resource.
/// </summary>
/// <returns>Task in which the request is executed</returns>
/// <param name="request">Request provided by <see cref="System.Net.Http.HttpClient"/></param>
/// <param name="cancellationToken">Cancellation token.</param>
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
AssertSelf();
if(request == null)
throw new ArgumentNullException(nameof(request));
if(!request.RequestUri.IsAbsoluteUri)
throw new ArgumentException("Must represent an absolute URI", "request");
var redirectState = new RequestRedirectionState
{
NewUrl = request.RequestUri,
RedirectCounter = 0,
Method = request.Method
};
while(true)
{
URL java_url = new URL(EncodeUrl(redirectState.NewUrl));
URLConnection java_connection = java_url.OpenConnection();
HttpURLConnection httpConnection = await SetupRequestInternal(request, java_connection);
HttpResponseMessage response = await ProcessRequest(request, java_url, httpConnection, cancellationToken, redirectState);
if(response != null)
return response;
if(redirectState.NewUrl == null)
throw new InvalidOperationException("Request redirected but no new URI specified");
request.Method = redirectState.Method;
}
}
Task<HttpResponseMessage> ProcessRequest(HttpRequestMessage request, URL javaUrl, HttpURLConnection httpConnection, CancellationToken cancellationToken, RequestRedirectionState redirectState)
{
cancellationToken.ThrowIfCancellationRequested();
httpConnection.InstanceFollowRedirects = false; // We handle it ourselves
RequestedAuthentication = null;
ProxyAuthenticationRequested = false;
return DoProcessRequest(request, javaUrl, httpConnection, cancellationToken, redirectState);
}
async Task<HttpResponseMessage> DoProcessRequest(HttpRequestMessage request, URL javaUrl, HttpURLConnection httpConnection, CancellationToken cancellationToken, RequestRedirectionState redirectState)
{
if(cancellationToken.IsCancellationRequested)
{
cancellationToken.ThrowIfCancellationRequested();
}
try
{
await Task.WhenAny(
httpConnection.ConnectAsync(),
Task.Run(() => { cancellationToken.WaitHandle.WaitOne(); }))
.ConfigureAwait(false);
}
catch(Java.Net.ConnectException ex)
{
// Wrap it nicely in a "standard" exception so that it's compatible with HttpClientHandler
throw new WebException(ex.Message, ex, WebExceptionStatus.ConnectFailure, null);
}
if(cancellationToken.IsCancellationRequested)
{
httpConnection.Disconnect();
cancellationToken.ThrowIfCancellationRequested();
}
cancellationToken.Register(httpConnection.Disconnect);
if(httpConnection.DoOutput)
{
using(var stream = await request.Content.ReadAsStreamAsync())
{
await stream.CopyToAsync(httpConnection.OutputStream, 4096, cancellationToken)
.ConfigureAwait(false);
}
}
if(cancellationToken.IsCancellationRequested)
{
httpConnection.Disconnect();
cancellationToken.ThrowIfCancellationRequested();
}
var statusCode = await Task.Run(() => (HttpStatusCode)httpConnection.ResponseCode).ConfigureAwait(false);
var connectionUri = new Uri(httpConnection.URL.ToString());
// If the request was redirected we need to put the new URL in the request
request.RequestUri = connectionUri;
var ret = new AndroidHttpResponseMessage(javaUrl, httpConnection)
{
RequestMessage = request,
ReasonPhrase = httpConnection.ResponseMessage,
StatusCode = statusCode,
};
bool disposeRet;
if(HandleRedirect(statusCode, httpConnection, redirectState, out disposeRet))
{
if(disposeRet)
{
ret.Dispose();
ret = null;
}
return ret;
}
switch(statusCode)
{
case HttpStatusCode.Unauthorized:
case HttpStatusCode.ProxyAuthenticationRequired:
// We don't resend the request since that would require new set of credentials if the
// ones provided in Credentials are invalid (or null) and that, in turn, may require asking the
// user which is not something that should be taken care of by us and in this
// context. The application should be responsible for this.
// HttpClientHandler throws an exception in this instance, but I think it's not a good
// idea. We'll return the response message with all the information required by the
// application to fill in the blanks and provide the requested credentials instead.
//
// We return the body of the response too, but the Java client will throw
// a FileNotFound exception if we attempt to access the input stream.
// Instead we try to read the error stream and return an default message if the error stream isn't readable.
ret.Content = GetErrorContent(httpConnection, new StringContent("Unauthorized", Encoding.ASCII));
CopyHeaders(httpConnection, ret);
if(ret.Headers.WwwAuthenticate != null)
{
ProxyAuthenticationRequested = false;
CollectAuthInfo(ret.Headers.WwwAuthenticate);
}
else if(ret.Headers.ProxyAuthenticate != null)
{
ProxyAuthenticationRequested = true;
CollectAuthInfo(ret.Headers.ProxyAuthenticate);
}
// COMMENTED OUT: Kyle
//ret.RequestedAuthentication = RequestedAuthentication;
return ret;
}
if(!IsErrorStatusCode(statusCode))
{
ret.Content = GetContent(httpConnection, httpConnection.InputStream);
}
else
{
// For 400 >= response code <= 599 the Java client throws the FileNotFound exception when attempting to read from the input stream.
// Instead we try to read the error stream and return an empty string if the error stream isn't readable.
ret.Content = GetErrorContent(httpConnection, new StringContent(String.Empty, Encoding.ASCII));
}
CopyHeaders(httpConnection, ret);
IEnumerable<string> cookieHeaderValue;
if(!UseCookies || CookieContainer == null || !ret.Headers.TryGetValues("Set-Cookie", out cookieHeaderValue) || cookieHeaderValue == null)
{
return ret;
}
try
{
CookieContainer.SetCookies(connectionUri, String.Join(",", cookieHeaderValue));
}
catch(Exception ex)
{
// We don't want to terminate the response because of a bad cookie, hence just reporting
// the issue. We might consider adding a virtual method to let the user handle the
// issue, but not sure if it's really needed. Set-Cookie header will be part of the
// header collection so the user can always examine it if they spot an error.
}
return ret;
}
HttpContent GetErrorContent(HttpURLConnection httpConnection, HttpContent fallbackContent)
{
var contentStream = httpConnection.ErrorStream;
if(contentStream != null)
{
return GetContent(httpConnection, contentStream);
}
return fallbackContent;
}
HttpContent GetContent(URLConnection httpConnection, Stream contentStream)
{
Stream inputStream = new BufferedStream(contentStream);
if(decompress_here)
{
string[] encodings = httpConnection.ContentEncoding?.Split(',');
if(encodings != null)
{
if(encodings.Contains(GZIP_ENCODING, StringComparer.OrdinalIgnoreCase))
inputStream = new GZipStream(inputStream, CompressionMode.Decompress);
else if(encodings.Contains(DEFLATE_ENCODING, StringComparer.OrdinalIgnoreCase))
inputStream = new DeflateStream(inputStream, CompressionMode.Decompress);
}
}
return new StreamContent(inputStream);
}
bool HandleRedirect(HttpStatusCode redirectCode, HttpURLConnection httpConnection, RequestRedirectionState redirectState, out bool disposeRet)
{
if(!AllowAutoRedirect)
{
disposeRet = false;
return true; // We shouldn't follow and there's no data to fetch, just return
}
disposeRet = true;
redirectState.NewUrl = null;
switch(redirectCode)
{
case HttpStatusCode.MultipleChoices: // 300
break;
case HttpStatusCode.Moved: // 301
case HttpStatusCode.Redirect: // 302
case HttpStatusCode.SeeOther: // 303
redirectState.Method = HttpMethod.Get;
break;
case HttpStatusCode.TemporaryRedirect: // 307
break;
default:
if((int)redirectCode >= 300 && (int)redirectCode < 400)
throw new InvalidOperationException($"HTTP Redirection status code {redirectCode} ({(int)redirectCode}) not supported");
return false;
}
IDictionary<string, IList<string>> headers = httpConnection.HeaderFields;
IList<string> locationHeader;
if(!headers.TryGetValue("Location", out locationHeader) || locationHeader == null || locationHeader.Count == 0)
throw new InvalidOperationException($"HTTP connection redirected with code {redirectCode} ({(int)redirectCode}) but no Location header found in response");
redirectState.RedirectCounter++;
if(redirectState.RedirectCounter >= MaxAutomaticRedirections)
throw new WebException($"Maximum automatic redirections exceeded (allowed {MaxAutomaticRedirections}, redirected {redirectState.RedirectCounter} times)");
Uri location = new Uri(locationHeader[0], UriKind.Absolute);
redirectState.NewUrl = location;
return true;
}
bool IsErrorStatusCode(HttpStatusCode statusCode)
{
return (int)statusCode >= 400 && (int)statusCode <= 599;
}
void CollectAuthInfo(HttpHeaderValueCollection<AuthenticationHeaderValue> headers)
{
var authData = new List<AuthenticationData>(headers.Count);
foreach(AuthenticationHeaderValue ahv in headers)
{
var data = new AuthenticationData
{
Scheme = GetAuthScheme(ahv.Scheme),
// COMMENTED OUT: Kyle
//Challenge = $"{ahv.Scheme} {ahv.Parameter}",
UseProxyAuthentication = ProxyAuthenticationRequested
};
authData.Add(data);
}
RequestedAuthentication = authData.AsReadOnly();
}
AuthenticationScheme GetAuthScheme(string scheme)
{
if(String.Compare("basic", scheme, StringComparison.OrdinalIgnoreCase) == 0)
return AuthenticationScheme.Basic;
if(String.Compare("digest", scheme, StringComparison.OrdinalIgnoreCase) == 0)
return AuthenticationScheme.Digest;
return AuthenticationScheme.Unsupported;
}
void CopyHeaders(HttpURLConnection httpConnection, HttpResponseMessage response)
{
IDictionary<string, IList<string>> headers = httpConnection.HeaderFields;
foreach(string key in headers.Keys)
{
if(key == null) // First header entry has null key, it corresponds to the response message
continue;
HttpHeaders item_headers;
string kind;
if(known_content_headers.Contains(key))
{
kind = "content";
item_headers = response.Content.Headers;
}
else
{
kind = "response";
item_headers = response.Headers;
}
item_headers.TryAddWithoutValidation(key, headers[key]);
}
}
/// <summary>
/// Configure the <see cref="HttpURLConnection"/> before the request is sent. This method is meant to be overriden
/// by applications which need to perform some extra configuration steps on the connection. It is called with all
/// the request headers set, pre-authentication performed (if applicable) but before the request body is set
/// (e.g. for POST requests). The default implementation in AndroidClientHandler does nothing.
/// </summary>
/// <param name="request">Request data</param>
/// <param name="conn">Pre-configured connection instance</param>
protected virtual Task SetupRequest(HttpRequestMessage request, HttpURLConnection conn)
{
return Task.Factory.StartNew(AssertSelf);
}
/// <summary>
/// Configures the key store. The <paramref name="keyStore"/> parameter is set to instance of <see cref="KeyStore"/>
/// created using the <see cref="KeyStore.DefaultType"/> type and with populated with certificates provided in the <see cref="TrustedCerts"/>
/// property. AndroidClientHandler implementation simply returns the instance passed in the <paramref name="keyStore"/> parameter
/// </summary>
/// <returns>The key store.</returns>
/// <param name="keyStore">Key store to configure.</param>
protected virtual KeyStore ConfigureKeyStore(KeyStore keyStore)
{
AssertSelf();
return keyStore;
}
/// <summary>
/// Create and configure an instance of <see cref="KeyManagerFactory"/>. The <paramref name="keyStore"/> parameter is set to the
/// return value of the <see cref="ConfigureKeyStore"/> method, so it might be null if the application overrode the method and provided
/// no key store. It will not be <c>null</c> when the default implementation is used. The application can return <c>null</c> here since
/// KeyManagerFactory is not required for the custom SSL configuration, but it might be used by the application to implement a more advanced
/// mechanism of key management.
/// </summary>
/// <returns>The key manager factory or <c>null</c>.</returns>
/// <param name="keyStore">Key store.</param>
protected virtual KeyManagerFactory ConfigureKeyManagerFactory(KeyStore keyStore)
{
AssertSelf();
return null;
}
/// <summary>
/// Create and configure an instance of <see cref="TrustManagerFactory"/>. The <paramref name="keyStore"/> parameter is set to the
/// return value of the <see cref="ConfigureKeyStore"/> method, so it might be null if the application overrode the method and provided
/// no key store. It will not be <c>null</c> when the default implementation is used. The application can return <c>null</c> from this
/// method in which case AndroidClientHandler will create its own instance of the trust manager factory provided that the <see cref="TrustCerts"/>
/// list contains at least one valid certificate. If there are no valid certificates and this method returns <c>null</c>, no custom
/// trust manager will be created since that would make all the HTTPS requests fail.
/// </summary>
/// <returns>The trust manager factory.</returns>
/// <param name="keyStore">Key store.</param>
protected virtual TrustManagerFactory ConfigureTrustManagerFactory(KeyStore keyStore)
{
AssertSelf();
return null;
}
void AppendEncoding(string encoding, ref List<string> list)
{
if(list == null)
list = new List<string>();
if(list.Contains(encoding))
return;
list.Add(encoding);
}
async Task<HttpURLConnection> SetupRequestInternal(HttpRequestMessage request, URLConnection conn)
{
if(conn == null)
throw new ArgumentNullException(nameof(conn));
var httpConnection = conn.JavaCast<HttpURLConnection>();
if(httpConnection == null)
throw new InvalidOperationException($"Unsupported URL scheme {conn.URL.Protocol}");
httpConnection.RequestMethod = request.Method.ToString();
// SSL context must be set up as soon as possible, before adding any content or
// headers. Otherwise Java won't use the socket factory
SetupSSL(httpConnection as HttpsURLConnection);
if(request.Content != null)
AddHeaders(httpConnection, request.Content.Headers);
AddHeaders(httpConnection, request.Headers);
List<string> accept_encoding = null;
decompress_here = false;
if((AutomaticDecompression & DecompressionMethods.GZip) != 0)
{
AppendEncoding(GZIP_ENCODING, ref accept_encoding);
decompress_here = true;
}
if((AutomaticDecompression & DecompressionMethods.Deflate) != 0)
{
AppendEncoding(DEFLATE_ENCODING, ref accept_encoding);
decompress_here = true;
}
if(AutomaticDecompression == DecompressionMethods.None)
{
accept_encoding?.Clear();
AppendEncoding(IDENTITY_ENCODING, ref accept_encoding); // Turns off compression for the Java client
}
if(accept_encoding?.Count > 0)
httpConnection.SetRequestProperty("Accept-Encoding", String.Join(",", accept_encoding));
if(UseCookies && CookieContainer != null)
{
string cookieHeaderValue = CookieContainer.GetCookieHeader(request.RequestUri);
if(!String.IsNullOrEmpty(cookieHeaderValue))
httpConnection.SetRequestProperty("Cookie", cookieHeaderValue);
}
HandlePreAuthentication(httpConnection);
await SetupRequest(request, httpConnection);
SetupRequestBody(httpConnection, request);
return httpConnection;
}
void SetupSSL(HttpsURLConnection httpsConnection)
{
if(httpsConnection == null)
return;
KeyStore keyStore = KeyStore.GetInstance(KeyStore.DefaultType);
keyStore.Load(null, null);
bool gotCerts = TrustedCerts?.Count > 0;
if(gotCerts)
{
for(int i = 0; i < TrustedCerts.Count; i++)
{
Certificate cert = TrustedCerts[i];
if(cert == null)
continue;
keyStore.SetCertificateEntry($"ca{i}", cert);
}
}
keyStore = ConfigureKeyStore(keyStore);
KeyManagerFactory kmf = ConfigureKeyManagerFactory(keyStore);
TrustManagerFactory tmf = ConfigureTrustManagerFactory(keyStore);
if(tmf == null)
{
// If there are no certs and no trust manager factory, we can't use a custom manager
// because it will cause all the HTTPS requests to fail because of unverified trust
// chain
if(!gotCerts)
return;
tmf = TrustManagerFactory.GetInstance(TrustManagerFactory.DefaultAlgorithm);
tmf.Init(keyStore);
}
SSLContext context = SSLContext.GetInstance("TLS");
context.Init(kmf?.GetKeyManagers(), tmf.GetTrustManagers(), null);
httpsConnection.SSLSocketFactory = context.SocketFactory;
}
void HandlePreAuthentication(HttpURLConnection httpConnection)
{
AuthenticationData data = PreAuthenticationData;
if(!PreAuthenticate || data == null)
return;
ICredentials creds = data.UseProxyAuthentication ? Proxy?.Credentials : Credentials;
if(creds == null)
{
return;
}
IAndroidAuthenticationModule auth = data.Scheme == AuthenticationScheme.Unsupported ? data.AuthModule : authModules.Find(m => m?.Scheme == data.Scheme);
if(auth == null)
{
return;
}
Authorization authorization = auth.Authenticate(data.Challenge, httpConnection, creds);
if(authorization == null)
{
return;
}
httpConnection.SetRequestProperty(data.UseProxyAuthentication ? "Proxy-Authorization" : "Authorization", authorization.Message);
}
void AddHeaders(HttpURLConnection conn, HttpHeaders headers)
{
if(headers == null)
return;
foreach(KeyValuePair<string, IEnumerable<string>> header in headers)
{
conn.SetRequestProperty(header.Key, header.Value != null ? String.Join(",", header.Value) : String.Empty);
}
}
void SetupRequestBody(HttpURLConnection httpConnection, HttpRequestMessage request)
{
if(request.Content == null)
{
// Pilfered from System.Net.Http.HttpClientHandler:SendAync
if(HttpMethod.Post.Equals(request.Method) || HttpMethod.Put.Equals(request.Method) || HttpMethod.Delete.Equals(request.Method))
{
// Explicitly set this to make sure we're sending a "Content-Length: 0" header.
// This fixes the issue that's been reported on the forums:
// http://forums.xamarin.com/discussion/17770/length-required-error-in-http-post-since-latest-release
httpConnection.SetRequestProperty("Content-Length", "0");
}
return;
}
httpConnection.DoOutput = true;
long? contentLength = request.Content.Headers.ContentLength;
if(contentLength != null)
httpConnection.SetFixedLengthStreamingMode((int)contentLength);
else
httpConnection.SetChunkedStreamingMode(0);
}
}
sealed class AuthModuleBasic : IAndroidAuthenticationModule
{
public AuthenticationScheme Scheme { get; } = AuthenticationScheme.Basic;
public string AuthenticationType { get; } = "Basic";
public bool CanPreAuthenticate { get; } = true;
public Authorization Authenticate(string challenge, HttpURLConnection request, ICredentials credentials)
{
string header = challenge?.Trim();
if(credentials == null || String.IsNullOrEmpty(header))
return null;
if(header.IndexOf("basic", StringComparison.OrdinalIgnoreCase) == -1)
return null;
return InternalAuthenticate(request, credentials);
}
public Authorization PreAuthenticate(HttpURLConnection request, ICredentials credentials)
{
return InternalAuthenticate(request, credentials);
}
Authorization InternalAuthenticate(HttpURLConnection request, ICredentials credentials)
{
if(request == null || credentials == null)
return null;
NetworkCredential cred = credentials.GetCredential(new Uri(request.URL.ToString()), AuthenticationType.ToLowerInvariant());
if(cred == null)
return null;
if(String.IsNullOrEmpty(cred.UserName))
return null;
string domain = cred.Domain?.Trim();
string response = String.Empty;
// If domain is set, MS sends "domain\user:password".
if(!String.IsNullOrEmpty(domain))
response = domain + "\\";
response += cred.UserName + ":" + cred.Password;
return new Authorization($"{AuthenticationType} {Convert.ToBase64String(Encoding.ASCII.GetBytes(response))}");
}
}
}

View File

@@ -1 +0,0 @@
XA_HTTP_CLIENT_HANDLER_TYPE=Xamarin.Android.Net.AndroidClientHandler

View File

@@ -73,7 +73,8 @@ namespace Bit.Android
Resolver.Resolve<ILockService>(),
Resolver.Resolve<IGoogleAnalyticsService>(),
Resolver.Resolve<ILocalizeService>(),
Resolver.Resolve<IAppInfoService>()));
Resolver.Resolve<IAppInfoService>(),
Resolver.Resolve<IAppSettingsService>()));
MessagingCenter.Subscribe<Xamarin.Forms.Application>(Xamarin.Forms.Application.Current, "RateApp", (sender) =>
{

View File

@@ -39,9 +39,6 @@ namespace Bit.Android
public MainApplication(IntPtr handle, JniHandleOwnership transer)
: base(handle, transer)
{
// NOTE: This is just here to stop the linker from removing AndroidClientHandler references
var handler = new AndroidClientHandler();
if(!Resolver.IsSet)
{
SetIoc(this);
@@ -214,6 +211,7 @@ namespace Bit.Android
.RegisterType<ITokenService, TokenService>(new ContainerControlledLifetimeManager())
.RegisterType<ISettingsService, SettingsService>(new ContainerControlledLifetimeManager())
.RegisterType<IMemoryService, MemoryService>(new ContainerControlledLifetimeManager())
.RegisterType<IAppSettingsService, AppSettingsService>(new ContainerControlledLifetimeManager())
// Repositories
.RegisterType<IFolderRepository, FolderRepository>(new ContainerControlledLifetimeManager())
.RegisterType<IFolderApiRepository, FolderApiRepository>(new ContainerControlledLifetimeManager())

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.x8bit.bitwarden" android:versionName="1.4.4" android:installLocation="auto" android:versionCode="502">
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.x8bit.bitwarden" android:versionName="1.5.0" android:installLocation="auto" android:versionCode="502">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="23" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />

View File

@@ -2742,8 +2742,8 @@ namespace Bit.Android
// aapt resource value: 0x7f0200e4
public const int notification_sm = 2130837732;
// aapt resource value: 0x7f0200f1
public const int notification_template_icon_bg = 2130837745;
// aapt resource value: 0x7f0200f3
public const int notification_template_icon_bg = 2130837747;
// aapt resource value: 0x7f0200e5
public const int plus = 2130837733;
@@ -2761,25 +2761,31 @@ namespace Bit.Android
public const int search = 2130837737;
// aapt resource value: 0x7f0200ea
public const int splash_screen = 2130837738;
public const int share = 2130837738;
// aapt resource value: 0x7f0200eb
public const int star = 2130837739;
public const int share_tools = 2130837739;
// aapt resource value: 0x7f0200ec
public const int star_selected = 2130837740;
public const int splash_screen = 2130837740;
// aapt resource value: 0x7f0200ed
public const int tools = 2130837741;
public const int star = 2130837741;
// aapt resource value: 0x7f0200ee
public const int tools_selected = 2130837742;
public const int star_selected = 2130837742;
// aapt resource value: 0x7f0200ef
public const int upload = 2130837743;
public const int tools = 2130837743;
// aapt resource value: 0x7f0200f0
public const int user = 2130837744;
public const int tools_selected = 2130837744;
// aapt resource value: 0x7f0200f1
public const int upload = 2130837745;
// aapt resource value: 0x7f0200f2
public const int user = 2130837746;
static Drawable()
{

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1009 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 503 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 649 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 817 B

View File

@@ -1,5 +1,7 @@
using System;
using Bit.App;
using Bit.App.Abstractions;
using Plugin.Settings.Abstractions;
using Android.Gms.Analytics;
using Android.Content;
@@ -7,8 +9,6 @@ namespace Bit.Android.Services
{
public class GoogleAnalyticsService : IGoogleAnalyticsService
{
private const string UserId = "&uid";
private readonly GoogleAnalytics _instance;
private readonly IAuthService _authService;
private readonly Tracker _tracker;
@@ -17,7 +17,8 @@ namespace Bit.Android.Services
public GoogleAnalyticsService(
Context appContext,
IAppIdService appIdService,
IAuthService authService)
IAuthService authService,
ISettings settings)
{
_authService = authService;
@@ -25,16 +26,13 @@ namespace Bit.Android.Services
_instance.SetLocalDispatchPeriod(10);
_tracker = _instance.NewTracker("UA-81915606-2");
_tracker.EnableExceptionReporting(true);
_tracker.EnableExceptionReporting(false);
_tracker.EnableAdvertisingIdCollection(true);
_tracker.EnableAutoActivityTracking(true);
_tracker.SetClientId(appIdService.AnonymousAppId);
}
public void RefreshUserId()
{
_tracker.Set(UserId, null);
_setUserId = true;
var gaOptOut = settings.GetValueOrDefault(Constants.SettingGaOptOut, false);
SetAppOptOut(gaOptOut);
}
public void TrackAppEvent(string eventName, string label = null)
@@ -57,7 +55,6 @@ namespace Bit.Android.Services
builder.SetLabel(label);
}
SetUserId();
_tracker.Send(builder.Build());
}
@@ -67,30 +64,24 @@ namespace Bit.Android.Services
builder.SetDescription(message);
builder.SetFatal(fatal);
SetUserId();
_tracker.Send(builder.Build());
}
public void TrackPage(string pageName)
{
SetUserId();
_tracker.SetScreenName(pageName);
_tracker.Send(new HitBuilders.ScreenViewBuilder().Build());
}
private void SetUserId()
{
if(_setUserId && _authService.IsAuthenticated)
{
_tracker.Set(UserId, _authService.UserId);
_setUserId = false;
}
}
public void Dispatch(Action completionHandler = null)
{
_instance.DispatchLocalHits();
completionHandler?.Invoke();
}
public void SetAppOptOut(bool optOut)
{
_instance.AppOptOut = optOut;
}
}
}

View File

@@ -7,6 +7,6 @@ namespace Bit.Android.Services
{
public class HttpService : IHttpService
{
public ApiHttpClient Client => new ApiHttpClient(new CustomAndroidClientHandler());
public ApiHttpClient Client => new ApiHttpClient(new AndroidClientHandler());
}
}

View File

@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Acr.Support" version="2.1.0" targetFramework="monoandroid60" />
<package id="Acr.UserDialogs" version="6.3.3" targetFramework="monoandroid60" />
<package id="Acr.UserDialogs" version="6.3.10" targetFramework="monoandroid71" />
<package id="AndHUD" version="1.2.0" targetFramework="monoandroid60" />
<package id="BouncyCastle" version="1.8.1" targetFramework="monoandroid60" />
<package id="CommonServiceLocator" version="1.3" targetFramework="monoandroid60" />
<package id="HockeySDK.Xamarin" version="4.1.0" targetFramework="monoandroid60" />
<package id="HockeySDK.Xamarin" version="4.1.2" targetFramework="monoandroid71" />
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="monoandroid60" />
<package id="PCLCrypto" version="2.0.147" targetFramework="monoandroid60" />
<package id="PInvoke.BCrypt" version="0.3.152" targetFramework="monoandroid60" />
@@ -25,9 +25,9 @@
<package id="SQLitePCLRaw.provider.e_sqlite3.android" version="1.1.2" targetFramework="monoandroid60" />
<package id="Unity" version="3.5.1405-prerelease" targetFramework="monoandroid60" />
<package id="Validation" version="2.3.7" targetFramework="monoandroid60" />
<package id="Xam.Plugin.Connectivity" version="2.2.12" targetFramework="monoandroid60" />
<package id="Xam.Plugin.Connectivity" version="2.3.0" targetFramework="monoandroid71" />
<package id="Xam.Plugin.PushNotification" version="1.2.4" targetFramework="monoandroid60" developmentDependency="true" />
<package id="Xam.Plugins.Settings" version="2.5.1.0" targetFramework="monoandroid60" />
<package id="Xam.Plugins.Settings" version="2.5.4" targetFramework="monoandroid71" />
<package id="Xamarin.Android.Support.Animated.Vector.Drawable" version="23.3.0" targetFramework="monoandroid60" />
<package id="Xamarin.Android.Support.Design" version="23.3.0" targetFramework="monoandroid60" />
<package id="Xamarin.Android.Support.v4" version="23.3.0" targetFramework="monoandroid60" />
@@ -36,9 +36,9 @@
<package id="Xamarin.Android.Support.v7.MediaRouter" version="23.3.0" targetFramework="monoandroid60" />
<package id="Xamarin.Android.Support.v7.RecyclerView" version="23.3.0" targetFramework="monoandroid60" />
<package id="Xamarin.Android.Support.Vector.Drawable" version="23.3.0" targetFramework="monoandroid60" />
<package id="Xamarin.FFImageLoading" version="2.2.8" targetFramework="monoandroid60" />
<package id="Xamarin.FFImageLoading.Forms" version="2.2.8" targetFramework="monoandroid60" />
<package id="Xamarin.Forms" version="2.3.3.180" targetFramework="monoandroid60" />
<package id="Xamarin.FFImageLoading" version="2.2.9" targetFramework="monoandroid71" />
<package id="Xamarin.FFImageLoading.Forms" version="2.2.9" targetFramework="monoandroid71" />
<package id="Xamarin.Forms" version="2.3.4.231" targetFramework="monoandroid71" />
<package id="Xamarin.GooglePlayServices.Analytics" version="29.0.0.2" targetFramework="monoandroid60" />
<package id="Xamarin.GooglePlayServices.Base" version="29.0.0.2" targetFramework="monoandroid60" />
<package id="Xamarin.GooglePlayServices.Basement" version="29.0.0.2" targetFramework="monoandroid60" />

View File

@@ -8,6 +8,8 @@ namespace Bit.App.Abstractions
{
Task<ApiResult> PostRegisterAsync(RegisterRequest requestObj);
Task<ApiResult> PostPasswordHintAsync(PasswordHintRequest requestObj);
Task<ApiResult<DateTime?>> GetAccountRevisionDate();
Task<ApiResult<DateTime?>> GetAccountRevisionDateAsync();
Task<ApiResult<ProfileResponse>> GetProfileAsync();
Task<ApiResult<KeysResponse>> GetKeys();
}
}

View File

@@ -8,6 +8,5 @@ namespace Bit.App.Abstractions
{
Task<ApiResult<CipherResponse>> GetByIdAsync(string id);
Task<ApiResult<ListResponse<CipherResponse>>> GetAsync();
Task<ApiResult<CipherHistoryResponse>> GetByRevisionDateWithHistoryAsync(DateTime since);
}
}

View File

@@ -0,0 +1,10 @@
using System;
namespace Bit.App.Abstractions
{
public interface IAppSettingsService
{
bool Locked { get; set; }
DateTime LastActivity { get; set; }
}
}

View File

@@ -1,6 +1,5 @@
using System.Threading.Tasks;
using Bit.App.Models.Api;
using System;
using Bit.App.Models;
using System.Threading.Tasks;
namespace Bit.App.Abstractions
{
@@ -13,7 +12,9 @@ namespace Bit.App.Abstractions
string Email { get; set; }
string PIN { get; set; }
bool BelongsToOrganization(string orgId);
void LogOut();
Task<ApiResult<TokenResponse>> TokenPostAsync(TokenRequest request);
Task<FullLoginResult> TokenPostAsync(string email, string masterPassword);
Task<LoginResult> TokenPostTwoFactorAsync(string token, string email, string masterPasswordHash, SymmetricCryptoKey key);
}
}

View File

@@ -1,19 +1,30 @@
using Bit.App.Models;
using Bit.App.Models.Api;
using System;
using System.Collections.Generic;
namespace Bit.App.Abstractions
{
public interface ICryptoService
{
string Base64Key { get; }
byte[] Key { get; set; }
byte[] PreviousKey { get; }
SymmetricCryptoKey Key { get; set; }
SymmetricCryptoKey PreviousKey { get; }
bool KeyChanged { get; }
byte[] PrivateKey { get; }
IDictionary<string, SymmetricCryptoKey> OrgKeys { get; }
string Decrypt(CipherString encyptedValue);
CipherString Encrypt(string plaintextValue);
byte[] MakeKeyFromPassword(string password, string salt);
void SetPrivateKey(CipherString privateKeyEnc);
void SetOrgKeys(ProfileResponse profile);
void SetOrgKeys(Dictionary<string, string> orgKeysEncDict);
SymmetricCryptoKey GetOrgKey(string orgId);
void ClearKeys();
string Decrypt(CipherString encyptedValue, SymmetricCryptoKey key = null);
byte[] DecryptToBytes(CipherString encyptedValue, SymmetricCryptoKey key = null);
byte[] RsaDecryptToBytes(CipherString encyptedValue, byte[] privateKey);
CipherString Encrypt(string plaintextValue, SymmetricCryptoKey key = null);
SymmetricCryptoKey MakeKeyFromPassword(string password, string salt);
string MakeKeyFromPasswordBase64(string password, string salt);
byte[] HashPassword(byte[] key, string password);
string HashPasswordBase64(byte[] key, string password);
byte[] HashPassword(SymmetricCryptoKey key, string password);
string HashPasswordBase64(SymmetricCryptoKey key, string password);
}
}

View File

@@ -4,12 +4,12 @@ namespace Bit.App.Abstractions
{
public interface IGoogleAnalyticsService
{
void RefreshUserId();
void TrackPage(string pageName);
void TrackAppEvent(string eventName, string label = null);
void TrackExtensionEvent(string eventName, string label = null);
void TrackEvent(string category, string eventName, string label = null);
void TrackException(string message, bool fatal);
void Dispatch(Action completionHandler = null);
void SetAppOptOut(bool optOut);
}
}

View File

@@ -1,9 +1,11 @@
using Bit.App.Enums;
using System;
namespace Bit.App.Abstractions
{
public interface ILockService
{
void UpdateLastActivity(DateTime? activityDate = null);
LockType GetLockType(bool forceLock);
}
}

View File

@@ -6,9 +6,12 @@ namespace Bit.App.Abstractions
public interface ISyncService
{
bool SyncInProgress { get; }
Task<bool> SyncAsync(string id);
Task<bool> SyncCipherAsync(string id);
Task<bool> SyncFolderAsync(string id);
Task<bool> SyncDeleteFolderAsync(string id, DateTime revisionDate);
Task<bool> SyncDeleteLoginAsync(string id);
Task<bool> SyncSettingsAsync();
Task<bool> SyncProfileAsync();
Task<bool> FullSyncAsync(bool forceSync = false);
Task<bool> FullSyncAsync(TimeSpan syncThreshold, bool forceSync = false);
}

View File

@@ -33,6 +33,7 @@ namespace Bit.App
private readonly IGoogleAnalyticsService _googleAnalyticsService;
private readonly ILocalizeService _localizeService;
private readonly IAppInfoService _appInfoService;
private readonly IAppSettingsService _appSettingsService;
public App(
string uri,
@@ -46,7 +47,8 @@ namespace Bit.App
ILockService lockService,
IGoogleAnalyticsService googleAnalyticsService,
ILocalizeService localizeService,
IAppInfoService appInfoService)
IAppInfoService appInfoService,
IAppSettingsService appSettingsService)
{
_uri = uri;
_databaseService = databaseService;
@@ -60,6 +62,7 @@ namespace Bit.App
_googleAnalyticsService = googleAnalyticsService;
_localizeService = localizeService;
_appInfoService = appInfoService;
_appSettingsService = appSettingsService;
SetCulture();
SetStyles();
@@ -123,7 +126,7 @@ namespace Bit.App
if(Device.OS == TargetPlatform.Android && !TopPageIsLock())
{
_settings.AddOrUpdateValue(Constants.LastActivityDate, DateTime.UtcNow);
_lockService.UpdateLastActivity();
}
}
@@ -226,7 +229,6 @@ namespace Bit.App
await Task.Run(() => deviceApiRepository.PutClearTokenAsync(appIdService.AppId)).ConfigureAwait(false);
_googleAnalyticsService.TrackAppEvent("LoggedOut");
_googleAnalyticsService.RefreshUserId();
Device.BeginInvokeOnMainThread(() => Current.MainPage = new ExtendedNavigationPage(new HomePage()));
if(!string.IsNullOrWhiteSpace(logoutMessage))
@@ -249,7 +251,7 @@ namespace Bit.App
return;
}
_settings.AddOrUpdateValue(Constants.Locked, true);
_appSettingsService.Locked = true;
switch(lockType)
{
case Enums.LockType.Fingerprint:

View File

@@ -39,6 +39,7 @@
<Compile Include="Abstractions\Repositories\IAccountsApiRepository.cs" />
<Compile Include="Abstractions\Repositories\IDeviceApiRepository.cs" />
<Compile Include="Abstractions\Repositories\ISettingsRepository.cs" />
<Compile Include="Abstractions\Services\IAppSettingsService.cs" />
<Compile Include="Abstractions\Services\IMemoryService.cs" />
<Compile Include="Abstractions\Services\ISettingsService.cs" />
<Compile Include="Abstractions\Services\ITokenService.cs" />
@@ -80,8 +81,11 @@
<Compile Include="Controls\FormEntryCell.cs" />
<Compile Include="Controls\PinControl.cs" />
<Compile Include="Controls\VaultListViewCell.cs" />
<Compile Include="Enums\EncryptionType.cs" />
<Compile Include="Enums\OrganizationUserType.cs" />
<Compile Include="Enums\LockType.cs" />
<Compile Include="Enums\CipherType.cs" />
<Compile Include="Enums\OrganizationUserStatusType.cs" />
<Compile Include="Enums\PushType.cs" />
<Compile Include="Enums\ReturnType.cs" />
<Compile Include="Abstractions\Services\ILocalizeService.cs" />
@@ -103,17 +107,21 @@
<Compile Include="Models\Api\Response\ListResponse.cs" />
<Compile Include="Models\Api\Response\DeviceResponse.cs" />
<Compile Include="Models\Api\Response\LoginResponse.cs" />
<Compile Include="Models\Api\Response\ProfileOrganizationResponse.cs" />
<Compile Include="Models\Api\Response\KeysResponse.cs" />
<Compile Include="Models\Api\Response\TokenResponse.cs" />
<Compile Include="Models\Api\Response\ProfileResponse.cs" />
<Compile Include="Models\Api\LoginDataModel.cs" />
<Compile Include="Models\Cipher.cs" />
<Compile Include="Models\CipherString.cs" />
<Compile Include="Models\SymmetricCryptoKey.cs" />
<Compile Include="Models\Data\SettingsData.cs" />
<Compile Include="Models\Data\FolderData.cs" />
<Compile Include="Abstractions\IDataObject.cs" />
<Compile Include="Models\Data\LoginData.cs" />
<Compile Include="Models\DomainName.cs" />
<Compile Include="Models\Folder.cs" />
<Compile Include="Models\LoginResult.cs" />
<Compile Include="Models\Page\AppExtensionPageModel.cs" />
<Compile Include="Models\Page\SettingsFolderPageModel.cs" />
<Compile Include="Models\Page\PinPageModel.cs" />
@@ -196,6 +204,7 @@
<DesignTime>True</DesignTime>
<DependentUpon>AppResources.zh-Hans.resx</DependentUpon>
</Compile>
<Compile Include="Services\AppSettingsService.cs" />
<Compile Include="Services\SettingsService.cs" />
<Compile Include="Services\TokenService.cs" />
<Compile Include="Services\AppIdService.cs" />
@@ -254,28 +263,22 @@
</ItemGroup>
<ItemGroup>
<Reference Include="Acr.UserDialogs, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Acr.UserDialogs.6.3.3\lib\portable-win+net45+wp8+win8+wpa81\Acr.UserDialogs.dll</HintPath>
<Private>True</Private>
<HintPath>..\..\packages\Acr.UserDialogs.6.3.10\lib\portable-win+net45+wp8+win8+wpa81\Acr.UserDialogs.dll</HintPath>
</Reference>
<Reference Include="Acr.UserDialogs.Interface, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Acr.UserDialogs.6.3.3\lib\portable-win+net45+wp8+win8+wpa81\Acr.UserDialogs.Interface.dll</HintPath>
<Private>True</Private>
<HintPath>..\..\packages\Acr.UserDialogs.6.3.10\lib\portable-win+net45+wp8+win8+wpa81\Acr.UserDialogs.Interface.dll</HintPath>
</Reference>
<Reference Include="FFImageLoading, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xamarin.FFImageLoading.2.2.8\lib\portable-net45+win8+wpa81+wp8\FFImageLoading.dll</HintPath>
<Private>True</Private>
<HintPath>..\..\packages\Xamarin.FFImageLoading.2.2.9\lib\portable-net45+win8+wpa81+wp8\FFImageLoading.dll</HintPath>
</Reference>
<Reference Include="FFImageLoading.Forms, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xamarin.FFImageLoading.Forms.2.2.8\lib\portable-net45+win8+wpa81+wp8\FFImageLoading.Forms.dll</HintPath>
<Private>True</Private>
<HintPath>..\..\packages\Xamarin.FFImageLoading.Forms.2.2.9\lib\portable-net45+win8+wpa81+wp8\FFImageLoading.Forms.dll</HintPath>
</Reference>
<Reference Include="FFImageLoading.Platform, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xamarin.FFImageLoading.2.2.8\lib\portable-net45+win8+wpa81+wp8\FFImageLoading.Platform.dll</HintPath>
<Private>True</Private>
<HintPath>..\..\packages\Xamarin.FFImageLoading.2.2.9\lib\portable-net45+win8+wpa81+wp8\FFImageLoading.Platform.dll</HintPath>
</Reference>
<Reference Include="HockeySDK, Version=1.0.6103.22141, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\HockeySDK.Xamarin.4.1.0\lib\portable-net45+wp8+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\HockeySDK.dll</HintPath>
<Private>True</Private>
<Reference Include="HockeySDK, Version=1.0.6288.33979, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\HockeySDK.Xamarin.4.1.2\lib\portable-net45+wp8+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\HockeySDK.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Practices.ServiceLocation, Version=1.3.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\CommonServiceLocator.1.3\lib\portable-net4+sl5+netcore45+wpa81+wp8\Microsoft.Practices.ServiceLocation.dll</HintPath>
@@ -312,13 +315,11 @@
<HintPath>..\..\packages\PInvoke.Windows.Core.0.3.152\lib\portable-net45+win+wpa81+MonoAndroid10+xamarinios10+MonoTouch10\PInvoke.Windows.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Plugin.Connectivity, Version=2.2.12.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xam.Plugin.Connectivity.2.2.12\lib\portable-net45+wp80+wp81+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10+Xamarin.Mac20+UAP10\Plugin.Connectivity.dll</HintPath>
<Private>True</Private>
<Reference Include="Plugin.Connectivity, Version=2.3.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xam.Plugin.Connectivity.2.3.0\lib\portable-net45+wp80+win8+wpa81\Plugin.Connectivity.dll</HintPath>
</Reference>
<Reference Include="Plugin.Connectivity.Abstractions, Version=2.2.12.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xam.Plugin.Connectivity.2.2.12\lib\portable-net45+wp80+wp81+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10+Xamarin.Mac20+UAP10\Plugin.Connectivity.Abstractions.dll</HintPath>
<Private>True</Private>
<Reference Include="Plugin.Connectivity.Abstractions, Version=2.3.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xam.Plugin.Connectivity.2.3.0\lib\portable-net45+wp80+win8+wpa81\Plugin.Connectivity.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Plugin.Fingerprint, Version=1.2.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Plugin.Fingerprint.1.2.0\lib\portable-net45+win8+wpa81+wp8\Plugin.Fingerprint.dll</HintPath>
@@ -328,13 +329,11 @@
<HintPath>..\..\packages\Plugin.Fingerprint.1.2.0\lib\portable-net45+win8+wpa81+wp8\Plugin.Fingerprint.Abstractions.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Plugin.Settings, Version=2.5.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xam.Plugins.Settings.2.5.1.0\lib\portable-net45+wp80+win8+wpa81\Plugin.Settings.dll</HintPath>
<Private>True</Private>
<Reference Include="Plugin.Settings, Version=2.5.4.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xam.Plugins.Settings.2.5.4\lib\portable-net45+wp80+win8+wpa81\Plugin.Settings.dll</HintPath>
</Reference>
<Reference Include="Plugin.Settings.Abstractions, Version=2.5.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xam.Plugins.Settings.2.5.1.0\lib\portable-net45+wp80+win8+wpa81\Plugin.Settings.Abstractions.dll</HintPath>
<Private>True</Private>
<Reference Include="Plugin.Settings.Abstractions, Version=2.5.4.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xam.Plugins.Settings.2.5.4\lib\portable-net45+wp80+win8+wpa81\Plugin.Settings.Abstractions.dll</HintPath>
</Reference>
<Reference Include="PushNotification.Plugin, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xam.Plugin.PushNotification.1.2.4\lib\portable-net45+wp8+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10+UAP10\PushNotification.Plugin.dll</HintPath>
@@ -377,16 +376,13 @@
<Private>True</Private>
</Reference>
<Reference Include="Xamarin.Forms.Core, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xamarin.Forms.2.3.3.180\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Core.dll</HintPath>
<Private>True</Private>
<HintPath>..\..\packages\Xamarin.Forms.2.3.4.231\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Core.dll</HintPath>
</Reference>
<Reference Include="Xamarin.Forms.Platform, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xamarin.Forms.2.3.3.180\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Platform.dll</HintPath>
<Private>True</Private>
<HintPath>..\..\packages\Xamarin.Forms.2.3.4.231\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Platform.dll</HintPath>
</Reference>
<Reference Include="Xamarin.Forms.Xaml, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xamarin.Forms.2.3.3.180\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Xaml.dll</HintPath>
<Private>True</Private>
<HintPath>..\..\packages\Xamarin.Forms.2.3.4.231\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Xaml.dll</HintPath>
</Reference>
<Reference Include="XLabs.Ioc, Version=2.0.5782.12218, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\XLabs.IoC.2.0.5782\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid1+MonoTouch1+Xamarin.iOS10\XLabs.Ioc.dll</HintPath>
@@ -403,12 +399,12 @@
<Compile Include="Services\PushNotificationListener.cs" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<Import Project="..\..\packages\Xamarin.Forms.2.3.3.180\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets" Condition="Exists('..\..\packages\Xamarin.Forms.2.3.3.180\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets')" />
<Import Project="..\..\packages\Xamarin.Forms.2.3.4.231\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets" Condition="Exists('..\..\packages\Xamarin.Forms.2.3.4.231\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\packages\Xamarin.Forms.2.3.3.180\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Xamarin.Forms.2.3.3.180\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets'))" />
<Error Condition="!Exists('..\..\packages\Xamarin.Forms.2.3.4.231\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Xamarin.Forms.2.3.4.231\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

View File

@@ -7,6 +7,7 @@
public const string SettingFingerprintUnlockOn = "setting:fingerprintUnlockOn";
public const string SettingPinUnlockOn = "setting:pinUnlockOn";
public const string SettingLockSeconds = "setting:lockSeconds";
public const string SettingGaOptOut = "setting:googleAnalyticsOptOut";
public const string PasswordGeneratorLength = "pwGenerator:length";
public const string PasswordGeneratorUppercase = "pwGenerator:uppercase";

View File

@@ -10,7 +10,7 @@ namespace Bit.App.Controls
{
private ISyncService _syncService;
private IGoogleAnalyticsService _googleAnalyticsService;
private ISettings _settings;
private ILockService _lockService;
private bool _syncIndicator;
private bool _updateActivity;
@@ -20,7 +20,7 @@ namespace Bit.App.Controls
_updateActivity = updateActivity;
_syncService = Resolver.Resolve<ISyncService>();
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
_settings = Resolver.Resolve<ISettings>();
_lockService = Resolver.Resolve<ILockService>();
BackgroundColor = Color.FromHex("efeff4");
@@ -45,11 +45,6 @@ namespace Bit.App.Controls
IsBusy = _syncService.SyncInProgress;
}
if(_updateActivity)
{
_settings.AddOrUpdateValue(Constants.LastActivityDate, DateTime.UtcNow);
}
_googleAnalyticsService.TrackPage(GetType().Name);
base.OnAppearing();
}
@@ -61,6 +56,11 @@ namespace Bit.App.Controls
IsBusy = false;
}
if(_updateActivity)
{
_lockService.UpdateLastActivity();
}
base.OnDisappearing();
}
}

View File

@@ -1,4 +1,5 @@
using Xamarin.Forms;
using FFImageLoading.Forms;
using Xamarin.Forms;
namespace Bit.App.Controls
{
@@ -19,6 +20,14 @@ namespace Bit.App.Controls
Style = (Style)Application.Current.Resources["text-muted"]
};
LabelIcon = new CachedImage
{
WidthRequest = 16,
HeightRequest = 16,
HorizontalOptions = LayoutOptions.Start,
Margin = new Thickness(5, 0, 0, 0)
};
Button = new ExtendedButton
{
WidthRequest = 60
@@ -32,11 +41,14 @@ namespace Bit.App.Controls
};
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(60, GridUnitType.Absolute) });
grid.Children.Add(Label, 0, 0);
grid.Children.Add(Detail, 0, 1);
grid.Children.Add(Button, 1, 0);
grid.Children.Add(LabelIcon, 1, 0);
grid.Children.Add(Button, 2, 0);
Grid.SetColumnSpan(Detail, 2);
Grid.SetRowSpan(Button, 2);
if(Device.OS == TargetPlatform.Android)
@@ -49,6 +61,7 @@ namespace Bit.App.Controls
public Label Label { get; private set; }
public Label Detail { get; private set; }
public CachedImage LabelIcon { get; private set; }
public Button Button { get; private set; }
}
}

View File

@@ -12,13 +12,16 @@ namespace Bit.App.Controls
public VaultListViewCell(Action<VaultListPageModel.Login> moreClickedAction)
{
SetBinding(LoginParameterProperty, new Binding("."));
Label.SetBinding<VaultListPageModel.Login>(Label.TextProperty, s => s.Name);
Detail.SetBinding<VaultListPageModel.Login>(Label.TextProperty, s => s.Username);
Label.SetBinding<VaultListPageModel.Login>(Label.TextProperty, l => l.Name);
Detail.SetBinding<VaultListPageModel.Login>(Label.TextProperty, l => l.Username);
LabelIcon.SetBinding<VaultListPageModel.Login>(VisualElement.IsVisibleProperty, l => l.Shared);
Button.Image = "more";
Button.Command = new Command(() => moreClickedAction?.Invoke(LoginParameter));
Button.BackgroundColor = Color.Transparent;
LabelIcon.Source = "share";
BackgroundColor = Color.White;
}

View File

@@ -2,7 +2,8 @@
{
public enum CipherType : short
{
Folder = 0,
// Folder deprecated
//Folder = 0,
Login = 1
}
}

View File

@@ -0,0 +1,11 @@
namespace Bit.App.Enums
{
public enum EncryptionType : byte
{
AesCbc256_B64 = 0,
AesCbc128_HmacSha256_B64 = 1,
AesCbc256_HmacSha256_B64 = 2,
Rsa2048_OaepSha256_B64 = 3,
Rsa2048_OaepSha1_B64 = 4
}
}

View File

@@ -0,0 +1,9 @@
namespace Bit.App.Enums
{
public enum OrganizationUserStatusType : byte
{
Invited = 0,
Accepted = 1,
Confirmed = 2
}
}

View File

@@ -0,0 +1,9 @@
namespace Bit.App.Enums
{
public enum OrganizationUserType : byte
{
Owner = 0,
Admin = 1,
User = 2
}
}

View File

@@ -6,6 +6,13 @@
SyncCipherCreate = 1,
SyncLoginDelete = 2,
SyncFolderDelete = 3,
SyncCiphers = 4
SyncCiphers = 4,
SyncVault = 5,
SyncOrgKeys = 6,
SyncFolderCreate = 7,
SyncFolderUpdate = 8,
SyncCipherDelete = 9,
SyncSettings = 10
}
}

View File

@@ -4,6 +4,7 @@
{
public LoginRequest(Login login)
{
OrganizationId = login.OrganizationId;
FolderId = login.FolderId;
Name = login.Name?.EncryptedString;
Uri = login.Uri?.EncryptedString;
@@ -13,6 +14,7 @@
Favorite = login.Favorite;
}
public string OrganizationId { get; set; }
public string FolderId { get; set; }
public string Name { get; set; }
public string Uri { get; set; }

View File

@@ -8,6 +8,8 @@ namespace Bit.App.Models.Api
{
public string Id { get; set; }
public string FolderId { get; set; }
public string UserId { get; set; }
public string OrganizationId { get; set; }
public CipherType Type { get; set; }
public bool Favorite { get; set; }
public JObject Data { get; set; }

View File

@@ -0,0 +1,8 @@
namespace Bit.App.Models.Api
{
public class KeysResponse
{
public string PublicKey { get; set; }
public string PrivateKey { get; set; }
}
}

View File

@@ -6,6 +6,8 @@ namespace Bit.App.Models.Api
{
public string Id { get; set; }
public string FolderId { get; set; }
public string UserId { get; set; }
public string OrganizationId { get; set; }
public string Name { get; set; }
public string Uri { get; set; }
public string Username { get; set; }
@@ -13,8 +15,5 @@ namespace Bit.App.Models.Api
public string Notes { get; set; }
public bool Favorite { get; set; }
public DateTime RevisionDate { get; set; }
// Expandables
public FolderResponse Folder { get; set; }
}
}

View File

@@ -0,0 +1,14 @@
using Bit.App.Enums;
namespace Bit.App.Models.Api
{
public class ProfileOrganizationResponseModel
{
public string Id { get; set; }
public string Name { get; set; }
public string Key { get; set; }
public OrganizationUserStatusType Status { get; set; }
public OrganizationUserType Type { get; set; }
public bool Enabled { get; set; }
}
}

View File

@@ -1,4 +1,6 @@
namespace Bit.App.Models.Api
using System.Collections.Generic;
namespace Bit.App.Models.Api
{
public class ProfileResponse
{
@@ -8,5 +10,6 @@
public string MasterPasswordHint { get; set; }
public string Culture { get; set; }
public bool TwoFactorEnabled { get; set; }
public IEnumerable<ProfileOrganizationResponseModel> Organizations { get; set; }
}
}

View File

@@ -14,5 +14,6 @@ namespace Bit.App.Models.Api
[JsonProperty("token_type")]
public string TokenType { get; set; }
public List<int> TwoFactorProviders { get; set; }
public string PrivateKey { get; set; }
}
}

View File

@@ -1,6 +1,7 @@
using System;
using Bit.App.Abstractions;
using XLabs.Ioc;
using Bit.App.Enums;
namespace Bit.App.Models
{
@@ -10,15 +11,66 @@ namespace Bit.App.Models
public CipherString(string encryptedString)
{
if(string.IsNullOrWhiteSpace(encryptedString) || !encryptedString.Contains("|"))
if(string.IsNullOrWhiteSpace(encryptedString))
{
throw new ArgumentException(nameof(encryptedString));
}
var headerPieces = encryptedString.Split('.');
string[] encPieces;
EncryptionType encType;
if(headerPieces.Length == 2 && Enum.TryParse(headerPieces[0], out encType))
{
EncryptionType = encType;
encPieces = headerPieces[1].Split('|');
}
else if(headerPieces.Length == 1)
{
encPieces = headerPieces[0].Split('|');
EncryptionType = encPieces.Length == 3 ? EncryptionType.AesCbc128_HmacSha256_B64 : EncryptionType.AesCbc256_B64;
}
else
{
throw new ArgumentException("Malformed header.");
}
switch(EncryptionType)
{
case EncryptionType.AesCbc256_B64:
if(encPieces.Length != 2)
{
throw new ArgumentException("Malformed encPieces.");
}
InitializationVector = encPieces[0];
CipherText = encPieces[1];
break;
case EncryptionType.AesCbc128_HmacSha256_B64:
case EncryptionType.AesCbc256_HmacSha256_B64:
if(encPieces.Length != 3)
{
throw new ArgumentException("Malformed encPieces.");
}
InitializationVector = encPieces[0];
CipherText = encPieces[1];
Mac = encPieces[2];
break;
case EncryptionType.Rsa2048_OaepSha256_B64:
case EncryptionType.Rsa2048_OaepSha1_B64:
if(encPieces.Length != 1)
{
throw new ArgumentException("Malformed encPieces.");
}
CipherText = encPieces[0];
break;
default:
throw new ArgumentException("Unknown encType.");
}
EncryptedString = encryptedString;
}
public CipherString(string initializationVector, string cipherText, string mac = null)
public CipherString(EncryptionType encryptionType, string initializationVector, string cipherText, string mac = null)
{
if(string.IsNullOrWhiteSpace(initializationVector))
{
@@ -30,40 +82,47 @@ namespace Bit.App.Models
throw new ArgumentNullException(nameof(cipherText));
}
EncryptionType = encryptionType;
EncryptedString = string.Format("{0}|{1}", initializationVector, cipherText);
if(!string.IsNullOrWhiteSpace(mac))
{
EncryptedString = string.Format("{0}|{1}", EncryptedString, mac);
}
}
public string EncryptedString { get; private set; }
public string InitializationVector => EncryptedString?.Split('|')[0] ?? null;
public string CipherText => EncryptedString?.Split('|')[1] ?? null;
public string Mac
{
get
if(EncryptionType != EncryptionType.AesCbc256_B64)
{
var pieces = EncryptedString?.Split('|') ?? new string[0];
if(pieces.Length > 2)
{
return pieces[2];
}
return null;
EncryptedString = string.Format("{0}.{1}", (byte)EncryptionType, EncryptedString);
}
CipherText = cipherText;
InitializationVector = initializationVector;
Mac = mac;
}
public byte[] InitializationVectorBytes => Convert.FromBase64String(InitializationVector);
public EncryptionType EncryptionType { get; private set; }
public string EncryptedString { get; private set; }
public string InitializationVector { get; private set; }
public string CipherText { get; private set; }
public string Mac { get; private set; }
public byte[] InitializationVectorBytes => string.IsNullOrWhiteSpace(InitializationVector) ?
null : Convert.FromBase64String(InitializationVector);
public byte[] CipherTextBytes => Convert.FromBase64String(CipherText);
public byte[] MacBytes => Mac == null ? null : Convert.FromBase64String(Mac);
public string Decrypt()
public string Decrypt(string orgId = null)
{
if(_decryptedValue == null)
{
var cryptoService = Resolver.Resolve<ICryptoService>();
_decryptedValue = cryptoService.Decrypt(this);
if(!string.IsNullOrWhiteSpace(orgId))
{
_decryptedValue = cryptoService.Decrypt(this, cryptoService.GetOrgKey(orgId));
}
else
{
_decryptedValue = cryptoService.Decrypt(this);
}
}
return _decryptedValue;

View File

@@ -26,21 +26,6 @@ namespace Bit.App.Models.Data
RevisionDateTime = folder.RevisionDate;
}
public FolderData(CipherResponse cipher, string userId)
{
if(cipher.Type != Enums.CipherType.Folder)
{
throw new ArgumentException(nameof(cipher.Type));
}
var data = cipher.Data.ToObject<LoginDataModel>();
Id = cipher.Id;
UserId = userId;
Name = data.Name;
RevisionDateTime = cipher.RevisionDate;
}
[PrimaryKey]
public string Id { get; set; }
[Indexed]

View File

@@ -16,6 +16,7 @@ namespace Bit.App.Models.Data
Id = login.Id;
FolderId = login.FolderId;
UserId = userId;
OrganizationId = login.OrganizationId;
Name = login.Name?.EncryptedString;
Uri = login.Uri?.EncryptedString;
Username = login.Username?.EncryptedString;
@@ -29,6 +30,7 @@ namespace Bit.App.Models.Data
Id = login.Id;
FolderId = login.FolderId;
UserId = userId;
OrganizationId = login.OrganizationId;
Name = login.Name;
Uri = login.Uri;
Username = login.Username;
@@ -50,6 +52,7 @@ namespace Bit.App.Models.Data
Id = cipher.Id;
FolderId = cipher.FolderId;
UserId = userId;
OrganizationId = cipher.OrganizationId;
Name = data.Name;
Uri = data.Uri;
Username = data.Username;
@@ -64,6 +67,7 @@ namespace Bit.App.Models.Data
public string FolderId { get; set; }
[Indexed]
public string UserId { get; set; }
public string OrganizationId { get; set; }
public string Name { get; set; }
public string Uri { get; set; }
public string Username { get; set; }

View File

@@ -11,6 +11,8 @@ namespace Bit.App.Models
public Login(LoginData data)
{
Id = data.Id;
UserId = data.UserId;
OrganizationId = data.OrganizationId;
FolderId = data.FolderId;
Name = data.Name != null ? new CipherString(data.Name) : null;
Uri = data.Uri != null ? new CipherString(data.Uri) : null;
@@ -23,6 +25,8 @@ namespace Bit.App.Models
public Login(LoginResponse response)
{
Id = response.Id;
UserId = response.UserId;
OrganizationId = response.OrganizationId;
FolderId = response.FolderId;
Name = response.Name != null ? new CipherString(response.Name) : null;
Uri = response.Uri != null ? new CipherString(response.Uri) : null;
@@ -32,6 +36,8 @@ namespace Bit.App.Models
Favorite = response.Favorite;
}
public string UserId { get; set; }
public string OrganizationId { get; set; }
public string FolderId { get; set; }
public CipherString Uri { get; set; }
public CipherString Username { get; set; }

View File

@@ -0,0 +1,15 @@
namespace Bit.App.Models
{
public class LoginResult
{
public bool Success { get; set; }
public string ErrorMessage { get; set; }
}
public class FullLoginResult : LoginResult
{
public bool TwoFactorRequired { get; set; }
public SymmetricCryptoKey Key { get; set; }
public string MasterPasswordHash { get; set; }
}
}

View File

@@ -11,14 +11,16 @@ namespace Bit.App.Models.Page
public Login(Models.Login login)
{
Id = login.Id;
Shared = !string.IsNullOrWhiteSpace(login.OrganizationId);
FolderId = login.FolderId;
Name = login.Name?.Decrypt();
Username = login.Username?.Decrypt() ?? " ";
Password = new Lazy<string>(() => login.Password?.Decrypt());
Uri = new Lazy<string>(() => login.Uri?.Decrypt());
Name = login.Name?.Decrypt(login.OrganizationId);
Username = login.Username?.Decrypt(login.OrganizationId) ?? " ";
Password = new Lazy<string>(() => login.Password?.Decrypt(login.OrganizationId));
Uri = new Lazy<string>(() => login.Uri?.Decrypt(login.OrganizationId));
}
public string Id { get; set; }
public bool Shared { get; set; }
public string FolderId { get; set; }
public string Name { get; set; }
public string Username { get; set; }

View File

@@ -171,11 +171,11 @@ namespace Bit.App.Models.Page
public void Update(Login login)
{
Name = login.Name?.Decrypt();
Username = login.Username?.Decrypt();
Password = login.Password?.Decrypt();
Uri = login.Uri?.Decrypt();
Notes = login.Notes?.Decrypt();
Name = login.Name?.Decrypt(login.OrganizationId);
Username = login.Username?.Decrypt(login.OrganizationId);
Password = login.Password?.Decrypt(login.OrganizationId);
Uri = login.Uri?.Decrypt(login.OrganizationId);
Notes = login.Notes?.Decrypt(login.OrganizationId);
}
}
}

View File

@@ -8,19 +8,24 @@ namespace Bit.App.Models
public PushType Type { get; set; }
}
public abstract class SyncPushNotification : PushNotification
{
public string UserId { get; set; }
}
public class SyncCipherPushNotification : SyncPushNotification
public class SyncCipherPushNotification : PushNotification
{
public string Id { get; set; }
public string UserId { get; set; }
public string OrganizationId { get; set; }
public DateTime RevisionDate { get; set; }
}
public class SyncCiphersPushNotification : SyncPushNotification
public class SyncFolderPushNotification : PushNotification
{
public string Id { get; set; }
public string UserId { get; set; }
public DateTime RevisionDate { get; set; }
}
public class SyncUserPushNotification : PushNotification
{
public string UserId { get; set; }
public DateTime Date { get; set; }
}
}

View File

@@ -0,0 +1,62 @@
using Bit.App.Enums;
using System;
using System.Linq;
namespace Bit.App.Models
{
public class SymmetricCryptoKey
{
public SymmetricCryptoKey(byte[] rawBytes, EncryptionType? encType = null)
{
if(rawBytes == null || rawBytes.Length == 0)
{
throw new Exception("Must provide keyBytes.");
}
if(encType == null)
{
if(rawBytes.Length == 32)
{
encType = EncryptionType.AesCbc256_B64;
}
else if(rawBytes.Length == 64)
{
encType = EncryptionType.AesCbc256_HmacSha256_B64;
}
else
{
throw new Exception("Unable to determine encType.");
}
}
EncryptionType = encType.Value;
Key = rawBytes;
if(EncryptionType == EncryptionType.AesCbc256_B64 && Key.Length == 32)
{
EncKey = Key;
MacKey = null;
}
else if(EncryptionType == EncryptionType.AesCbc128_HmacSha256_B64 && Key.Length == 32)
{
EncKey = Key.Take(16).ToArray();
MacKey = Key.Skip(16).Take(16).ToArray();
}
else if(EncryptionType == EncryptionType.AesCbc256_HmacSha256_B64 && Key.Length == 64)
{
EncKey = Key.Take(32).ToArray();
MacKey = Key.Skip(32).Take(32).ToArray();
}
else
{
throw new Exception("Unsupported encType/key length.");
}
}
public byte[] Key { get; set; }
public string B64Key => Convert.ToBase64String(Key);
public byte[] EncKey { get; set; }
public byte[] MacKey { get; set; }
public EncryptionType EncryptionType { get; set; }
}
}

View File

@@ -6,6 +6,7 @@ using Xamarin.Forms;
using XLabs.Ioc;
using Plugin.Fingerprint.Abstractions;
using Plugin.Settings.Abstractions;
using Bit.App.Abstractions;
namespace Bit.App.Pages
{
@@ -13,6 +14,7 @@ namespace Bit.App.Pages
{
private readonly IFingerprint _fingerprint;
private readonly ISettings _settings;
private readonly IAppSettingsService _appSettings;
private readonly bool _checkFingerprintImmediately;
public LockFingerprintPage(bool checkFingerprintImmediately)
@@ -20,6 +22,7 @@ namespace Bit.App.Pages
_checkFingerprintImmediately = checkFingerprintImmediately;
_fingerprint = Resolver.Resolve<IFingerprint>();
_settings = Resolver.Resolve<ISettings>();
_appSettings = Resolver.Resolve<IAppSettingsService>();
Init();
}
@@ -79,7 +82,7 @@ namespace Bit.App.Pages
var result = await _fingerprint.AuthenticateAsync(AppResources.FingerprintDirection);
if(result.Authenticated)
{
_settings.AddOrUpdateValue(Constants.Locked, false);
_appSettings.Locked = false;
await Navigation.PopModalAsync();
}
else if(result.Status == FingerprintAuthenticationResultStatus.FallbackRequested)

View File

@@ -6,20 +6,19 @@ using Xamarin.Forms;
using XLabs.Ioc;
using Bit.App.Controls;
using System.Linq;
using Plugin.Settings.Abstractions;
namespace Bit.App.Pages
{
public class LockPasswordPage : BaseLockPage
{
private readonly IAuthService _authService;
private readonly ISettings _settings;
private readonly IAppSettingsService _appSettingsService;
private readonly ICryptoService _cryptoService;
public LockPasswordPage()
{
_authService = Resolver.Resolve<IAuthService>();
_settings = Resolver.Resolve<ISettings>();
_appSettingsService = Resolver.Resolve<IAppSettingsService>();
_cryptoService = Resolver.Resolve<ICryptoService>();
Init();
@@ -49,7 +48,7 @@ namespace Bit.App.Pages
NoFooter = true,
Root = new TableRoot
{
new TableSection
new TableSection(" ")
{
PasswordCell
}
@@ -120,9 +119,9 @@ namespace Bit.App.Pages
}
var key = _cryptoService.MakeKeyFromPassword(PasswordCell.Entry.Text, _authService.Email);
if(key.SequenceEqual(_cryptoService.Key))
if(key.Key.SequenceEqual(_cryptoService.Key.Key))
{
_settings.AddOrUpdateValue(Constants.Locked, false);
_appSettingsService.Locked = false;
await Navigation.PopModalAsync();
}
else

View File

@@ -1,11 +1,8 @@
using System;
using System.Threading.Tasks;
using Acr.UserDialogs;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Xamarin.Forms;
using XLabs.Ioc;
using Plugin.Settings.Abstractions;
using Bit.App.Models.Page;
using Bit.App.Controls;
@@ -14,13 +11,13 @@ namespace Bit.App.Pages
public class LockPinPage : BaseLockPage
{
private readonly IAuthService _authService;
private readonly ISettings _settings;
private readonly IAppSettingsService _appSettingsService;
private TapGestureRecognizer _tgr;
public LockPinPage()
{
_authService = Resolver.Resolve<IAuthService>();
_settings = Resolver.Resolve<ISettings>();
_appSettingsService = Resolver.Resolve<IAppSettingsService>();
Init();
}
@@ -96,7 +93,7 @@ namespace Bit.App.Pages
{
if(Model.PIN == _authService.PIN)
{
_settings.AddOrUpdateValue(Constants.Locked, false);
_appSettingsService.Locked = false;
PinControl.Entry.Unfocus();
Navigation.PopModalAsync();
}

View File

@@ -1,8 +1,6 @@
using System;
using System.Linq;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Models.Api;
using Bit.App.Resources;
using Xamarin.Forms;
using XLabs.Ioc;
@@ -15,11 +13,7 @@ namespace Bit.App.Pages
{
public class LoginPage : ExtendedContentPage
{
private ICryptoService _cryptoService;
private IAuthService _authService;
private ITokenService _tokenService;
private IDeviceInfoService _deviceInfoService;
private IAppIdService _appIdService;
private IUserDialogs _userDialogs;
private ISyncService _syncService;
private ISettings _settings;
@@ -31,11 +25,7 @@ namespace Bit.App.Pages
: base(updateActivity: false)
{
_email = email;
_cryptoService = Resolver.Resolve<ICryptoService>();
_authService = Resolver.Resolve<IAuthService>();
_tokenService = Resolver.Resolve<ITokenService>();
_deviceInfoService = Resolver.Resolve<IDeviceInfoService>();
_appIdService = Resolver.Resolve<IAppIdService>();
_userDialogs = Resolver.Resolve<IUserDialogs>();
_syncService = Resolver.Resolve<ISyncService>();
_settings = Resolver.Resolve<ISettings>();
@@ -82,10 +72,11 @@ namespace Bit.App.Pages
HasUnevenRows = true,
EnableSelection = true,
NoFooter = true,
//NoHeader = true,
VerticalOptions = LayoutOptions.Start,
Root = new TableRoot
{
new TableSection()
new TableSection(" ")
{
EmailCell,
PasswordCell
@@ -188,40 +179,22 @@ namespace Bit.App.Pages
return;
}
var normalizedEmail = EmailCell.Entry.Text.ToLower();
var key = _cryptoService.MakeKeyFromPassword(PasswordCell.Entry.Text, normalizedEmail);
var request = new TokenRequest
{
Email = normalizedEmail,
MasterPasswordHash = _cryptoService.HashPasswordBase64(key, PasswordCell.Entry.Text),
Device = new DeviceRequest(_appIdService, _deviceInfoService)
};
_userDialogs.ShowLoading(AppResources.LoggingIn, MaskType.Black);
var response = await _authService.TokenPostAsync(request);
var result = await _authService.TokenPostAsync(EmailCell.Entry.Text, PasswordCell.Entry.Text);
_userDialogs.HideLoading();
if(!response.Succeeded)
if(!result.Success)
{
await DisplayAlert(AppResources.AnErrorHasOccurred, response.Errors.FirstOrDefault()?.Message, AppResources.Ok);
await DisplayAlert(AppResources.AnErrorHasOccurred, result.ErrorMessage, AppResources.Ok);
return;
}
if(response.Result.TwoFactorProviders != null && response.Result.TwoFactorProviders.Count > 0)
if(result.TwoFactorRequired)
{
_googleAnalyticsService.TrackAppEvent("LoggedIn To Two-step");
await Navigation.PushAsync(new LoginTwoFactorPage(request.Email, request.MasterPasswordHash, key));
await Navigation.PushAsync(new LoginTwoFactorPage(EmailCell.Entry.Text, result.MasterPasswordHash, result.Key));
return;
}
_cryptoService.Key = key;
_tokenService.Token = response.Result.AccessToken;
_tokenService.RefreshToken = response.Result.RefreshToken;
_authService.UserId = _tokenService.TokenUserId;
_authService.Email = _tokenService.TokenEmail;
_settings.AddOrUpdateValue(Constants.LastLoginEmail, _authService.Email);
_googleAnalyticsService.RefreshUserId();
_googleAnalyticsService.TrackAppEvent("LoggedIn");
if(Device.OS == TargetPlatform.Android)

View File

@@ -1,49 +1,37 @@
using System;
using System.Linq;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Models.Api;
using Bit.App.Resources;
using Xamarin.Forms;
using XLabs.Ioc;
using Acr.UserDialogs;
using System.Threading.Tasks;
using Plugin.Settings.Abstractions;
using PushNotification.Plugin.Abstractions;
using Bit.App.Models;
namespace Bit.App.Pages
{
public class LoginTwoFactorPage : ExtendedContentPage
{
private ICryptoService _cryptoService;
private IAuthService _authService;
private ITokenService _tokenService;
private IDeviceInfoService _deviceInfoService;
private IAppIdService _appIdService;
private IUserDialogs _userDialogs;
private ISyncService _syncService;
private ISettings _settings;
private IGoogleAnalyticsService _googleAnalyticsService;
private IPushNotification _pushNotification;
private readonly string _email;
private readonly string _masterPasswordHash;
private readonly byte[] _key;
private readonly SymmetricCryptoKey _key;
public LoginTwoFactorPage(string email, string masterPasswordHash, byte[] key)
public LoginTwoFactorPage(string email, string masterPasswordHash, SymmetricCryptoKey key)
: base(updateActivity: false)
{
_email = email;
_masterPasswordHash = masterPasswordHash;
_key = key;
_cryptoService = Resolver.Resolve<ICryptoService>();
_authService = Resolver.Resolve<IAuthService>();
_tokenService = Resolver.Resolve<ITokenService>();
_deviceInfoService = Resolver.Resolve<IDeviceInfoService>();
_appIdService = Resolver.Resolve<IAppIdService>();
_userDialogs = Resolver.Resolve<IUserDialogs>();
_syncService = Resolver.Resolve<ISyncService>();
_settings = Resolver.Resolve<ISettings>();
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
_pushNotification = Resolver.Resolve<IPushNotification>();
@@ -75,7 +63,7 @@ namespace Bit.App.Pages
VerticalOptions = LayoutOptions.Start,
Root = new TableRoot
{
new TableSection()
new TableSection(" ")
{
CodeCell
}
@@ -117,7 +105,7 @@ namespace Bit.App.Pages
var continueToolbarItem = new ToolbarItem(AppResources.Continue, null, async () =>
{
await LogIn();
await LogInAsync();
}, ToolbarItemOrder.Default, 0);
ToolbarItems.Add(continueToolbarItem);
@@ -147,10 +135,10 @@ namespace Bit.App.Pages
private async void Entry_Completed(object sender, EventArgs e)
{
await LogIn();
await LogInAsync();
}
private async Task LogIn()
private async Task LogInAsync()
{
if(string.IsNullOrWhiteSpace(CodeCell.Entry.Text))
{
@@ -159,31 +147,15 @@ namespace Bit.App.Pages
return;
}
var request = new TokenRequest
{
Email = _email,
MasterPasswordHash = _masterPasswordHash,
Token = CodeCell.Entry.Text.Replace(" ", ""),
Provider = 0, // Authenticator app (only 1 provider for now, so hard coded)
Device = new DeviceRequest(_appIdService, _deviceInfoService)
};
_userDialogs.ShowLoading(AppResources.ValidatingCode, MaskType.Black);
var response = await _authService.TokenPostAsync(request);
var response = await _authService.TokenPostTwoFactorAsync(CodeCell.Entry.Text, _email, _masterPasswordHash, _key);
_userDialogs.HideLoading();
if(!response.Succeeded)
if(!response.Success)
{
await DisplayAlert(AppResources.AnErrorHasOccurred, response.Errors.FirstOrDefault()?.Message, AppResources.Ok);
await DisplayAlert(AppResources.AnErrorHasOccurred, response.ErrorMessage, AppResources.Ok);
return;
}
_cryptoService.Key = _key;
_tokenService.Token = response.Result.AccessToken;
_tokenService.RefreshToken = response.Result.RefreshToken;
_authService.UserId = _tokenService.TokenUserId;
_authService.Email = _tokenService.TokenEmail;
_settings.AddOrUpdateValue(Constants.LastLoginEmail, _authService.Email);
_googleAnalyticsService.RefreshUserId();
_googleAnalyticsService.TrackAppEvent("LoggedIn From Two-step");
if(Device.OS == TargetPlatform.Android)

View File

@@ -49,7 +49,7 @@ namespace Bit.App.Pages
VerticalOptions = LayoutOptions.Start,
Root = new TableRoot
{
new TableSection()
new TableSection(" ")
{
EmailCell
}

View File

@@ -66,7 +66,7 @@ namespace Bit.App.Pages
{
Root = new TableRoot
{
new TableSection
new TableSection(" ")
{
EmailCell,
PasswordCell
@@ -88,7 +88,7 @@ namespace Bit.App.Pages
NoHeader = true,
Root = new TableRoot
{
new TableSection
new TableSection(" ")
{
ConfirmPasswordCell,
PasswordHintCell

View File

@@ -59,7 +59,7 @@ namespace Bit.App.Pages
HasUnevenRows = true,
Root = new TableRoot
{
new TableSection
new TableSection(" ")
{
CreditsCell
}

View File

@@ -41,7 +41,7 @@ namespace Bit.App.Pages
HasUnevenRows = true,
Root = new TableRoot
{
new TableSection()
new TableSection(" ")
{
NameCell
}

View File

@@ -54,11 +54,11 @@ namespace Bit.App.Pages
VerticalOptions = LayoutOptions.Start,
Root = new TableRoot
{
new TableSection
new TableSection(" ")
{
NameCell
},
new TableSection
new TableSection(" ")
{
DeleteCell
}
@@ -152,7 +152,6 @@ namespace Bit.App.Pages
return;
}
_userDialogs.ShowLoading(AppResources.Deleting, MaskType.Black);
var deleteTask = await _folderService.DeleteAsync(_folderId);
_userDialogs.HideLoading();

View File

@@ -38,7 +38,7 @@ namespace Bit.App.Pages
{
Root = new TableRoot
{
new TableSection
new TableSection(" ")
{
EmailCell
}
@@ -61,7 +61,7 @@ namespace Bit.App.Pages
NoHeader = true,
Root = new TableRoot
{
new TableSection
new TableSection(" ")
{
WebsiteCell
}
@@ -84,7 +84,7 @@ namespace Bit.App.Pages
NoHeader = true,
Root = new TableRoot
{
new TableSection
new TableSection(" ")
{
BugCell
}

View File

@@ -40,6 +40,7 @@ namespace Bit.App.Pages
private ExtendedTextCell TwoStepCell { get; set; }
private ExtendedTextCell ChangeMasterPasswordCell { get; set; }
private ExtendedTextCell ChangeEmailCell { get; set; }
private ExtendedSwitchCell AnalyticsCell { get; set; }
private ExtendedTextCell FoldersCell { get; set; }
private ExtendedTextCell SyncCell { get; set; }
private ExtendedTextCell LockCell { get; set; }
@@ -103,6 +104,12 @@ namespace Bit.App.Pages
ShowDisclousure = true
};
AnalyticsCell = new ExtendedSwitchCell
{
Text = AppResources.DisableGA,
On = _settings.GetValueOrDefault(Constants.SettingGaOptOut, false)
};
FoldersCell = new ExtendedTextCell
{
Text = AppResources.Folders,
@@ -139,6 +146,7 @@ namespace Bit.App.Pages
var otherSection = new TableSection(AppResources.Other)
{
AnalyticsCell,
AboutCell,
HelpCell
};
@@ -193,6 +201,7 @@ namespace Bit.App.Pages
base.OnAppearing();
PinCell.OnChanged += PinCell_Changed;
AnalyticsCell.OnChanged += AnalyticsCell_Changed;
LockOptionsCell.Tapped += LockOptionsCell_Tapped;
TwoStepCell.Tapped += TwoStepCell_Tapped;
ChangeMasterPasswordCell.Tapped += ChangeMasterPasswordCell_Tapped;
@@ -226,6 +235,7 @@ namespace Bit.App.Pages
base.OnDisappearing();
PinCell.OnChanged -= PinCell_Changed;
AnalyticsCell.OnChanged -= AnalyticsCell_Changed;
LockOptionsCell.Tapped -= LockOptionsCell_Tapped;
TwoStepCell.Tapped -= TwoStepCell_Tapped;
ChangeMasterPasswordCell.Tapped -= ChangeMasterPasswordCell_Tapped;
@@ -411,6 +421,18 @@ namespace Bit.App.Pages
}
}
private void AnalyticsCell_Changed(object sender, ToggledEventArgs e)
{
var cell = sender as ExtendedSwitchCell;
if (cell == null)
{
return;
}
_settings.AddOrUpdateValue(Constants.SettingGaOptOut, cell.On);
_googleAnalyticsService.SetAppOptOut(cell.On);
}
private void PinEntered(SettingsPinPage page)
{
page.PinControl.Entry.Unfocus();

View File

@@ -25,6 +25,7 @@ namespace Bit.App.Pages
public ToolsViewCell GeneratorCell { get; set; }
public ToolsViewCell WebCell { get; set; }
public ToolsViewCell ShareCell { get; set; }
public ToolsViewCell ImportCell { get; set; }
public ToolsViewCell ExtensionCell { get; set; }
public ToolsViewCell AutofillCell { get; set; }
@@ -34,9 +35,10 @@ namespace Bit.App.Pages
GeneratorCell = new ToolsViewCell(AppResources.PasswordGenerator, AppResources.PasswordGeneratorDescription,
"refresh");
WebCell = new ToolsViewCell(AppResources.WebVault, AppResources.WebVaultDescription, "globe");
ShareCell = new ToolsViewCell(AppResources.ShareVault, AppResources.ShareVaultDescription, "share_tools");
ImportCell = new ToolsViewCell(AppResources.ImportLogins, AppResources.ImportLoginsDescription, "cloudup");
var section = new TableSection { GeneratorCell };
var section = new TableSection(" ") { GeneratorCell };
if(Device.OS == TargetPlatform.iOS)
{
@@ -46,13 +48,13 @@ namespace Bit.App.Pages
}
else
{
AutofillCell = new ToolsViewCell(
string.Format("{0} ({1})", AppResources.BitwardenAutofillService, AppResources.Beta),
AutofillCell = new ToolsViewCell(AppResources.BitwardenAutofillService,
AppResources.BitwardenAutofillServiceDescription, "upload");
section.Add(AutofillCell);
}
section.Add(WebCell);
section.Add(ShareCell);
section.Add(ImportCell);
var table = new ExtendedTableView
@@ -81,6 +83,7 @@ namespace Bit.App.Pages
base.OnAppearing();
GeneratorCell.Tapped += GeneratorCell_Tapped;
WebCell.Tapped += WebCell_Tapped;
ShareCell.Tapped += ShareCell_Tapped;
ImportCell.Tapped += ImportCell_Tapped;
if(ExtensionCell != null)
{
@@ -97,6 +100,7 @@ namespace Bit.App.Pages
base.OnDisappearing();
GeneratorCell.Tapped -= GeneratorCell_Tapped;
WebCell.Tapped -= WebCell_Tapped;
ShareCell.Tapped -= ShareCell_Tapped;
ImportCell.Tapped -= ImportCell_Tapped;
if(ExtensionCell != null)
{
@@ -130,6 +134,12 @@ namespace Bit.App.Pages
Device.OpenUri(new Uri("https://vault.bitwarden.com"));
}
private void ShareCell_Tapped(object sender, EventArgs e)
{
_googleAnalyticsService.TrackAppEvent("OpenedTool", "Share");
Device.OpenUri(new Uri("https://vault.bitwarden.com/#/?org=free"));
}
private async void ImportCell_Tapped(object sender, EventArgs e)
{
if(!await _userDialogs.ConfirmAsync(AppResources.ImportLoginsConfirmation, null, AppResources.Yes,

View File

@@ -73,7 +73,7 @@ namespace Bit.App.Pages
NoHeader = true,
Root = new TableRoot
{
new TableSection
new TableSection(" ")
{
RegenerateCell,
CopyCell

View File

@@ -77,19 +77,19 @@ namespace Bit.App.Pages
EnableSelection = false,
Root = new TableRoot
{
new TableSection
new TableSection(" ")
{
UppercaseCell,
LowercaseCell,
NumbersCell,
SpecialCell
},
new TableSection
new TableSection(" ")
{
NumbersMinCell,
SpecialMinCell
},
new TableSection
new TableSection(" ")
{
AvoidAmbiguousCell
}

View File

@@ -55,7 +55,7 @@ namespace Bit.App.Pages
private void Init()
{
NotesCell = new FormEditorCell(height: 90);
NotesCell = new FormEditorCell(height: 180);
PasswordCell = new FormEntryCell(AppResources.Password, isPassword: true, nextElement: NotesCell.Editor,
useButton: true);
PasswordCell.Button.Image = "eye";
@@ -111,7 +111,7 @@ namespace Bit.App.Pages
PasswordCell,
GenerateCell
},
new TableSection
new TableSection(" ")
{
FolderCell,
favoriteCell
@@ -230,6 +230,8 @@ namespace Bit.App.Pages
AppResources.Ok);
}
}
NameCell.Entry.FocusWithDelay();
}
protected override void OnDisappearing()

View File

@@ -31,13 +31,14 @@ namespace Bit.App.Pages
Uri uri;
DomainName domainName;
if(!System.Uri.TryCreate(uriString, UriKind.Absolute, out uri) ||
if(uriString?.StartsWith(Constants.AndroidAppProtocol) ?? false)
{
_name = uriString.Substring(Constants.AndroidAppProtocol.Length);
}
else if(!System.Uri.TryCreate(uriString, UriKind.Absolute, out uri) ||
!DomainName.TryParse(uri.Host, out domainName))
{
if(uriString != null && uriString.StartsWith(Constants.AndroidAppProtocol))
{
_name = uriString.Substring(Constants.AndroidAppProtocol.Length);
}
_name = "--";
}
else
{

View File

@@ -50,26 +50,26 @@ namespace Bit.App.Pages
return;
}
NotesCell = new FormEditorCell(height: 90);
NotesCell.Editor.Text = login.Notes?.Decrypt();
NotesCell = new FormEditorCell(height: 180);
NotesCell.Editor.Text = login.Notes?.Decrypt(login.OrganizationId);
PasswordCell = new FormEntryCell(AppResources.Password, isPassword: true, nextElement: NotesCell.Editor,
useButton: true);
PasswordCell.Entry.Text = login.Password?.Decrypt();
PasswordCell.Entry.Text = login.Password?.Decrypt(login.OrganizationId);
PasswordCell.Button.Image = "eye";
PasswordCell.Entry.DisableAutocapitalize = true;
PasswordCell.Entry.Autocorrect = false;
PasswordCell.Entry.FontFamily = Device.OnPlatform(iOS: "Courier", Android: "monospace", WinPhone: "Courier");
UsernameCell = new FormEntryCell(AppResources.Username, nextElement: PasswordCell.Entry);
UsernameCell.Entry.Text = login.Username?.Decrypt();
UsernameCell.Entry.Text = login.Username?.Decrypt(login.OrganizationId);
UsernameCell.Entry.DisableAutocapitalize = true;
UsernameCell.Entry.Autocorrect = false;
UriCell = new FormEntryCell(AppResources.URI, Keyboard.Url, nextElement: UsernameCell.Entry);
UriCell.Entry.Text = login.Uri?.Decrypt();
UriCell.Entry.Text = login.Uri?.Decrypt(login.OrganizationId);
NameCell = new FormEntryCell(AppResources.Name, nextElement: UriCell.Entry);
NameCell.Entry.Text = login.Name?.Decrypt();
NameCell.Entry.Text = login.Name?.Decrypt(login.OrganizationId);
GenerateCell = new ExtendedTextCell
{
@@ -118,7 +118,7 @@ namespace Bit.App.Pages
PasswordCell,
GenerateCell
},
new TableSection
new TableSection(" ")
{
FolderCell,
favoriteCell
@@ -127,7 +127,7 @@ namespace Bit.App.Pages
{
NotesCell
},
new TableSection
new TableSection(" ")
{
DeleteCell
}
@@ -159,11 +159,11 @@ namespace Bit.App.Pages
return;
}
login.Uri = UriCell.Entry.Text?.Encrypt();
login.Name = NameCell.Entry.Text?.Encrypt();
login.Username = UsernameCell.Entry.Text?.Encrypt();
login.Password = PasswordCell.Entry.Text?.Encrypt();
login.Notes = NotesCell.Editor.Text?.Encrypt();
login.Uri = UriCell.Entry.Text?.Encrypt(login.OrganizationId);
login.Name = NameCell.Entry.Text?.Encrypt(login.OrganizationId);
login.Username = UsernameCell.Entry.Text?.Encrypt(login.OrganizationId);
login.Password = PasswordCell.Entry.Text?.Encrypt(login.OrganizationId);
login.Notes = NotesCell.Editor.Text?.Encrypt(login.OrganizationId);
login.Favorite = favoriteCell.On;
if(FolderCell.Picker.SelectedIndex > 0)

View File

@@ -34,6 +34,7 @@ namespace Bit.App.Pages
public LabeledValueCell UsernameCell { get; set; }
public LabeledValueCell PasswordCell { get; set; }
public LabeledValueCell UriCell { get; set; }
public LabeledValueCell NotesCell { get; set; }
private EditLoginToolBarItem EditItem { get; set; }
private void Init()
@@ -76,9 +77,9 @@ namespace Bit.App.Pages
UriCell.Button1.Command = new Command(() => Device.OpenUri(new Uri(Model.Uri)));
// Notes
var notesCell = new LabeledValueCell();
notesCell.Value.SetBinding<VaultViewLoginPageModel>(Label.TextProperty, s => s.Notes);
notesCell.Value.LineBreakMode = LineBreakMode.WordWrap;
NotesCell = new LabeledValueCell();
NotesCell.Value.SetBinding<VaultViewLoginPageModel>(Label.TextProperty, s => s.Notes);
NotesCell.Value.LineBreakMode = LineBreakMode.WordWrap;
LoginInformationSection = new TableSection(AppResources.LoginInformation)
{
@@ -87,7 +88,7 @@ namespace Bit.App.Pages
NotesSection = new TableSection(AppResources.Notes)
{
notesCell
NotesCell
};
Table = new ExtendedTableView
@@ -124,6 +125,7 @@ namespace Bit.App.Pages
protected async override void OnAppearing()
{
NotesCell.Tapped += NotesCell_Tapped;
EditItem.InitEvents();
var login = await _loginService.GetByIdAsync(_loginId);
@@ -176,9 +178,15 @@ namespace Bit.App.Pages
protected override void OnDisappearing()
{
NotesCell.Tapped -= NotesCell_Tapped;
EditItem.Dispose();
}
private void NotesCell_Tapped(object sender, EventArgs e)
{
Copy(Model.Notes, AppResources.Notes);
}
private void Copy(string copyText, string alertLabel)
{
_clipboardService.CopyToClipboard(copyText);

View File

@@ -4,6 +4,7 @@ using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Models.Api;
using Plugin.Connectivity.Abstractions;
using Newtonsoft.Json;
namespace Bit.App.Repositories
{
@@ -84,7 +85,7 @@ namespace Bit.App.Repositories
}
}
public virtual async Task<ApiResult<DateTime?>> GetAccountRevisionDate()
public virtual async Task<ApiResult<DateTime?>> GetAccountRevisionDateAsync()
{
if(!Connectivity.IsConnected)
{
@@ -132,5 +133,85 @@ namespace Bit.App.Repositories
}
}
}
public virtual async Task<ApiResult<ProfileResponse>> GetProfileAsync()
{
if(!Connectivity.IsConnected)
{
return HandledNotConnected<ProfileResponse>();
}
var tokenStateResponse = await HandleTokenStateAsync<ProfileResponse>();
if(!tokenStateResponse.Succeeded)
{
return tokenStateResponse;
}
using(var client = HttpService.Client)
{
var requestMessage = new TokenHttpRequestMessage()
{
Method = HttpMethod.Get,
RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "/profile")),
};
try
{
var response = await client.SendAsync(requestMessage).ConfigureAwait(false);
if(!response.IsSuccessStatusCode)
{
return await HandleErrorAsync<ProfileResponse>(response).ConfigureAwait(false);
}
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var responseObj = JsonConvert.DeserializeObject<ProfileResponse>(responseContent);
return ApiResult<ProfileResponse>.Success(responseObj, response.StatusCode);
}
catch
{
return HandledWebException<ProfileResponse>();
}
}
}
public virtual async Task<ApiResult<KeysResponse>> GetKeys()
{
if(!Connectivity.IsConnected)
{
return HandledNotConnected<KeysResponse>();
}
var tokenStateResponse = await HandleTokenStateAsync<KeysResponse>();
if(!tokenStateResponse.Succeeded)
{
return tokenStateResponse;
}
using(var client = HttpService.Client)
{
var requestMessage = new TokenHttpRequestMessage()
{
Method = HttpMethod.Get,
RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "/keys")),
};
try
{
var response = await client.SendAsync(requestMessage).ConfigureAwait(false);
if(!response.IsSuccessStatusCode)
{
return await HandleErrorAsync<KeysResponse>(response).ConfigureAwait(false);
}
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var responseObj = JsonConvert.DeserializeObject<KeysResponse>(responseContent);
return ApiResult<KeysResponse>.Success(responseObj, response.StatusCode);
}
catch
{
return HandledWebException<KeysResponse>();
}
}
}
}
}

View File

@@ -77,7 +77,8 @@ namespace Bit.App.Repositories
var requestMessage = new TokenHttpRequestMessage()
{
Method = HttpMethod.Get,
RequestUri = new Uri(client.BaseAddress, ApiRoute),
RequestUri = new Uri(client.BaseAddress,
string.Format("{0}?includeFolders=false&includeShared=true", ApiRoute)),
};
try
@@ -98,45 +99,5 @@ namespace Bit.App.Repositories
}
}
}
public virtual async Task<ApiResult<CipherHistoryResponse>> GetByRevisionDateWithHistoryAsync(DateTime since)
{
if(!Connectivity.IsConnected)
{
return HandledNotConnected<CipherHistoryResponse>();
}
var tokenStateResponse = await HandleTokenStateAsync<CipherHistoryResponse>();
if(!tokenStateResponse.Succeeded)
{
return tokenStateResponse;
}
using(var client = HttpService.Client)
{
var requestMessage = new TokenHttpRequestMessage()
{
Method = HttpMethod.Get,
RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "/history", "?since=", since)),
};
try
{
var response = await client.SendAsync(requestMessage).ConfigureAwait(false);
if(!response.IsSuccessStatusCode)
{
return await HandleErrorAsync<CipherHistoryResponse>(response).ConfigureAwait(false);
}
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var responseObj = JsonConvert.DeserializeObject<CipherHistoryResponse>(responseContent);
return ApiResult<CipherHistoryResponse>.Success(responseObj, response.StatusCode);
}
catch
{
return HandledWebException<CipherHistoryResponse>();
}
}
}
}
}

View File

@@ -1,12 +1,7 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Models.Api;
using Newtonsoft.Json;
using Plugin.Connectivity.Abstractions;
using System.Net;
namespace Bit.App.Repositories
{
@@ -20,45 +15,5 @@ namespace Bit.App.Repositories
{ }
protected override string ApiRoute => "folders";
public virtual async Task<ApiResult<ListResponse<FolderResponse>>> GetByRevisionDateAsync(DateTime since)
{
if(!Connectivity.IsConnected)
{
return HandledNotConnected<ListResponse<FolderResponse>>();
}
var tokenStateResponse = await HandleTokenStateAsync<ListResponse<FolderResponse>>();
if(!tokenStateResponse.Succeeded)
{
return tokenStateResponse;
}
using(var client = HttpService.Client)
{
var requestMessage = new TokenHttpRequestMessage()
{
Method = HttpMethod.Get,
RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "?since=", since)),
};
try
{
var response = await client.SendAsync(requestMessage).ConfigureAwait(false);
if(!response.IsSuccessStatusCode)
{
return await HandleErrorAsync<ListResponse<FolderResponse>>(response).ConfigureAwait(false);
}
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var responseObj = JsonConvert.DeserializeObject<ListResponse<FolderResponse>>(responseContent);
return ApiResult<ListResponse<FolderResponse>>.Success(responseObj, response.StatusCode);
}
catch
{
return HandledWebException<ListResponse<FolderResponse>>();
}
}
}
}
}

View File

@@ -520,6 +520,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Disable Analytics.
/// </summary>
public static string DisableGA {
get {
return ResourceManager.GetString("DisableGA", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Do you really want to delete? This cannot be undone..
/// </summary>
@@ -827,7 +836,7 @@ namespace Bit.App.Resources {
}
/// <summary>
/// Looks up a localized string similar to (none).
/// Looks up a localized string similar to No Folder.
/// </summary>
public static string FolderNone {
get {
@@ -1654,6 +1663,24 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Share Your Vault.
/// </summary>
public static string ShareVault {
get {
return ResourceManager.GetString("ShareVault", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Create an organization to securely share your logins with other users..
/// </summary>
public static string ShareVaultDescription {
get {
return ResourceManager.GetString("ShareVaultDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Show.
/// </summary>

View File

@@ -222,7 +222,7 @@
<value>Kansio poistettu.</value>
</data>
<data name="FolderNone" xml:space="preserve">
<value>(muut)</value>
<value>Ei kansioa</value>
<comment>Logins that have no folder specified go in this special "catch-all" folder.</comment>
</data>
<data name="Folders" xml:space="preserve">
@@ -792,4 +792,4 @@
<data name="Beta" xml:space="preserve">
<value>Beeta</value>
</data>
</root>
</root>

View File

@@ -222,7 +222,7 @@
<value>Dossier supprimé.</value>
</data>
<data name="FolderNone" xml:space="preserve">
<value>(aucun)</value>
<value>Pas de dossier</value>
<comment>Logins that have no folder specified go in this special "catch-all" folder.</comment>
</data>
<data name="Folders" xml:space="preserve">
@@ -757,7 +757,7 @@
<data name="Translations" xml:space="preserve">
<value>Traductions</value>
</data>
<data name="LoginsForUri" xml:space="preserve">
<data name="LoginsForUri" xml:space="preserve">
<value>Identifiants pour {0}</value>
<comment>This is used for the autofill service. ex. "Logins for twitter.com"</comment>
</data>
@@ -816,4 +816,4 @@
<data name="BitwardenAutofillServiceSearch" xml:space="preserve">
<value>Vous recherchez un identifiant pour remplir automatiquement "{0}".</value>
</data>
</root>
</root>

View File

@@ -222,7 +222,7 @@
<value>Folder deleted.</value>
</data>
<data name="FolderNone" xml:space="preserve">
<value>(none)</value>
<value>No Folder</value>
<comment>Logins that have no folder specified go in this special "catch-all" folder.</comment>
</data>
<data name="Folders" xml:space="preserve">
@@ -302,6 +302,9 @@
<value>Ok</value>
<comment>Acknowledgement.</comment>
</data>
<data name="DisableGA" xml:space="preserve">
<value>Disable Analytics</value>
</data>
<data name="Password" xml:space="preserve">
<value>Password</value>
<comment>Label for a password.</comment>
@@ -816,4 +819,10 @@
<data name="BitwardenAutofillServiceSearch" xml:space="preserve">
<value>You are searching for an auto-fill login for "{0}".</value>
</data>
<data name="ShareVault" xml:space="preserve">
<value>Share Your Vault</value>
</data>
<data name="ShareVaultDescription" xml:space="preserve">
<value>Create an organization to securely share your logins with other users.</value>
</data>
</root>

View File

@@ -228,7 +228,7 @@
<comment>Folder deleted.</comment>
</data>
<data name="FolderNone" xml:space="preserve">
<value>(ingen)</value>
<value>Ingen mapp</value>
<comment>Logins that have no folder specified go in this special "catch-all" folder.</comment>
</data>
<data name="Folders" xml:space="preserve">
@@ -868,4 +868,4 @@
<data name="BitwardenAutofillServiceAlert" xml:space="preserve">
<value>Det lättaste sättet att lägga till nya inloggningar till ditt valv är från bitwardens hjälpmedelsservice för automatisk ifyllnad. Lär dig mer om hur man använder bitwardens hjälpmedelsservice för automatisk ifyllnad genom att navigera till "Verktyg"-skärmen.</value>
</data>
</root>
</root>

View File

@@ -222,7 +222,7 @@
<value>文件夹已删除。</value>
</data>
<data name="FolderNone" xml:space="preserve">
<value>(无)</value>
<value>没有文件夹</value>
<comment>Logins that have no folder specified go in this special "catch-all" folder.</comment>
</data>
<data name="Folders" xml:space="preserve">

View File

@@ -0,0 +1,41 @@
using System;
using Bit.App.Abstractions;
using Plugin.Settings.Abstractions;
namespace Bit.App.Services
{
public class AppSettingsService : IAppSettingsService
{
private readonly ISettings _settings;
public AppSettingsService(
ISettings settings)
{
_settings = settings;
}
public bool Locked
{
get
{
return _settings.GetValueOrDefault(Constants.Locked, false);
}
set
{
_settings.AddOrUpdateValue(Constants.Locked, value);
}
}
public DateTime LastActivity
{
get
{
return _settings.GetValueOrDefault(Constants.LastActivityDate, DateTime.MinValue);
}
set
{
_settings.AddOrUpdateValue(Constants.LastActivityDate, value);
}
}
}
}

View File

@@ -4,6 +4,10 @@ using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Models.Api;
using Plugin.Settings.Abstractions;
using Bit.App.Models;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;
namespace Bit.App.Services
{
@@ -19,6 +23,9 @@ namespace Bit.App.Services
private readonly ISettings _settings;
private readonly ICryptoService _cryptoService;
private readonly IConnectApiRepository _connectApiRepository;
private readonly IAccountsApiRepository _accountsApiRepository;
private readonly IAppIdService _appIdService;
private readonly IDeviceInfoService _deviceInfoService;
private string _email;
private string _userId;
@@ -30,13 +37,19 @@ namespace Bit.App.Services
ITokenService tokenService,
ISettings settings,
ICryptoService cryptoService,
IConnectApiRepository connectApiRepository)
IConnectApiRepository connectApiRepository,
IAccountsApiRepository accountsApiRepository,
IAppIdService appIdService,
IDeviceInfoService deviceInfoService)
{
_secureStorage = secureStorage;
_tokenService = tokenService;
_settings = settings;
_cryptoService = cryptoService;
_connectApiRepository = connectApiRepository;
_accountsApiRepository = accountsApiRepository;
_appIdService = appIdService;
_deviceInfoService = deviceInfoService;
}
public string UserId
@@ -178,6 +191,11 @@ namespace Bit.App.Services
}
}
public bool BelongsToOrganization(string orgId)
{
return !string.IsNullOrWhiteSpace(orgId) && (_cryptoService.OrgKeys?.ContainsKey(orgId) ?? false);
}
public void LogOut()
{
_tokenService.Token = null;
@@ -185,16 +203,96 @@ namespace Bit.App.Services
_tokenService.AuthBearer = null;
UserId = null;
Email = null;
_cryptoService.Key = null;
_cryptoService.ClearKeys();
_settings.Remove(Constants.FirstVaultLoad);
_settings.Remove(Constants.PushLastRegistrationDate);
_settings.Remove(Constants.Locked);
}
public async Task<ApiResult<TokenResponse>> TokenPostAsync(TokenRequest request)
public async Task<FullLoginResult> TokenPostAsync(string email, string masterPassword)
{
// TODO: move more logic in here
return await _connectApiRepository.PostTokenAsync(request);
var result = new FullLoginResult();
var normalizedEmail = email.Trim().ToLower();
var key = _cryptoService.MakeKeyFromPassword(masterPassword, normalizedEmail);
var request = new TokenRequest
{
Email = normalizedEmail,
MasterPasswordHash = _cryptoService.HashPasswordBase64(key, masterPassword),
Device = new DeviceRequest(_appIdService, _deviceInfoService)
};
var response = await _connectApiRepository.PostTokenAsync(request);
if(!response.Succeeded)
{
result.Success = false;
result.ErrorMessage = response.Errors.FirstOrDefault()?.Message;
return result;
}
result.Success = true;
if(response.Result.TwoFactorProviders != null && response.Result.TwoFactorProviders.Count > 0)
{
result.Key = key;
result.MasterPasswordHash = request.MasterPasswordHash;
result.TwoFactorRequired = true;
return result;
}
await ProcessLoginSuccessAsync(key, response.Result);
return result;
}
public async Task<LoginResult> TokenPostTwoFactorAsync(string token, string email, string masterPasswordHash,
SymmetricCryptoKey key)
{
var result = new LoginResult();
var request = new TokenRequest
{
Email = email.Trim().ToLower(),
MasterPasswordHash = masterPasswordHash,
Token = token.Trim().Replace(" ", ""),
Provider = 0, // Authenticator app (only 1 provider for now, so hard coded)
Device = new DeviceRequest(_appIdService, _deviceInfoService)
};
var response = await _connectApiRepository.PostTokenAsync(request);
if(!response.Succeeded)
{
result.Success = false;
result.ErrorMessage = response.Errors.FirstOrDefault()?.Message;
return result;
}
result.Success = true;
await ProcessLoginSuccessAsync(key, response.Result);
return result;
}
private async Task ProcessLoginSuccessAsync(SymmetricCryptoKey key, TokenResponse response)
{
if(response.PrivateKey != null)
{
_cryptoService.SetPrivateKey(new CipherString(response.PrivateKey));
}
_cryptoService.Key = key;
_tokenService.Token = response.AccessToken;
_tokenService.RefreshToken = response.RefreshToken;
UserId = _tokenService.TokenUserId;
Email = _tokenService.TokenEmail;
_settings.AddOrUpdateValue(Constants.LastLoginEmail, Email);
if(response.PrivateKey != null)
{
var profile = await _accountsApiRepository.GetProfileAsync();
if(profile.Succeeded)
{
_cryptoService.SetOrgKeys(profile.Result);
}
}
}
}
}

View File

@@ -5,7 +5,11 @@ using Bit.App.Abstractions;
using Bit.App.Models;
using PCLCrypto;
using System.Linq;
using Xamarin.Forms;
using Bit.App.Enums;
using System.Collections.Generic;
using Newtonsoft.Json;
using Plugin.Settings.Abstractions;
using Bit.App.Models.Api;
namespace Bit.App.Services
{
@@ -13,28 +17,40 @@ namespace Bit.App.Services
{
private const string KeyKey = "key";
private const string PreviousKeyKey = "previousKey";
private const string PrivateKeyKey = "encPrivateKey";
private const string OrgKeysKey = "encOrgKeys";
private const int InitializationVectorSize = 16;
private readonly ISettings _settings;
private readonly ISecureStorageService _secureStorage;
private readonly IKeyDerivationService _keyDerivationService;
private byte[] _key;
private byte[] _previousKey;
private SymmetricCryptoKey _key;
private SymmetricCryptoKey _legacyEtmKey;
private SymmetricCryptoKey _previousKey;
private IDictionary<string, SymmetricCryptoKey> _orgKeys;
private byte[] _privateKey;
public CryptoService(
ISettings settings,
ISecureStorageService secureStorage,
IKeyDerivationService keyDerivationService)
{
_settings = settings;
_secureStorage = secureStorage;
_keyDerivationService = keyDerivationService;
}
public byte[] Key
public SymmetricCryptoKey Key
{
get
{
if(_key == null)
if(_key == null && _secureStorage.Contains(KeyKey))
{
_key = _secureStorage.Retrieve(KeyKey);
var keyBytes = _secureStorage.Retrieve(KeyKey);
if(keyBytes != null)
{
_key = new SymmetricCryptoKey(keyBytes);
}
}
return _key;
@@ -43,37 +59,29 @@ namespace Bit.App.Services
{
if(value != null)
{
_secureStorage.Store(KeyKey, value);
_secureStorage.Store(KeyKey, value.Key);
}
else
{
PreviousKey = _key;
_secureStorage.Delete(KeyKey);
_key = null;
_legacyEtmKey = null;
}
}
}
public string Base64Key
public SymmetricCryptoKey PreviousKey
{
get
{
if(Key == null)
if(_previousKey == null && _secureStorage.Contains(PreviousKeyKey))
{
return null;
}
return Convert.ToBase64String(Key);
}
}
public byte[] PreviousKey
{
get
{
if(_previousKey == null)
{
_previousKey = _secureStorage.Retrieve(PreviousKeyKey);
var keyBytes = _secureStorage.Retrieve(PreviousKeyKey);
if(keyBytes != null)
{
_previousKey = new SymmetricCryptoKey(keyBytes);
}
}
return _previousKey;
@@ -82,7 +90,7 @@ namespace Bit.App.Services
{
if(value != null)
{
_secureStorage.Store(PreviousKeyKey, value);
_secureStorage.Store(PreviousKeyKey, value.Key);
_previousKey = value;
}
}
@@ -102,18 +110,141 @@ namespace Bit.App.Services
return Key != null;
}
return !PreviousKey.SequenceEqual(Key);
return !PreviousKey.Key.SequenceEqual(Key.Key);
}
}
public byte[] EncKey => Key?.Take(16).ToArray();
public byte[] MacKey => Key?.Skip(16).Take(16).ToArray();
public CipherString Encrypt(string plaintextValue)
public byte[] PrivateKey
{
if(Key == null)
get
{
throw new ArgumentNullException(nameof(Key));
if(_privateKey == null && _settings.Contains(PrivateKeyKey))
{
var encPrivateKey = _settings.GetValueOrDefault<string>(PrivateKeyKey);
var encPrivateKeyCs = new CipherString(encPrivateKey);
try
{
_privateKey = DecryptToBytes(encPrivateKeyCs);
}
catch
{
_privateKey = null;
Debug.WriteLine($"Cannot set private key. Decryption failed.");
}
}
return _privateKey;
}
}
public IDictionary<string, SymmetricCryptoKey> OrgKeys
{
get
{
if((!_orgKeys?.Any() ?? true) && _settings.Contains(OrgKeysKey))
{
var orgKeysEncDictJson = _settings.GetValueOrDefault<string>(OrgKeysKey);
if(!string.IsNullOrWhiteSpace(orgKeysEncDictJson))
{
_orgKeys = new Dictionary<string, SymmetricCryptoKey>();
var orgKeysDict = JsonConvert.DeserializeObject<IDictionary<string, string>>(orgKeysEncDictJson);
foreach(var item in orgKeysDict)
{
try
{
var orgKeyCs = new CipherString(item.Value);
var decOrgKeyBytes = RsaDecryptToBytes(orgKeyCs, PrivateKey);
_orgKeys.Add(item.Key, new SymmetricCryptoKey(decOrgKeyBytes));
}
catch
{
Debug.WriteLine($"Cannot set org key {item.Key}. Decryption failed.");
}
}
}
}
return _orgKeys;
}
}
public void SetPrivateKey(CipherString privateKeyEnc)
{
if(privateKeyEnc != null)
{
_settings.AddOrUpdateValue(PrivateKeyKey, privateKeyEnc.EncryptedString);
}
else if(_settings.Contains(PrivateKeyKey))
{
_settings.Remove(PrivateKeyKey);
_privateKey = null;
}
else
{
_privateKey = null;
}
}
public void SetOrgKeys(ProfileResponse profile)
{
var orgKeysEncDict = new Dictionary<string, string>();
if(profile?.Organizations?.Any() ?? false)
{
foreach(var org in profile.Organizations)
{
orgKeysEncDict.Add(org.Id, org.Key);
}
}
SetOrgKeys(orgKeysEncDict);
}
public void SetOrgKeys(Dictionary<string, string> orgKeysEncDict)
{
if(orgKeysEncDict?.Any() ?? false)
{
var dictJson = JsonConvert.SerializeObject(orgKeysEncDict);
_settings.AddOrUpdateValue(OrgKeysKey, dictJson);
}
else if(_settings.Contains(OrgKeysKey))
{
_settings.Remove(OrgKeysKey);
_orgKeys = null;
}
else
{
_orgKeys = null;
}
}
public SymmetricCryptoKey GetOrgKey(string orgId)
{
if(OrgKeys == null || !OrgKeys.ContainsKey(orgId))
{
return null;
}
return OrgKeys[orgId];
}
public void ClearKeys()
{
SetOrgKeys((Dictionary<string, string>)null);
Key = null;
SetPrivateKey(null);
}
public CipherString Encrypt(string plaintextValue, SymmetricCryptoKey key = null)
{
if(key == null)
{
key = Key;
}
if(key == null)
{
throw new ArgumentNullException(nameof(key));
}
if(plaintextValue == null)
@@ -124,44 +255,21 @@ namespace Bit.App.Services
var plaintextBytes = Encoding.UTF8.GetBytes(plaintextValue);
var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7);
// TODO: Turn on whenever ready to support encrypt-then-mac
var cryptoKey = provider.CreateSymmetricKey(true ? EncKey : Key);
var cryptoKey = provider.CreateSymmetricKey(key.EncKey);
var iv = WinRTCrypto.CryptographicBuffer.GenerateRandom(provider.BlockLength);
var encryptedBytes = WinRTCrypto.CryptographicEngine.Encrypt(cryptoKey, plaintextBytes, iv);
// TODO: Turn on whenever ready to support encrypt-then-mac
var mac = true ? ComputeMac(encryptedBytes, iv) : null;
var mac = key.MacKey != null ? ComputeMacBase64(encryptedBytes, iv, key.MacKey) : null;
return new CipherString(Convert.ToBase64String(iv), Convert.ToBase64String(encryptedBytes), mac);
return new CipherString(key.EncryptionType, Convert.ToBase64String(iv),
Convert.ToBase64String(encryptedBytes), mac);
}
public string Decrypt(CipherString encyptedValue)
public string Decrypt(CipherString encyptedValue, SymmetricCryptoKey key = null)
{
try
{
if(Key == null)
{
throw new ArgumentNullException(nameof(Key));
}
if(encyptedValue == null)
{
throw new ArgumentNullException(nameof(encyptedValue));
}
if(encyptedValue.Mac != null)
{
var computedMac = ComputeMac(encyptedValue.CipherTextBytes, encyptedValue.InitializationVectorBytes);
if(computedMac != encyptedValue.Mac)
{
throw new InvalidOperationException("MAC failed.");
}
}
var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7);
var cryptoKey = provider.CreateSymmetricKey(encyptedValue.Mac != null ? EncKey : Key);
var decryptedBytes = WinRTCrypto.CryptographicEngine.Decrypt(cryptoKey, encyptedValue.CipherTextBytes,
encyptedValue.InitializationVectorBytes);
return Encoding.UTF8.GetString(decryptedBytes, 0, decryptedBytes.Length).TrimEnd('\0');
var bytes = DecryptToBytes(encyptedValue, key);
return Encoding.UTF8.GetString(bytes, 0, bytes.Length).TrimEnd('\0');
}
catch(Exception e)
{
@@ -170,11 +278,98 @@ namespace Bit.App.Services
}
}
private string ComputeMac(byte[] ctBytes, byte[] ivBytes)
public byte[] DecryptToBytes(CipherString encyptedValue, SymmetricCryptoKey key = null)
{
if(MacKey == null)
if(key == null)
{
throw new ArgumentNullException(nameof(MacKey));
key = Key;
}
if(key == null)
{
throw new ArgumentNullException(nameof(key));
}
if(encyptedValue == null)
{
throw new ArgumentNullException(nameof(encyptedValue));
}
if(encyptedValue.EncryptionType == Enums.EncryptionType.AesCbc128_HmacSha256_B64 &&
key.EncryptionType == Enums.EncryptionType.AesCbc256_B64)
{
// Old encrypt-then-mac scheme, swap out the key
if(_legacyEtmKey == null)
{
_legacyEtmKey = new SymmetricCryptoKey(key.Key, Enums.EncryptionType.AesCbc128_HmacSha256_B64);
}
key = _legacyEtmKey;
}
if(encyptedValue.EncryptionType != key.EncryptionType)
{
throw new ArgumentException("encType unavailable.");
}
if(key.MacKey != null && !string.IsNullOrWhiteSpace(encyptedValue.Mac))
{
var computedMacBytes = ComputeMac(encyptedValue.CipherTextBytes,
encyptedValue.InitializationVectorBytes, key.MacKey);
if(!MacsEqual(key.MacKey, computedMacBytes, encyptedValue.MacBytes))
{
throw new InvalidOperationException("MAC failed.");
}
}
var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7);
var cryptoKey = provider.CreateSymmetricKey(key.EncKey);
var decryptedBytes = WinRTCrypto.CryptographicEngine.Decrypt(cryptoKey, encyptedValue.CipherTextBytes,
encyptedValue.InitializationVectorBytes);
return decryptedBytes;
}
public byte[] RsaDecryptToBytes(CipherString encyptedValue, byte[] privateKey)
{
if(privateKey == null)
{
privateKey = PrivateKey;
}
if(privateKey == null)
{
throw new ArgumentNullException(nameof(privateKey));
}
IAsymmetricKeyAlgorithmProvider provider = null;
switch(encyptedValue.EncryptionType)
{
case EncryptionType.Rsa2048_OaepSha256_B64:
provider = WinRTCrypto.AsymmetricKeyAlgorithmProvider.OpenAlgorithm(AsymmetricAlgorithm.RsaOaepSha256);
break;
case EncryptionType.Rsa2048_OaepSha1_B64:
provider = WinRTCrypto.AsymmetricKeyAlgorithmProvider.OpenAlgorithm(AsymmetricAlgorithm.RsaOaepSha1);
break;
default:
throw new ArgumentException("EncryptionType unavailable.");
}
var cryptoKey = provider.ImportKeyPair(privateKey, CryptographicPrivateKeyBlobType.Pkcs8RawPrivateKeyInfo);
var decryptedBytes = WinRTCrypto.CryptographicEngine.Decrypt(cryptoKey, encyptedValue.CipherTextBytes);
return decryptedBytes;
}
private string ComputeMacBase64(byte[] ctBytes, byte[] ivBytes, byte[] macKey)
{
var mac = ComputeMac(ctBytes, ivBytes, macKey);
return Convert.ToBase64String(mac);
}
private byte[] ComputeMac(byte[] ctBytes, byte[] ivBytes, byte[] macKey)
{
if(macKey == null)
{
throw new ArgumentNullException(nameof(macKey));
}
if(ctBytes == null)
@@ -188,13 +383,42 @@ namespace Bit.App.Services
}
var algorithm = WinRTCrypto.MacAlgorithmProvider.OpenAlgorithm(MacAlgorithm.HmacSha256);
var hasher = algorithm.CreateHash(MacKey);
var hasher = algorithm.CreateHash(macKey);
hasher.Append(ivBytes.Concat(ctBytes).ToArray());
var mac = hasher.GetValueAndReset();
return Convert.ToBase64String(mac);
return mac;
}
public byte[] MakeKeyFromPassword(string password, string salt)
// Safely compare two MACs in a way that protects against timing attacks (Double HMAC Verification).
// ref: https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/february/double-hmac-verification/
private bool MacsEqual(byte[] macKey, byte[] mac1, byte[] mac2)
{
var algorithm = WinRTCrypto.MacAlgorithmProvider.OpenAlgorithm(MacAlgorithm.HmacSha256);
var hasher = algorithm.CreateHash(macKey);
hasher.Append(mac1);
mac1 = hasher.GetValueAndReset();
hasher.Append(mac2);
mac2 = hasher.GetValueAndReset();
if(mac1.Length != mac2.Length)
{
return false;
}
for(int i = 0; i < mac2.Length; i++)
{
if(mac1[i] != mac2[i])
{
return false;
}
}
return true;
}
public SymmetricCryptoKey MakeKeyFromPassword(string password, string salt)
{
if(password == null)
{
@@ -209,17 +433,17 @@ namespace Bit.App.Services
var passwordBytes = Encoding.UTF8.GetBytes(password);
var saltBytes = Encoding.UTF8.GetBytes(salt);
var key = _keyDerivationService.DeriveKey(passwordBytes, saltBytes, 5000);
return key;
var keyBytes = _keyDerivationService.DeriveKey(passwordBytes, saltBytes, 5000);
return new SymmetricCryptoKey(keyBytes);
}
public string MakeKeyFromPasswordBase64(string password, string salt)
{
var key = MakeKeyFromPassword(password, salt);
return Convert.ToBase64String(key);
return Convert.ToBase64String(key.Key);
}
public byte[] HashPassword(byte[] key, string password)
public byte[] HashPassword(SymmetricCryptoKey key, string password)
{
if(key == null)
{
@@ -232,11 +456,11 @@ namespace Bit.App.Services
}
var passwordBytes = Encoding.UTF8.GetBytes(password);
var hash = _keyDerivationService.DeriveKey(key, passwordBytes, 1);
var hash = _keyDerivationService.DeriveKey(key.Key, passwordBytes, 1);
return hash;
}
public string HashPasswordBase64(byte[] key, string password)
public string HashPasswordBase64(SymmetricCryptoKey key, string password)
{
var hash = HashPassword(key, password);
return Convert.ToBase64String(hash);

View File

@@ -9,19 +9,32 @@ namespace Bit.App.Services
public class LockService : ILockService
{
private readonly ISettings _settings;
private readonly IAppSettingsService _appSettings;
private readonly IAuthService _authService;
private readonly IFingerprint _fingerprint;
public LockService(
ISettings settings,
IAppSettingsService appSettings,
IAuthService authService,
IFingerprint fingerprint)
{
_settings = settings;
_appSettings = appSettings;
_authService = authService;
_fingerprint = fingerprint;
}
public void UpdateLastActivity(DateTime? activityDate = null)
{
if(_appSettings.Locked)
{
return;
}
_appSettings.LastActivity = activityDate.GetValueOrDefault(DateTime.UtcNow);
}
public LockType GetLockType(bool forceLock)
{
// Only lock if they are logged in
@@ -31,7 +44,7 @@ namespace Bit.App.Services
}
// Are we forcing a lock? (i.e. clicking a button to lock the app manually, immediately)
if(!forceLock && !_settings.GetValueOrDefault(Constants.Locked, false))
if(!forceLock && !_appSettings.Locked)
{
// Lock seconds tells if they want to lock the app or not
var lockSeconds = _settings.GetValueOrDefault(Constants.SettingLockSeconds, 60 * 15);
@@ -42,8 +55,7 @@ namespace Bit.App.Services
// Has it been longer than lockSeconds since the last time the app was used?
var now = DateTime.UtcNow;
var lastBackground = _settings.GetValueOrDefault(Constants.LastActivityDate, now.AddYears(-1));
if((now - lastBackground).TotalSeconds < lockSeconds)
if(now > _appSettings.LastActivity && (now - _appSettings.LastActivity).TotalSeconds < lockSeconds)
{
return LockType.None;
}

View File

@@ -64,17 +64,10 @@ namespace Bit.App.Services
Uri uri = null;
DomainName domainName = null;
var androidApp = false;
var androidApp = UriIsAndroidApp(uriString);
if(!Uri.TryCreate(uriString, UriKind.Absolute, out uri) || !DomainName.TryParse(uri.Host, out domainName))
{
if(domainName == null)
{
androidApp = UriIsAndroidApp(uriString);
}
}
if(!androidApp && domainName == null)
if(!androidApp &&
(!Uri.TryCreate(uriString, UriKind.Absolute, out uri) || !DomainName.TryParse(uri.Host, out domainName)))
{
return null;
}
@@ -91,8 +84,7 @@ namespace Bit.App.Services
{
matchingDomains.AddRange(eqDomain.Select(d => d).ToList());
}
if(androidAppWebUriString != null && Array.IndexOf(eqDomain, androidAppWebUriString) >= 0)
else if(androidAppWebUriString != null && Array.IndexOf(eqDomain, androidAppWebUriString) >= 0)
{
matchingFuzzyDomains.AddRange(eqDomain.Select(d => d).ToList());
}
@@ -108,7 +100,8 @@ namespace Bit.App.Services
matchingDomains.Add(androidApp ? uriString : domainName.BaseDomain);
}
if(androidApp && androidAppWebUriString != null && !matchingFuzzyDomains.Any())
if(androidApp && androidAppWebUriString != null &&
!matchingFuzzyDomains.Any() && !matchingDomains.Contains(androidAppWebUriString))
{
matchingFuzzyDomains.Add(androidAppWebUriString);
}
@@ -125,7 +118,7 @@ namespace Bit.App.Services
continue;
}
var loginUriString = new CipherString(login.Uri).Decrypt();
var loginUriString = new CipherString(login.Uri).Decrypt(login.OrganizationId);
if(string.IsNullOrWhiteSpace(loginUriString))
{
continue;

View File

@@ -59,15 +59,30 @@ namespace Bit.App.Services
{
case Enums.PushType.SyncCipherUpdate:
case Enums.PushType.SyncCipherCreate:
var createUpdateMessage = values.ToObject<SyncCipherPushNotification>();
if(createUpdateMessage.UserId != _authService.UserId)
var cipherCreateUpdateMessage = values.ToObject<SyncCipherPushNotification>();
if(cipherCreateUpdateMessage.OrganizationId == null &&
cipherCreateUpdateMessage.UserId != _authService.UserId)
{
break;
}
_syncService.SyncAsync(createUpdateMessage.Id);
else if(cipherCreateUpdateMessage.OrganizationId != null &&
!_authService.BelongsToOrganization(cipherCreateUpdateMessage.OrganizationId))
{
break;
}
_syncService.SyncCipherAsync(cipherCreateUpdateMessage.Id);
break;
case Enums.PushType.SyncFolderUpdate:
case Enums.PushType.SyncFolderCreate:
var folderCreateUpdateMessage = values.ToObject<SyncFolderPushNotification>();
if(folderCreateUpdateMessage.UserId != _authService.UserId)
{
break;
}
_syncService.SyncFolderAsync(folderCreateUpdateMessage.Id);
break;
case Enums.PushType.SyncFolderDelete:
var folderDeleteMessage = values.ToObject<SyncCipherPushNotification>();
var folderDeleteMessage = values.ToObject<SyncFolderPushNotification>();
if(folderDeleteMessage.UserId != _authService.UserId)
{
break;
@@ -76,20 +91,43 @@ namespace Bit.App.Services
break;
case Enums.PushType.SyncLoginDelete:
var loginDeleteMessage = values.ToObject<SyncCipherPushNotification>();
if(loginDeleteMessage.UserId != _authService.UserId)
if(loginDeleteMessage.OrganizationId == null &&
loginDeleteMessage.UserId != _authService.UserId)
{
break;
}
else if(loginDeleteMessage.OrganizationId != null &&
!_authService.BelongsToOrganization(loginDeleteMessage.OrganizationId))
{
break;
}
_syncService.SyncDeleteLoginAsync(loginDeleteMessage.Id);
break;
case Enums.PushType.SyncCiphers:
var cipherMessage = values.ToObject<SyncCiphersPushNotification>();
case Enums.PushType.SyncVault:
var cipherMessage = values.ToObject<SyncUserPushNotification>();
if(cipherMessage.UserId != _authService.UserId)
{
break;
}
_syncService.FullSyncAsync(true);
break;
case Enums.PushType.SyncSettings:
var domainMessage = values.ToObject<SyncUserPushNotification>();
if(domainMessage.UserId != _authService.UserId)
{
break;
}
_syncService.SyncSettingsAsync();
break;
case Enums.PushType.SyncOrgKeys:
var orgKeysMessage = values.ToObject<SyncUserPushNotification>();
if(orgKeysMessage.UserId != _authService.UserId)
{
break;
}
_syncService.SyncProfileAsync();
break;
default:
break;
}

View File

@@ -8,6 +8,8 @@ using Bit.App.Models.Api;
using System.Collections.Generic;
using Xamarin.Forms;
using Newtonsoft.Json;
using Bit.App.Models;
using System.Diagnostics;
namespace Bit.App.Services
{
@@ -22,6 +24,7 @@ namespace Bit.App.Services
private readonly ILoginRepository _loginRepository;
private readonly ISettingsRepository _settingsRepository;
private readonly IAuthService _authService;
private readonly ICryptoService _cryptoService;
private readonly ISettings _settings;
public SyncService(
@@ -34,6 +37,7 @@ namespace Bit.App.Services
ILoginRepository loginRepository,
ISettingsRepository settingsRepository,
IAuthService authService,
ICryptoService cryptoService,
ISettings settings)
{
_cipherApiRepository = cipherApiRepository;
@@ -45,12 +49,13 @@ namespace Bit.App.Services
_loginRepository = loginRepository;
_settingsRepository = settingsRepository;
_authService = authService;
_cryptoService = cryptoService;
_settings = settings;
}
public bool SyncInProgress { get; private set; }
public async Task<bool> SyncAsync(string id)
public async Task<bool> SyncCipherAsync(string id)
{
if(!_authService.IsAuthenticated)
{
@@ -60,16 +65,8 @@ namespace Bit.App.Services
SyncStarted();
var cipher = await _cipherApiRepository.GetByIdAsync(id).ConfigureAwait(false);
if(!cipher.Succeeded)
if(!CheckSuccess(cipher))
{
SyncCompleted(false);
if(Application.Current != null && (cipher.StatusCode == System.Net.HttpStatusCode.Forbidden
|| cipher.StatusCode == System.Net.HttpStatusCode.Unauthorized))
{
MessagingCenter.Send(Application.Current, "Logout", (string)null);
}
return false;
}
@@ -77,10 +74,6 @@ namespace Bit.App.Services
{
switch(cipher.Result.Type)
{
case Enums.CipherType.Folder:
var folderData = new FolderData(cipher.Result, _authService.UserId);
await _folderRepository.UpsertAsync(folderData).ConfigureAwait(false);
break;
case Enums.CipherType.Login:
var loginData = new LoginData(cipher.Result, _authService.UserId);
await _loginRepository.UpsertAsync(loginData).ConfigureAwait(false);
@@ -100,6 +93,36 @@ namespace Bit.App.Services
return true;
}
public async Task<bool> SyncFolderAsync(string id)
{
if(!_authService.IsAuthenticated)
{
return false;
}
SyncStarted();
var folder = await _folderApiRepository.GetByIdAsync(id).ConfigureAwait(false);
if(!CheckSuccess(folder))
{
return false;
}
try
{
var folderData = new FolderData(folder.Result, _authService.UserId);
await _folderRepository.UpsertAsync(folderData).ConfigureAwait(false);
}
catch(SQLite.SQLiteException)
{
SyncCompleted(false);
return false;
}
SyncCompleted(true);
return true;
}
public async Task<bool> SyncDeleteFolderAsync(string id, DateTime revisionDate)
{
if(!_authService.IsAuthenticated)
@@ -144,6 +167,48 @@ namespace Bit.App.Services
}
}
public async Task<bool> SyncSettingsAsync()
{
if(!_authService.IsAuthenticated)
{
return false;
}
SyncStarted();
var domains = await _settingsApiRepository.GetDomains(false).ConfigureAwait(false);
if(!CheckSuccess(domains))
{
return false;
}
await SyncDomainsAsync(domains.Result);
SyncCompleted(true);
return true;
}
public async Task<bool> SyncProfileAsync()
{
if(!_authService.IsAuthenticated)
{
return false;
}
SyncStarted();
var profile = await _accountsApiRepository.GetProfileAsync().ConfigureAwait(false);
if(!CheckSuccess(profile))
{
return false;
}
await SyncOrgKeysAsync(profile.Result);
SyncCompleted(true);
return true;
}
public async Task<bool> FullSyncAsync(TimeSpan syncThreshold, bool forceSync = false)
{
DateTime? lastSync = _settings.GetValueOrDefault<DateTime?>(Constants.LastSync, null);
@@ -171,37 +236,33 @@ namespace Bit.App.Services
SyncStarted();
var now = DateTime.UtcNow;
var ciphers = await _cipherApiRepository.GetAsync().ConfigureAwait(false);
var domains = await _settingsApiRepository.GetDomains(false).ConfigureAwait(false);
if(!ciphers.Succeeded || !domains.Succeeded)
// Just check profile first to make sure we'll have a success with the API
var profile = await _accountsApiRepository.GetProfileAsync().ConfigureAwait(false);
if(!CheckSuccess(profile))
{
SyncCompleted(false);
if(Application.Current == null)
{
return false;
}
if(ciphers.StatusCode == System.Net.HttpStatusCode.Forbidden ||
ciphers.StatusCode == System.Net.HttpStatusCode.Unauthorized ||
domains.StatusCode == System.Net.HttpStatusCode.Forbidden ||
domains.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
MessagingCenter.Send(Application.Current, "Logout", (string)null);
}
return false;
}
var logins = ciphers.Result.Data.Where(c => c.Type == Enums.CipherType.Login).ToDictionary(s => s.Id);
var folders = ciphers.Result.Data.Where(c => c.Type == Enums.CipherType.Folder).ToDictionary(f => f.Id);
var ciphers = await _cipherApiRepository.GetAsync().ConfigureAwait(false);
var folders = await _folderApiRepository.GetAsync().ConfigureAwait(false);
var domains = await _settingsApiRepository.GetDomains(false).ConfigureAwait(false);
if(!CheckSuccess(ciphers) || !CheckSuccess(folders) || !CheckSuccess(domains))
{
return false;
}
var loginTask = SyncLoginsAsync(logins);
var folderTask = SyncFoldersAsync(folders);
var loginsDict = ciphers.Result.Data.Where(c => c.Type == Enums.CipherType.Login).ToDictionary(s => s.Id);
var foldersDict = folders.Result.Data.ToDictionary(f => f.Id);
var loginTask = SyncLoginsAsync(loginsDict);
var folderTask = SyncFoldersAsync(foldersDict);
var domainsTask = SyncDomainsAsync(domains.Result);
await Task.WhenAll(loginTask, folderTask, domainsTask).ConfigureAwait(false);
var orgKeysTask = SyncOrgKeysAsync(profile.Result);
await Task.WhenAll(loginTask, folderTask, domainsTask, orgKeysTask).ConfigureAwait(false);
if(folderTask.Exception != null || loginTask.Exception != null || domainsTask.Exception != null)
if(folderTask.Exception != null || loginTask.Exception != null || domainsTask.Exception != null ||
orgKeysTask.Exception != null)
{
SyncCompleted(false);
return false;
@@ -220,7 +281,7 @@ namespace Bit.App.Services
return true;
}
var accountRevisionDate = await _accountsApiRepository.GetAccountRevisionDate();
var accountRevisionDate = await _accountsApiRepository.GetAccountRevisionDateAsync();
if(accountRevisionDate.Succeeded && accountRevisionDate.Result.HasValue &&
accountRevisionDate.Result.Value > lastSync)
{
@@ -236,7 +297,7 @@ namespace Bit.App.Services
return false;
}
private async Task SyncFoldersAsync(IDictionary<string, CipherResponse> serverFolders)
private async Task SyncFoldersAsync(IDictionary<string, FolderResponse> serverFolders)
{
if(!_authService.IsAuthenticated)
{
@@ -330,6 +391,25 @@ namespace Bit.App.Services
catch(SQLite.SQLiteException) { }
}
private async Task SyncOrgKeysAsync(ProfileResponse profile)
{
if(_cryptoService.PrivateKey == null && (profile.Organizations?.Any() ?? false))
{
var keys = await _accountsApiRepository.GetKeys();
if(!CheckSuccess(keys))
{
return;
}
if(!string.IsNullOrWhiteSpace(keys.Result.PrivateKey))
{
_cryptoService.SetPrivateKey(new CipherString(keys.Result.PrivateKey));
}
}
_cryptoService.SetOrgKeys(profile);
}
private void SyncStarted()
{
if(Application.Current == null)
@@ -351,5 +431,23 @@ namespace Bit.App.Services
SyncInProgress = false;
MessagingCenter.Send(Application.Current, "SyncCompleted", successfully);
}
private bool CheckSuccess<T>(ApiResult<T> result)
{
if(!result.Succeeded)
{
SyncCompleted(false);
if(Application.Current != null && (result.StatusCode == System.Net.HttpStatusCode.Forbidden
|| result.StatusCode == System.Net.HttpStatusCode.Unauthorized))
{
MessagingCenter.Send(Application.Current, "Logout", (string)null);
}
return false;
}
return true;
}
}
}

View File

@@ -19,8 +19,10 @@ namespace Bit.App
private void Init()
{
//BaseAddress = new Uri("http://192.168.1.3:4000");
BaseAddress = new Uri("https://api.bitwarden.com");
//BaseAddress = new Uri("http://169.254.80.80:4000"); // Desktop from VS Android Emulator
//BaseAddress = new Uri("http://192.168.1.8:4000"); // Desktop
//BaseAddress = new Uri("https://preview-api.bitwarden.com"); // Preview
BaseAddress = new Uri("https://api.bitwarden.com"); // Production
DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
}

View File

@@ -10,7 +10,7 @@ namespace Bit.App
{
public static class Extentions
{
public static CipherString Encrypt(this string s)
public static CipherString Encrypt(this string s, string orgId = null)
{
if(s == null)
{
@@ -18,6 +18,12 @@ namespace Bit.App
}
var cryptoService = Resolver.Resolve<ICryptoService>();
if(!string.IsNullOrWhiteSpace(orgId))
{
return cryptoService.Encrypt(s, cryptoService.GetOrgKey(orgId));
}
return cryptoService.Encrypt(s);
}

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Acr.UserDialogs" version="6.3.3" targetFramework="portable45-net45+win8+wpa81" />
<package id="Acr.UserDialogs" version="6.3.10" targetFramework="portable45-net45+win8+wpa81" />
<package id="CommonServiceLocator" version="1.3" targetFramework="portable45-net45+win8+wpa81" />
<package id="HockeySDK.Xamarin" version="4.1.0" targetFramework="portable45-net45+win8+wpa81" />
<package id="HockeySDK.Xamarin" version="4.1.2" targetFramework="portable45-net45+win8+wpa81" />
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="portable45-net45+win8+wpa81" />
<package id="PCLCrypto" version="2.0.147" targetFramework="portable45-net45+win8+wpa81" />
<package id="PInvoke.BCrypt" version="0.3.152" targetFramework="portable45-net45+win8+wpa81" />
@@ -18,11 +18,11 @@
<package id="SQLitePCLRaw.core" version="1.1.2" targetFramework="portable45-net45+win8+wpa81" />
<package id="Unity" version="3.5.1405-prerelease" targetFramework="portable45-net45+win8+wpa81" />
<package id="Validation" version="2.3.7" targetFramework="portable45-net45+win8+wpa81" />
<package id="Xam.Plugin.Connectivity" version="2.2.12" targetFramework="portable45-net45+win8+wpa81" />
<package id="Xam.Plugin.Connectivity" version="2.3.0" targetFramework="portable45-net45+win8+wpa81" />
<package id="Xam.Plugin.PushNotification" version="1.2.4" targetFramework="portable45-net45+win8+wpa81" developmentDependency="true" />
<package id="Xam.Plugins.Settings" version="2.5.1.0" targetFramework="portable45-net45+win8+wpa81" />
<package id="Xamarin.FFImageLoading" version="2.2.8" targetFramework="portable45-net45+win8+wpa81" />
<package id="Xamarin.FFImageLoading.Forms" version="2.2.8" targetFramework="portable45-net45+win8+wpa81" />
<package id="Xamarin.Forms" version="2.3.3.180" targetFramework="portable45-net45+win8+wpa81" />
<package id="Xam.Plugins.Settings" version="2.5.4" targetFramework="portable45-net45+win8+wpa81" />
<package id="Xamarin.FFImageLoading" version="2.2.9" targetFramework="portable45-net45+win8+wpa81" />
<package id="Xamarin.FFImageLoading.Forms" version="2.2.9" targetFramework="portable45-net45+win8+wpa81" />
<package id="Xamarin.Forms" version="2.3.4.231" targetFramework="portable45-net45+win8+wpa81" />
<package id="XLabs.IoC" version="2.0.5782" targetFramework="portable45-net45+win8+wpa81" />
</packages>

View File

@@ -1,6 +1,7 @@
using System;
using Bit.App.Abstractions;
using Google.Analytics;
using Plugin.Settings.Abstractions;
namespace Bit.iOS.Core.Services
{
@@ -8,25 +9,22 @@ namespace Bit.iOS.Core.Services
{
private readonly ITracker _tracker;
private readonly IAuthService _authService;
private bool _setUserId = true;
public GoogleAnalyticsService(
IAppIdService appIdService,
IAuthService authService)
IAuthService authService,
ISettings settings)
{
_authService = authService;
Gai.SharedInstance.DispatchInterval = 10;
Gai.SharedInstance.TrackUncaughtExceptions = true;
Gai.SharedInstance.TrackUncaughtExceptions = false;
_tracker = Gai.SharedInstance.GetTracker("UA-81915606-1");
_tracker.SetAllowIdfaCollection(true);
_tracker.Set(GaiConstants.ClientId, appIdService.AnonymousAppId);
}
public void RefreshUserId()
{
_tracker.Set(GaiConstants.UserId, null);
_setUserId = true;
var gaOptOut = settings.GetValueOrDefault(App.Constants.SettingGaOptOut, false);
SetAppOptOut(gaOptOut);
}
public void TrackAppEvent(string eventName, string label = null)
@@ -41,7 +39,6 @@ namespace Bit.iOS.Core.Services
public void TrackEvent(string category, string eventName, string label = null)
{
SetUserId();
var dict = DictionaryBuilder.CreateEvent(category, eventName, label, null).Build();
_tracker.Send(dict);
Gai.SharedInstance.Dispatch();
@@ -49,14 +46,12 @@ namespace Bit.iOS.Core.Services
public void TrackException(string message, bool fatal)
{
SetUserId();
var dict = DictionaryBuilder.CreateException(message, fatal).Build();
_tracker.Send(dict);
}
public void TrackPage(string pageName)
{
SetUserId();
_tracker.Set(GaiConstants.ScreenName, pageName);
var dict = DictionaryBuilder.CreateScreenView().Build();
_tracker.Send(dict);
@@ -70,13 +65,9 @@ namespace Bit.iOS.Core.Services
});
}
private void SetUserId()
public void SetAppOptOut(bool optOut)
{
if(_setUserId && _authService.IsAuthenticated)
{
_tracker.Set(GaiConstants.UserId, _authService.UserId);
_setUserId = false;
}
Gai.SharedInstance.OptOut = optOut;
}
}
}

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\packages\Xamarin.Build.Download.0.3.0\build\Xamarin.Build.Download.props" Condition="Exists('..\..\packages\Xamarin.Build.Download.0.3.0\build\Xamarin.Build.Download.props')" />
<Import Project="..\..\packages\Xamarin.Build.Download.0.4.3\build\Xamarin.Build.Download.props" Condition="Exists('..\..\packages\Xamarin.Build.Download.0.4.3\build\Xamarin.Build.Download.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
@@ -35,28 +35,23 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="Google.Analytics, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xamarin.Google.iOS.Analytics.3.17.0\lib\Xamarin.iOS10\Google.Analytics.dll</HintPath>
<Private>True</Private>
<HintPath>..\..\packages\Xamarin.Google.iOS.Analytics.3.17.0.1\lib\Xamarin.iOS10\Google.Analytics.dll</HintPath>
</Reference>
<Reference Include="HockeySDK, Version=1.0.6103.22142, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\HockeySDK.Xamarin.4.1.0\lib\Xamarin.iOS10\HockeySDK.dll</HintPath>
<Private>True</Private>
<Reference Include="HockeySDK, Version=1.0.6288.33981, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\HockeySDK.Xamarin.4.1.2\lib\Xamarin.iOS10\HockeySDK.dll</HintPath>
</Reference>
<Reference Include="HockeySDK.iOSBindings, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\HockeySDK.Xamarin.4.1.0\lib\Xamarin.iOS10\HockeySDK.iOSBindings.dll</HintPath>
<Private>True</Private>
<HintPath>..\..\packages\HockeySDK.Xamarin.4.1.2\lib\Xamarin.iOS10\HockeySDK.iOSBindings.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\..\packages\Newtonsoft.Json.9.0.1\lib\portable-net45+wp80+win8+wpa81\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Plugin.Settings, Version=2.5.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xam.Plugins.Settings.2.5.1.0\lib\Xamarin.iOS10\Plugin.Settings.dll</HintPath>
<Private>True</Private>
<Reference Include="Plugin.Settings, Version=2.5.4.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xam.Plugins.Settings.2.5.4\lib\Xamarin.iOS10\Plugin.Settings.dll</HintPath>
</Reference>
<Reference Include="Plugin.Settings.Abstractions, Version=2.5.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xam.Plugins.Settings.2.5.1.0\lib\Xamarin.iOS10\Plugin.Settings.Abstractions.dll</HintPath>
<Private>True</Private>
<Reference Include="Plugin.Settings.Abstractions, Version=2.5.4.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xam.Plugins.Settings.2.5.4\lib\Xamarin.iOS10\Plugin.Settings.Abstractions.dll</HintPath>
</Reference>
<Reference Include="SQLite-net, Version=1.1.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\sqlite-net-pcl.1.2.1\lib\portable-net45+wp8+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLite-net.dll</HintPath>
@@ -138,10 +133,10 @@
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\packages\Xamarin.Build.Download.0.3.0\build\Xamarin.Build.Download.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Xamarin.Build.Download.0.3.0\build\Xamarin.Build.Download.props'))" />
<Error Condition="!Exists('..\..\packages\Xamarin.Build.Download.0.3.0\build\Xamarin.Build.Download.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Xamarin.Build.Download.0.3.0\build\Xamarin.Build.Download.targets'))" />
<Error Condition="!Exists('..\..\packages\Xamarin.Google.iOS.Analytics.3.17.0\build\Xamarin.Google.iOS.Analytics.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Xamarin.Google.iOS.Analytics.3.17.0\build\Xamarin.Google.iOS.Analytics.targets'))" />
<Error Condition="!Exists('..\..\packages\Xamarin.Build.Download.0.4.3\build\Xamarin.Build.Download.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Xamarin.Build.Download.0.4.3\build\Xamarin.Build.Download.props'))" />
<Error Condition="!Exists('..\..\packages\Xamarin.Build.Download.0.4.3\build\Xamarin.Build.Download.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Xamarin.Build.Download.0.4.3\build\Xamarin.Build.Download.targets'))" />
<Error Condition="!Exists('..\..\packages\Xamarin.Google.iOS.Analytics.3.17.0.1\build\Xamarin.Google.iOS.Analytics.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Xamarin.Google.iOS.Analytics.3.17.0.1\build\Xamarin.Google.iOS.Analytics.targets'))" />
</Target>
<Import Project="..\..\packages\Xamarin.Build.Download.0.3.0\build\Xamarin.Build.Download.targets" Condition="Exists('..\..\packages\Xamarin.Build.Download.0.3.0\build\Xamarin.Build.Download.targets')" />
<Import Project="..\..\packages\Xamarin.Google.iOS.Analytics.3.17.0\build\Xamarin.Google.iOS.Analytics.targets" Condition="Exists('..\..\packages\Xamarin.Google.iOS.Analytics.3.17.0\build\Xamarin.Google.iOS.Analytics.targets')" />
<Import Project="..\..\packages\Xamarin.Build.Download.0.4.3\build\Xamarin.Build.Download.targets" Condition="Exists('..\..\packages\Xamarin.Build.Download.0.4.3\build\Xamarin.Build.Download.targets')" />
<Import Project="..\..\packages\Xamarin.Google.iOS.Analytics.3.17.0.1\build\Xamarin.Google.iOS.Analytics.targets" Condition="Exists('..\..\packages\Xamarin.Google.iOS.Analytics.3.17.0.1\build\Xamarin.Google.iOS.Analytics.targets')" />
</Project>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="HockeySDK.Xamarin" version="4.1.0" targetFramework="xamarinios10" />
<package id="HockeySDK.Xamarin" version="4.1.2" targetFramework="xamarinios10" />
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="xamarinios10" />
<package id="sqlite-net-pcl" version="1.2.1" targetFramework="xamarinios10" />
<package id="SQLitePCL.bundle_green" version="0.9.3" targetFramework="xamarinios10" />
@@ -8,8 +8,8 @@
<package id="SQLitePCLRaw.bundle_green" version="1.1.2" targetFramework="xamarinios10" />
<package id="SQLitePCLRaw.core" version="1.1.2" targetFramework="xamarinios10" />
<package id="SQLitePCLRaw.provider.sqlite3.ios_unified" version="1.1.2" targetFramework="xamarinios10" />
<package id="Xam.Plugins.Settings" version="2.5.1.0" targetFramework="xamarinios10" />
<package id="Xamarin.Build.Download" version="0.3.0" targetFramework="xamarinios10" />
<package id="Xamarin.Google.iOS.Analytics" version="3.17.0" targetFramework="xamarinios10" />
<package id="Xam.Plugins.Settings" version="2.5.4" targetFramework="xamarinios10" />
<package id="Xamarin.Build.Download" version="0.4.3" targetFramework="xamarinios10" />
<package id="Xamarin.Google.iOS.Analytics" version="3.17.0.1" targetFramework="xamarinios10" />
<package id="XLabs.IoC" version="2.0.5782" targetFramework="xamarinios10" />
</packages>

View File

@@ -4,8 +4,6 @@
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>iOS.Extension</string>
<key>CFBundleIdentifier</key>
<string>com.8bit.bitwarden.find-login-action-extension</string>
<key>CFBundleInfoDictionaryVersion</key>
@@ -13,11 +11,11 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.4.1</string>
<string>1.5.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>11</string>
<string>13</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
@@ -74,6 +72,8 @@
<string>es</string>
<string>zh-Hans</string>
<string>sv</string>
<string>fi</string>
<string>fr</string>
</array>
</dict>
</plist>

View File

@@ -30,7 +30,7 @@ namespace Bit.iOS.Extension
private readonly JsonSerializerSettings _jsonSettings =
new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
private IGoogleAnalyticsService _googleAnalyticsService;
private ISettings _settings;
private ILockService _lockService;
public LoadingViewController(IntPtr handle) : base(handle)
{ }
@@ -44,7 +44,7 @@ namespace Bit.iOS.Extension
View.BackgroundColor = new UIColor(red: 0.94f, green: 0.94f, blue: 0.96f, alpha: 1.0f);
_context.ExtContext = ExtensionContext;
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
_settings = Resolver.Resolve<ISettings>();
_lockService = Resolver.Resolve<ILockService>();
if(!_setupHockeyApp)
{
@@ -175,7 +175,7 @@ namespace Bit.iOS.Extension
private void ContinueOn()
{
Debug.WriteLine("BW Log, Segue to setup, login add or list.");
_settings.AddOrUpdateValue(App.Constants.LastActivityDate, DateTime.UtcNow);
_lockService.UpdateLastActivity();
if(_context.ProviderType == Constants.UTTypeAppExtensionSaveLoginAction)
{
@@ -240,7 +240,7 @@ namespace Bit.iOS.Extension
if(itemData != null)
{
_settings.AddOrUpdateValue(App.Constants.LastActivityDate, DateTime.UtcNow);
_lockService.UpdateLastActivity();
_googleAnalyticsService.TrackExtensionEvent("AutoFilled", _context.ProviderType);
}
else
@@ -282,6 +282,8 @@ namespace Bit.iOS.Extension
.RegisterType<IHttpService, HttpService>(new ContainerControlledLifetimeManager())
.RegisterType<ITokenService, TokenService>(new ContainerControlledLifetimeManager())
.RegisterType<ISettingsService, SettingsService>(new ContainerControlledLifetimeManager())
.RegisterType<IDeviceInfoService, DeviceInfoService>(new ContainerControlledLifetimeManager())
.RegisterType<IAppSettingsService, AppSettingsService>(new ContainerControlledLifetimeManager())
// Repositories
.RegisterType<IFolderRepository, FolderRepository>(new ContainerControlledLifetimeManager())
.RegisterType<IFolderApiRepository, FolderApiRepository>(new ContainerControlledLifetimeManager())
@@ -289,6 +291,7 @@ namespace Bit.iOS.Extension
.RegisterType<ILoginApiRepository, LoginApiRepository>(new ContainerControlledLifetimeManager())
.RegisterType<IConnectApiRepository, ConnectApiRepository>(new ContainerControlledLifetimeManager())
.RegisterType<ISettingsRepository, SettingsRepository>(new ContainerControlledLifetimeManager())
.RegisterType<IAccountsApiRepository, AccountsApiRepository>(new ContainerControlledLifetimeManager())
// Other
.RegisterInstance(CrossConnectivity.Current, new ContainerControlledLifetimeManager())
.RegisterInstance(CrossFingerprint.Current, new ContainerControlledLifetimeManager());
@@ -307,7 +310,8 @@ namespace Bit.iOS.Extension
localizeService.SetLocale(ci);
}
private bool ProcessItemProvider(NSItemProvider itemProvider, string type, Action<NSDictionary> action)
private bool ProcessItemProvider(NSItemProvider itemProvider, string type, Action<NSDictionary> dictAction,
Action<NSUrl> urlAction = null)
{
if(!itemProvider.HasItemConformingTo(type))
{
@@ -322,8 +326,21 @@ namespace Bit.iOS.Extension
}
_context.ProviderType = type;
var dict = list as NSDictionary;
action(dict);
if(dict != null && dictAction != null)
{
dictAction(dict);
}
else if(list is NSUrl && urlAction != null)
{
var url = list as NSUrl;
urlAction(url);
}
else
{
throw new Exception("Cannot parse list for action.");
}
_googleAnalyticsService.TrackExtensionEvent("ProcessItemProvider", type);
@@ -391,6 +408,12 @@ namespace Bit.iOS.Extension
}
_context.Details = DeserializeDictionary<PageDetails>(dict[Constants.AppExtensionWebViewPageDetails] as NSDictionary);
}, (url) =>
{
if(url != null)
{
_context.UrlString = url.AbsoluteString;
}
});
}

Some files were not shown because too many files have changed in this diff Show More