mirror of
https://github.com/bitwarden/mobile
synced 2025-12-27 13:43:32 +00:00
reset for v2
This commit is contained in:
@@ -1,44 +0,0 @@
|
||||
using System.Net.Http;
|
||||
using System;
|
||||
using System.Net.Http.Headers;
|
||||
using XLabs.Ioc;
|
||||
using Bit.App.Abstractions;
|
||||
|
||||
namespace Bit.App
|
||||
{
|
||||
public class ApiHttpClient : HttpClient
|
||||
{
|
||||
public ApiHttpClient()
|
||||
{
|
||||
Init();
|
||||
}
|
||||
|
||||
public ApiHttpClient(HttpMessageHandler handler)
|
||||
: base(handler)
|
||||
{
|
||||
Init();
|
||||
}
|
||||
|
||||
private void Init()
|
||||
{
|
||||
DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
|
||||
var appSettings = Resolver.Resolve<IAppSettingsService>();
|
||||
if(!string.IsNullOrWhiteSpace(appSettings.BaseUrl))
|
||||
{
|
||||
BaseAddress = new Uri($"{appSettings.BaseUrl}/api");
|
||||
}
|
||||
else if(!string.IsNullOrWhiteSpace(appSettings.ApiUrl))
|
||||
{
|
||||
BaseAddress = new Uri($"{appSettings.ApiUrl}");
|
||||
}
|
||||
else
|
||||
{
|
||||
//BaseAddress = new Uri("http://169.254.80.80:4000"); // Desktop from VS Android Emulator
|
||||
//BaseAddress = new Uri("http://192.168.1.3:4000"); // Desktop
|
||||
//BaseAddress = new Uri("https://preview-api.bitwarden.com"); // Preview
|
||||
BaseAddress = new Uri("https://api.bitwarden.com"); // Production
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Bit.App.Utilities
|
||||
{
|
||||
// ref: https://github.com/aspnet/Identity/blob/dev/src/Microsoft.Extensions.Identity.Core/Base32.cs
|
||||
// with some modifications for cleaning input
|
||||
public static class Base32
|
||||
{
|
||||
private static readonly string _base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||
|
||||
public static byte[] FromBase32(string input)
|
||||
{
|
||||
if(input == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(input));
|
||||
}
|
||||
|
||||
input = input.ToUpperInvariant();
|
||||
var cleanedInput = string.Empty;
|
||||
foreach(var c in input)
|
||||
{
|
||||
if(_base32Chars.IndexOf(c) < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
cleanedInput += c;
|
||||
}
|
||||
|
||||
input = cleanedInput;
|
||||
if(input.Length == 0)
|
||||
{
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
var output = new byte[input.Length * 5 / 8];
|
||||
var bitIndex = 0;
|
||||
var inputIndex = 0;
|
||||
var outputBits = 0;
|
||||
var outputIndex = 0;
|
||||
|
||||
while(outputIndex < output.Length)
|
||||
{
|
||||
var byteIndex = _base32Chars.IndexOf(input[inputIndex]);
|
||||
if(byteIndex < 0)
|
||||
{
|
||||
throw new FormatException();
|
||||
}
|
||||
|
||||
var bits = Math.Min(5 - bitIndex, 8 - outputBits);
|
||||
output[outputIndex] <<= bits;
|
||||
output[outputIndex] |= (byte)(byteIndex >> (5 - (bitIndex + bits)));
|
||||
|
||||
bitIndex += bits;
|
||||
if(bitIndex >= 5)
|
||||
{
|
||||
inputIndex++;
|
||||
bitIndex = 0;
|
||||
}
|
||||
|
||||
outputBits += bits;
|
||||
if(outputBits >= 8)
|
||||
{
|
||||
outputIndex++;
|
||||
outputBits = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Utilities
|
||||
{
|
||||
public static class Colors
|
||||
{
|
||||
public static Color Primary = Color.FromHex("3c8dbc");
|
||||
}
|
||||
}
|
||||
@@ -1,246 +0,0 @@
|
||||
using Bit.App.Enums;
|
||||
using Bit.App.Models;
|
||||
using PCLCrypto;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Bit.App.Utilities
|
||||
{
|
||||
public static class Crypto
|
||||
{
|
||||
private static string SteamChars = "23456789BCDFGHJKMNPQRTVWXY";
|
||||
|
||||
public static CipherString AesCbcEncrypt(byte[] plainBytes, SymmetricCryptoKey key)
|
||||
{
|
||||
var parts = AesCbcEncryptToParts(plainBytes, key);
|
||||
return new CipherString(parts.Item1, Convert.ToBase64String(parts.Item2),
|
||||
Convert.ToBase64String(parts.Item4), parts.Item3 != null ? Convert.ToBase64String(parts.Item3) : null);
|
||||
}
|
||||
|
||||
public static byte[] AesCbcEncryptToBytes(byte[] plainBytes, SymmetricCryptoKey key)
|
||||
{
|
||||
var parts = AesCbcEncryptToParts(plainBytes, key);
|
||||
var macLength = parts.Item3?.Length ?? 0;
|
||||
|
||||
var encBytes = new byte[1 + parts.Item2.Length + macLength + parts.Item4.Length];
|
||||
encBytes[0] = (byte)parts.Item1;
|
||||
parts.Item2.CopyTo(encBytes, 1);
|
||||
if(parts.Item3 != null)
|
||||
{
|
||||
parts.Item3.CopyTo(encBytes, 1 + parts.Item2.Length);
|
||||
}
|
||||
parts.Item4.CopyTo(encBytes, 1 + parts.Item2.Length + macLength);
|
||||
return encBytes;
|
||||
}
|
||||
|
||||
private static Tuple<EncryptionType, byte[], byte[], byte[]> AesCbcEncryptToParts(byte[] plainBytes,
|
||||
SymmetricCryptoKey key)
|
||||
{
|
||||
if(key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
if(plainBytes == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(plainBytes));
|
||||
}
|
||||
|
||||
var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7);
|
||||
var cryptoKey = provider.CreateSymmetricKey(key.EncKey);
|
||||
var iv = RandomBytes(provider.BlockLength);
|
||||
var ct = WinRTCrypto.CryptographicEngine.Encrypt(cryptoKey, plainBytes, iv);
|
||||
var mac = key.MacKey != null ? ComputeMac(ct, iv, key.MacKey) : null;
|
||||
|
||||
return new Tuple<EncryptionType, byte[], byte[], byte[]>(key.EncryptionType, iv, mac, ct);
|
||||
}
|
||||
|
||||
public static byte[] AesCbcDecrypt(CipherString encyptedValue, SymmetricCryptoKey key)
|
||||
{
|
||||
if(encyptedValue == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(encyptedValue));
|
||||
}
|
||||
|
||||
return AesCbcDecrypt(encyptedValue.EncryptionType, encyptedValue.CipherTextBytes,
|
||||
encyptedValue.InitializationVectorBytes, encyptedValue.MacBytes, key);
|
||||
}
|
||||
|
||||
public static byte[] AesCbcDecrypt(EncryptionType type, byte[] ct, byte[] iv, byte[] mac, SymmetricCryptoKey key)
|
||||
{
|
||||
if(key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
if(ct == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(ct));
|
||||
}
|
||||
|
||||
if(iv == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(iv));
|
||||
}
|
||||
|
||||
if(key.MacKey != null && mac == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(mac));
|
||||
}
|
||||
|
||||
if(key.EncryptionType != type)
|
||||
{
|
||||
throw new InvalidOperationException(nameof(type));
|
||||
}
|
||||
|
||||
if(key.MacKey != null && mac != null)
|
||||
{
|
||||
var computedMacBytes = ComputeMac(ct, iv, key.MacKey);
|
||||
if(!MacsEqual(computedMacBytes, mac))
|
||||
{
|
||||
throw new InvalidOperationException("MAC failed.");
|
||||
}
|
||||
}
|
||||
|
||||
var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7);
|
||||
var cryptoKey = provider.CreateSymmetricKey(key.EncKey);
|
||||
var decryptedBytes = WinRTCrypto.CryptographicEngine.Decrypt(cryptoKey, ct, iv);
|
||||
return decryptedBytes;
|
||||
}
|
||||
|
||||
public static byte[] RandomBytes(int length)
|
||||
{
|
||||
return WinRTCrypto.CryptographicBuffer.GenerateRandom(length);
|
||||
}
|
||||
|
||||
public static byte[] ComputeMac(byte[] ctBytes, byte[] ivBytes, byte[] macKey)
|
||||
{
|
||||
if(ctBytes == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(ctBytes));
|
||||
}
|
||||
|
||||
if(ivBytes == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(ivBytes));
|
||||
}
|
||||
|
||||
return ComputeMac(ivBytes.Concat(ctBytes), macKey);
|
||||
}
|
||||
|
||||
public static byte[] ComputeMac(IEnumerable<byte> dataBytes, byte[] macKey)
|
||||
{
|
||||
if(macKey == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(macKey));
|
||||
}
|
||||
|
||||
if(dataBytes == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(dataBytes));
|
||||
}
|
||||
|
||||
var algorithm = WinRTCrypto.MacAlgorithmProvider.OpenAlgorithm(MacAlgorithm.HmacSha256);
|
||||
var hasher = algorithm.CreateHash(macKey);
|
||||
hasher.Append(dataBytes.ToArray());
|
||||
var mac = hasher.GetValueAndReset();
|
||||
return mac;
|
||||
}
|
||||
|
||||
// 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/
|
||||
// ref: https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy
|
||||
public static bool MacsEqual(byte[] mac1, byte[] mac2)
|
||||
{
|
||||
var algorithm = WinRTCrypto.MacAlgorithmProvider.OpenAlgorithm(MacAlgorithm.HmacSha256);
|
||||
var hasher = algorithm.CreateHash(RandomBytes(32));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// ref: https://github.com/mirthas/totp-net/blob/master/TOTP/Totp.cs
|
||||
public static string Totp(string key)
|
||||
{
|
||||
var otpParams = new OtpAuth(key);
|
||||
var b32Key = Base32.FromBase32(otpParams.Secret);
|
||||
if(b32Key == null || b32Key.Length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var now = Helpers.EpocUtcNow() / 1000;
|
||||
var sec = now / otpParams.Period;
|
||||
|
||||
var secBytes = BitConverter.GetBytes(sec);
|
||||
if(BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(secBytes, 0, secBytes.Length);
|
||||
}
|
||||
|
||||
var algorithm = WinRTCrypto.MacAlgorithmProvider.OpenAlgorithm(otpParams.Algorithm);
|
||||
var hasher = algorithm.CreateHash(b32Key);
|
||||
hasher.Append(secBytes);
|
||||
var hash = hasher.GetValueAndReset();
|
||||
|
||||
var offset = (hash[hash.Length - 1] & 0xf);
|
||||
var binary = ((hash[offset] & 0x7f) << 24) | ((hash[offset + 1] & 0xff) << 16) |
|
||||
((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff);
|
||||
|
||||
string otp = string.Empty;
|
||||
if(otpParams.Steam)
|
||||
{
|
||||
var fullCode = binary & 0x7fffffff;
|
||||
for(var i = 0; i < otpParams.Digits; i++)
|
||||
{
|
||||
otp += SteamChars[fullCode % SteamChars.Length];
|
||||
fullCode = (int)Math.Truncate(fullCode / (double)SteamChars.Length);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var rawOtp = binary % (int)Math.Pow(10, otpParams.Digits);
|
||||
otp = rawOtp.ToString().PadLeft(otpParams.Digits, '0');
|
||||
}
|
||||
return otp;
|
||||
}
|
||||
|
||||
// ref: https://tools.ietf.org/html/rfc5869
|
||||
public static byte[] HkdfExpand(byte[] prk, byte[] info, int size)
|
||||
{
|
||||
var hashLen = 32; // sha256
|
||||
var okm = new byte[size];
|
||||
var previousT = new byte[0];
|
||||
var n = (int)Math.Ceiling((double)size / hashLen);
|
||||
for(int i = 0; i < n; i++)
|
||||
{
|
||||
var t = new byte[previousT.Length + info.Length + 1];
|
||||
previousT.CopyTo(t, 0);
|
||||
info.CopyTo(t, previousT.Length);
|
||||
t[t.Length - 1] = (byte)(i + 1);
|
||||
previousT = ComputeMac(t, prk);
|
||||
previousT.CopyTo(okm, i * hashLen);
|
||||
}
|
||||
return okm;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Bit.App.Utilities
|
||||
{
|
||||
public class ExtendedObservableCollection<T> : ObservableCollection<T>
|
||||
{
|
||||
public ExtendedObservableCollection() : base() { }
|
||||
public ExtendedObservableCollection(IEnumerable<T> collection) : base(collection) { }
|
||||
public ExtendedObservableCollection(List<T> list) : base(list) { }
|
||||
|
||||
public void AddRange(IEnumerable<T> range)
|
||||
{
|
||||
foreach(var item in range)
|
||||
{
|
||||
Items.Add(item);
|
||||
}
|
||||
|
||||
OnPropertyChanged(new PropertyChangedEventArgs("Count"));
|
||||
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
|
||||
public void ResetWithRange(IEnumerable<T> range)
|
||||
{
|
||||
Items.Clear();
|
||||
AddRange(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Models;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Controls;
|
||||
|
||||
namespace Bit.App
|
||||
{
|
||||
public static class Extentions
|
||||
{
|
||||
public static CipherString Encrypt(this string s, string orgId = null)
|
||||
{
|
||||
if(s == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(s));
|
||||
}
|
||||
|
||||
var cryptoService = Resolver.Resolve<ICryptoService>();
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(orgId))
|
||||
{
|
||||
return cryptoService.Encrypt(s, cryptoService.GetOrgKey(orgId));
|
||||
}
|
||||
|
||||
return cryptoService.Encrypt(s);
|
||||
}
|
||||
|
||||
public static bool IsPortrait(this Page page)
|
||||
{
|
||||
return page.Width < page.Height;
|
||||
}
|
||||
|
||||
public static bool IsLandscape(this Page page)
|
||||
{
|
||||
return !page.IsPortrait();
|
||||
}
|
||||
|
||||
public static void FocusWithDelay(this View view, int delay = 1000, bool forceDelay = false)
|
||||
{
|
||||
if(Device.RuntimePlatform == Device.Android || forceDelay)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(delay);
|
||||
Device.BeginInvokeOnMainThread(() => view.Focus());
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
view.Focus();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task PushForDeviceAsync(this INavigation navigation, Page page)
|
||||
{
|
||||
if (Device.RuntimePlatform != Device.UWP)
|
||||
{
|
||||
await navigation.PushModalAsync(new ExtendedNavigationPage(page), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
await navigation.PushAsync(page, true);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task PopForDeviceAsync(this INavigation navigation)
|
||||
{
|
||||
if(navigation.ModalStack.Count < 1)
|
||||
{
|
||||
if (navigation.NavigationStack.Count > 0 && Device.RuntimePlatform == Device.UWP)
|
||||
{
|
||||
await navigation.PopAsync();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
await navigation.PopModalAsync(true);
|
||||
}
|
||||
|
||||
public static void AdjustMarginsForDevice(this View view)
|
||||
{
|
||||
if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
var deviceInfo = Resolver.Resolve<IDeviceInfoService>();
|
||||
if(deviceInfo.Version < 21)
|
||||
{
|
||||
view.Margin = new Thickness(-12, -5, -12, -6);
|
||||
}
|
||||
else if(deviceInfo.Version == 21)
|
||||
{
|
||||
view.Margin = new Thickness(-4, -2, -4, -11);
|
||||
}
|
||||
else
|
||||
{
|
||||
view.Margin = new Thickness(-4, -7, -4, -11);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void AdjustPaddingForDevice(this Layout view)
|
||||
{
|
||||
if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
var deviceInfo = Resolver.Resolve<IDeviceInfoService>();
|
||||
if(deviceInfo.Scale == 1) // mdpi
|
||||
{
|
||||
view.Padding = new Thickness(22, view.Padding.Top, 22, view.Padding.Bottom);
|
||||
}
|
||||
else if(deviceInfo.Scale < 2) // hdpi
|
||||
{
|
||||
view.Padding = new Thickness(19, view.Padding.Top, 19, view.Padding.Bottom);
|
||||
}
|
||||
else if(deviceInfo.Scale < 3) // xhdpi
|
||||
{
|
||||
view.Padding = new Thickness(17, view.Padding.Top, 17, view.Padding.Bottom);
|
||||
}
|
||||
else // xxhdpi and xxxhdpi
|
||||
{
|
||||
view.Padding = new Thickness(15, view.Padding.Top, 15, view.Padding.Bottom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool LastActionWasRecent(this DateTime? lastAction, int milliseconds = 1000)
|
||||
{
|
||||
if(lastAction.HasValue && (DateTime.UtcNow - lastAction.Value).TotalMilliseconds < milliseconds)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("Last action occurred recently.");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,647 +0,0 @@
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Enums;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Models.Page;
|
||||
using Bit.App.Pages;
|
||||
using Bit.App.Resources;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
|
||||
namespace Bit.App.Utilities
|
||||
{
|
||||
public static class Helpers
|
||||
{
|
||||
public static readonly DateTime Epoc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
public static IDictionary<UriMatchType?, string> UriMatchOptionsMap = new Dictionary<UriMatchType?, string>
|
||||
{
|
||||
[UriMatchType.Domain] = AppResources.BaseDomain,
|
||||
[UriMatchType.Host] = AppResources.Host,
|
||||
[UriMatchType.StartsWith] = AppResources.StartsWith,
|
||||
[UriMatchType.RegularExpression] = AppResources.RegEx,
|
||||
[UriMatchType.Exact] = AppResources.Exact,
|
||||
[UriMatchType.Never] = AppResources.Never
|
||||
};
|
||||
|
||||
public static long EpocUtcNow()
|
||||
{
|
||||
return (long)(DateTime.UtcNow - Epoc).TotalMilliseconds;
|
||||
}
|
||||
|
||||
public static T OnPlatform<T>(T iOS = default(T), T Android = default(T),
|
||||
T WinPhone = default(T), T Windows = default(T), string platform = null)
|
||||
{
|
||||
if(platform == null)
|
||||
{
|
||||
platform = Device.RuntimePlatform;
|
||||
}
|
||||
|
||||
switch(platform)
|
||||
{
|
||||
case Device.iOS:
|
||||
return iOS;
|
||||
case Device.Android:
|
||||
return Android;
|
||||
case Device.UWP:
|
||||
return Windows;
|
||||
default:
|
||||
throw new Exception("Unsupported platform.");
|
||||
}
|
||||
}
|
||||
|
||||
public static bool InDebugMode()
|
||||
{
|
||||
#if DEBUG
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static bool PerformUpdateTasks(ISettings settings,
|
||||
IAppInfoService appInfoService, IDatabaseService databaseService, ISyncService syncService)
|
||||
{
|
||||
var lastBuild = settings.GetValueOrDefault(Constants.LastBuildKey, null);
|
||||
if(InDebugMode() || lastBuild == null || lastBuild != appInfoService.Build)
|
||||
{
|
||||
settings.AddOrUpdateValue(Constants.LastBuildKey, appInfoService.Build);
|
||||
databaseService.CreateTables();
|
||||
var task = Task.Run(async () => await syncService.FullSyncAsync(true));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static string GetEmptyTableSectionTitle()
|
||||
{
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return " ";
|
||||
}
|
||||
|
||||
public static string ToolbarImage(string image)
|
||||
{
|
||||
if(Device.RuntimePlatform == Device.iOS || Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
public static async void CipherMoreClickedAsync(Page page, VaultListPageModel.Cipher cipher, bool autofill)
|
||||
{
|
||||
var buttons = new List<string> { AppResources.View, AppResources.Edit };
|
||||
|
||||
if(cipher.Type == CipherType.Login)
|
||||
{
|
||||
if(!string.IsNullOrWhiteSpace(cipher.LoginPassword.Value))
|
||||
{
|
||||
buttons.Add(AppResources.CopyPassword);
|
||||
}
|
||||
if(!string.IsNullOrWhiteSpace(cipher.LoginUsername))
|
||||
{
|
||||
buttons.Add(AppResources.CopyUsername);
|
||||
}
|
||||
if(!autofill && !string.IsNullOrWhiteSpace(cipher.LoginUri) && (cipher.LoginUri.StartsWith("http://")
|
||||
|| cipher.LoginUri.StartsWith("https://")))
|
||||
{
|
||||
buttons.Add(AppResources.GoToWebsite);
|
||||
}
|
||||
}
|
||||
else if(cipher.Type == CipherType.Card)
|
||||
{
|
||||
if(!string.IsNullOrWhiteSpace(cipher.CardNumber))
|
||||
{
|
||||
buttons.Add(AppResources.CopyNumber);
|
||||
}
|
||||
if(!string.IsNullOrWhiteSpace(cipher.CardCode.Value))
|
||||
{
|
||||
buttons.Add(AppResources.CopySecurityCode);
|
||||
}
|
||||
}
|
||||
|
||||
var selection = await page.DisplayActionSheet(cipher.Name, AppResources.Cancel, null, buttons.ToArray());
|
||||
|
||||
if(selection == AppResources.View)
|
||||
{
|
||||
var p = new VaultViewCipherPage(cipher.Type, cipher.Id);
|
||||
await page.Navigation.PushForDeviceAsync(p);
|
||||
}
|
||||
else if(selection == AppResources.Edit)
|
||||
{
|
||||
var p = new VaultEditCipherPage(cipher.Id);
|
||||
await page.Navigation.PushForDeviceAsync(p);
|
||||
}
|
||||
else if(selection == AppResources.CopyPassword)
|
||||
{
|
||||
CipherCopy(cipher.LoginPassword.Value, AppResources.Password);
|
||||
}
|
||||
else if(selection == AppResources.CopyUsername)
|
||||
{
|
||||
CipherCopy(cipher.LoginUsername, AppResources.Username);
|
||||
}
|
||||
else if(selection == AppResources.GoToWebsite)
|
||||
{
|
||||
Device.OpenUri(new Uri(cipher.LoginUri));
|
||||
}
|
||||
else if(selection == AppResources.CopyNumber)
|
||||
{
|
||||
CipherCopy(cipher.CardNumber, AppResources.Number);
|
||||
}
|
||||
else if(selection == AppResources.CopySecurityCode)
|
||||
{
|
||||
CipherCopy(cipher.CardCode.Value, AppResources.SecurityCode);
|
||||
}
|
||||
}
|
||||
|
||||
public static void CipherCopy(string copyText, string alertLabel)
|
||||
{
|
||||
var daService = Resolver.Resolve<IDeviceActionService>();
|
||||
daService.CopyToClipboard(copyText);
|
||||
daService.Toast(string.Format(AppResources.ValueHasBeenCopied, alertLabel));
|
||||
}
|
||||
|
||||
public static async void AddCipher(Page page, string folderId)
|
||||
{
|
||||
var type = await page.DisplayActionSheet(AppResources.SelectTypeAdd, AppResources.Cancel, null,
|
||||
AppResources.TypeLogin, AppResources.TypeCard, AppResources.TypeIdentity, AppResources.TypeSecureNote);
|
||||
|
||||
var selectedType = CipherType.SecureNote;
|
||||
if(type == null || type == AppResources.Cancel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if(type == AppResources.TypeLogin)
|
||||
{
|
||||
selectedType = CipherType.Login;
|
||||
}
|
||||
else if(type == AppResources.TypeCard)
|
||||
{
|
||||
selectedType = CipherType.Card;
|
||||
}
|
||||
else if(type == AppResources.TypeIdentity)
|
||||
{
|
||||
selectedType = CipherType.Identity;
|
||||
}
|
||||
else if(type == AppResources.TypeSecureNote)
|
||||
{
|
||||
selectedType = CipherType.SecureNote;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var addPage = new VaultAddCipherPage(selectedType, defaultFolderId: folderId);
|
||||
await page.Navigation.PushForDeviceAsync(addPage);
|
||||
}
|
||||
|
||||
public static async Task AddField(Page page, TableSection fieldsSection)
|
||||
{
|
||||
var type = await page.DisplayActionSheet(AppResources.SelectTypeField, AppResources.Cancel, null,
|
||||
AppResources.FieldTypeText, AppResources.FieldTypeHidden, AppResources.FieldTypeBoolean);
|
||||
|
||||
FieldType fieldType;
|
||||
if(type == AppResources.FieldTypeText)
|
||||
{
|
||||
fieldType = FieldType.Text;
|
||||
}
|
||||
else if(type == AppResources.FieldTypeHidden)
|
||||
{
|
||||
fieldType = FieldType.Hidden;
|
||||
}
|
||||
else if(type == AppResources.FieldTypeBoolean)
|
||||
{
|
||||
fieldType = FieldType.Boolean;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var daService = Resolver.Resolve<IDeviceActionService>();
|
||||
var label = await daService.DisplayPromptAync(AppResources.CustomFieldName);
|
||||
if(label == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var cell = MakeFieldCell(fieldType, label, string.Empty, fieldsSection, page);
|
||||
if(cell != null)
|
||||
{
|
||||
fieldsSection.Insert(fieldsSection.Count - 1, cell);
|
||||
if(cell is FormEntryCell feCell)
|
||||
{
|
||||
feCell.InitEvents();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Cell MakeFieldCell(FieldType type, string label, string value,
|
||||
TableSection fieldsSection, Page page)
|
||||
{
|
||||
Cell cell;
|
||||
FormEntryCell feCell = null;
|
||||
FormSwitchCell fsCell = null;
|
||||
switch(type)
|
||||
{
|
||||
case FieldType.Text:
|
||||
case FieldType.Hidden:
|
||||
var hidden = type == FieldType.Hidden;
|
||||
cell = feCell = new FormEntryCell(label, isPassword: hidden,
|
||||
button1: hidden ? "eye.png" : "cog_alt.png", button2: hidden ? "cog_alt.png" : null);
|
||||
feCell.Entry.Text = value;
|
||||
feCell.Entry.DisableAutocapitalize = true;
|
||||
feCell.Entry.Autocorrect = false;
|
||||
|
||||
if(hidden)
|
||||
{
|
||||
feCell.Entry.FontFamily = OnPlatform(iOS: "Menlo-Regular", Android: "monospace",
|
||||
Windows: "Courier");
|
||||
feCell.Button1.Command = new Command(() =>
|
||||
{
|
||||
feCell.Entry.InvokeToggleIsPassword();
|
||||
feCell.Button1.Image = "eye" +
|
||||
(!feCell.Entry.IsPasswordFromToggled ? "_slash" : string.Empty) + ".png";
|
||||
});
|
||||
}
|
||||
break;
|
||||
case FieldType.Boolean:
|
||||
cell = fsCell = new FormSwitchCell(label, "cog_alt.png");
|
||||
fsCell.Switch.IsToggled = value == "true";
|
||||
break;
|
||||
default:
|
||||
cell = null;
|
||||
break;
|
||||
}
|
||||
|
||||
if(cell != null)
|
||||
{
|
||||
var optionsButton = feCell != null ? feCell.Button2 ?? feCell.Button1 : fsCell.Button1;
|
||||
optionsButton.Command = new Command(async () =>
|
||||
{
|
||||
var optionsVal = await page.DisplayActionSheet(AppResources.Options, AppResources.Cancel,
|
||||
null, AppResources.Edit, AppResources.Remove);
|
||||
if(optionsVal == AppResources.Remove)
|
||||
{
|
||||
if(fieldsSection.Contains(cell))
|
||||
{
|
||||
fieldsSection.Remove(cell);
|
||||
}
|
||||
|
||||
if(feCell != null)
|
||||
{
|
||||
feCell.Dispose();
|
||||
}
|
||||
cell = null;
|
||||
feCell = null;
|
||||
fsCell = null;
|
||||
}
|
||||
else if(optionsVal == AppResources.Edit)
|
||||
{
|
||||
var existingLabel = feCell?.Label.Text ?? fsCell?.Label.Text;
|
||||
var daService = Resolver.Resolve<IDeviceActionService>();
|
||||
var editLabel = await daService.DisplayPromptAync(AppResources.CustomFieldName,
|
||||
null, existingLabel);
|
||||
if(editLabel != null)
|
||||
{
|
||||
if(feCell != null)
|
||||
{
|
||||
feCell.Label.Text = editLabel;
|
||||
}
|
||||
else if(fsCell != null)
|
||||
{
|
||||
fsCell.Label.Text = editLabel;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
public static List<Tuple<string, string>> ProcessFieldsSectionForSave(TableSection fieldsSection, Cipher cipher)
|
||||
{
|
||||
var hiddenFieldValues = new List<Tuple<string, string>>();
|
||||
if(fieldsSection != null && fieldsSection.Count > 0)
|
||||
{
|
||||
var fields = new List<Field>();
|
||||
foreach(var cell in fieldsSection)
|
||||
{
|
||||
if(cell is FormEntryCell entryCell)
|
||||
{
|
||||
var type = entryCell.Entry.IsPassword || entryCell.Button2 != null ?
|
||||
FieldType.Hidden : FieldType.Text;
|
||||
fields.Add(new Field
|
||||
{
|
||||
Name = string.IsNullOrWhiteSpace(entryCell.Label.Text) ? null :
|
||||
entryCell.Label.Text.Encrypt(cipher.OrganizationId),
|
||||
Value = string.IsNullOrWhiteSpace(entryCell.Entry.Text) ? null :
|
||||
entryCell.Entry.Text.Encrypt(cipher.OrganizationId),
|
||||
Type = type
|
||||
});
|
||||
|
||||
if(type == FieldType.Hidden && !string.IsNullOrWhiteSpace(entryCell.Label.Text))
|
||||
{
|
||||
hiddenFieldValues.Add(new Tuple<string, string>(entryCell.Label.Text,
|
||||
entryCell.Entry.Text));
|
||||
}
|
||||
}
|
||||
else if(cell is FormSwitchCell switchCell)
|
||||
{
|
||||
var value = switchCell.Switch.IsToggled ? "true" : "false";
|
||||
fields.Add(new Field
|
||||
{
|
||||
Name = string.IsNullOrWhiteSpace(switchCell.Label.Text) ? null :
|
||||
switchCell.Label.Text.Encrypt(cipher.OrganizationId),
|
||||
Value = value.Encrypt(cipher.OrganizationId),
|
||||
Type = FieldType.Boolean
|
||||
});
|
||||
}
|
||||
}
|
||||
cipher.Fields = fields;
|
||||
}
|
||||
|
||||
if(!cipher.Fields?.Any() ?? true)
|
||||
{
|
||||
cipher.Fields = null;
|
||||
}
|
||||
return hiddenFieldValues;
|
||||
}
|
||||
|
||||
public static FormEntryCell MakeUriCell(string value, UriMatchType? match, TableSection urisSection, Page page)
|
||||
{
|
||||
var label = string.Format(AppResources.URIPosition, urisSection.Count);
|
||||
var cell = new FormEntryCell(label, entryKeyboard: Keyboard.Url, button1: "cog_alt.png");
|
||||
cell.Entry.Text = value;
|
||||
cell.Entry.DisableAutocapitalize = true;
|
||||
cell.Entry.Autocorrect = false;
|
||||
cell.MetaData = new Dictionary<string, object> { ["match"] = match };
|
||||
|
||||
cell.Button1.Command = new Command(async () =>
|
||||
{
|
||||
var optionsVal = await page.DisplayActionSheet(AppResources.Options, AppResources.Cancel,
|
||||
null, AppResources.MatchDetection, AppResources.Remove);
|
||||
|
||||
if(optionsVal == AppResources.MatchDetection)
|
||||
{
|
||||
var options = UriMatchOptionsMap.Select(v => v.Value).ToList();
|
||||
options.Insert(0, AppResources.Default);
|
||||
var exactingMatchVal = cell.MetaData["match"] as UriMatchType?;
|
||||
|
||||
var matchIndex = exactingMatchVal.HasValue ?
|
||||
Array.IndexOf(UriMatchOptionsMap.Keys.ToArray(), exactingMatchVal) + 1 : 0;
|
||||
options[matchIndex] = $"✓ {options[matchIndex]}";
|
||||
|
||||
var optionsArr = options.ToArray();
|
||||
var val = await page.DisplayActionSheet(AppResources.URIMatchDetection, AppResources.Cancel,
|
||||
null, options.ToArray());
|
||||
|
||||
UriMatchType? selectedVal = null;
|
||||
if(val == null || val == AppResources.Cancel)
|
||||
{
|
||||
selectedVal = exactingMatchVal;
|
||||
}
|
||||
else if(val.Replace("✓ ", string.Empty) != AppResources.Default)
|
||||
{
|
||||
selectedVal = UriMatchOptionsMap.ElementAt(Array.IndexOf(optionsArr, val) - 1).Key;
|
||||
}
|
||||
cell.MetaData["match"] = selectedVal;
|
||||
}
|
||||
else if(optionsVal == AppResources.Remove)
|
||||
{
|
||||
if(urisSection.Contains(cell))
|
||||
{
|
||||
urisSection.Remove(cell);
|
||||
if(cell is FormEntryCell feCell)
|
||||
{
|
||||
feCell.Dispose();
|
||||
}
|
||||
cell = null;
|
||||
|
||||
for(int i = 0; i < urisSection.Count; i++)
|
||||
{
|
||||
if(urisSection[i] is FormEntryCell uriCell)
|
||||
{
|
||||
uriCell.Label.Text = string.Format(AppResources.URIPosition, i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
public static void ProcessUrisSectionForSave(TableSection urisSection, Cipher cipher)
|
||||
{
|
||||
if(urisSection != null && urisSection.Count > 0)
|
||||
{
|
||||
var uris = new List<LoginUri>();
|
||||
foreach(var cell in urisSection)
|
||||
{
|
||||
if(cell is FormEntryCell entryCell && !string.IsNullOrWhiteSpace(entryCell.Entry.Text))
|
||||
{
|
||||
var match = entryCell?.MetaData["match"] as UriMatchType?;
|
||||
uris.Add(new LoginUri
|
||||
{
|
||||
Uri = entryCell.Entry.Text.Encrypt(cipher.OrganizationId),
|
||||
Match = match
|
||||
});
|
||||
}
|
||||
}
|
||||
cipher.Login.Uris = uris;
|
||||
}
|
||||
|
||||
if(!cipher.Login.Uris?.Any() ?? true)
|
||||
{
|
||||
cipher.Login.Uris = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void InitSectionEvents(TableSection section)
|
||||
{
|
||||
if(section != null && section.Count > 0)
|
||||
{
|
||||
foreach(var cell in section)
|
||||
{
|
||||
if(cell is FormEntryCell entrycell)
|
||||
{
|
||||
entrycell.InitEvents();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void DisposeSectionEvents(TableSection section)
|
||||
{
|
||||
if(section != null && section.Count > 0)
|
||||
{
|
||||
foreach(var cell in section)
|
||||
{
|
||||
if(cell is FormEntryCell entrycell)
|
||||
{
|
||||
entrycell.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetUrlHost(string url)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(url))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
url = url.Trim();
|
||||
if(url == string.Empty)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if(!url.Contains("://"))
|
||||
{
|
||||
url = $"http://{url}";
|
||||
}
|
||||
|
||||
if(!Uri.TryCreate(url, UriKind.Absolute, out Uri u))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var host = u.Host;
|
||||
if(!u.IsDefaultPort)
|
||||
{
|
||||
host = $"{host}:{u.Port}";
|
||||
}
|
||||
|
||||
return host;
|
||||
}
|
||||
|
||||
public static void AlertNoConnection(Page page)
|
||||
{
|
||||
page.DisplayAlert(AppResources.InternetConnectionRequiredTitle,
|
||||
AppResources.InternetConnectionRequiredMessage, AppResources.Ok);
|
||||
}
|
||||
|
||||
public static Dictionary<string, string> GetQueryParams(string urlString)
|
||||
{
|
||||
var dict = new Dictionary<string, string>();
|
||||
if(!Uri.TryCreate(urlString, UriKind.Absolute, out var uri) || string.IsNullOrWhiteSpace(uri.Query))
|
||||
{
|
||||
return dict;
|
||||
}
|
||||
|
||||
var pairs = uri.Query.Substring(1).Split('&');
|
||||
foreach(var pair in pairs)
|
||||
{
|
||||
var parts = pair.Split('=');
|
||||
if(parts.Length < 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var key = System.Net.WebUtility.UrlDecode(parts[0]).ToLower();
|
||||
if(!dict.ContainsKey(key))
|
||||
{
|
||||
dict.Add(key, parts[1] == null ? string.Empty : System.Net.WebUtility.UrlDecode(parts[1]));
|
||||
}
|
||||
}
|
||||
return dict;
|
||||
}
|
||||
|
||||
public static bool CanAccessPremium()
|
||||
{
|
||||
var tokenService = Resolver.Resolve<ITokenService>();
|
||||
if(tokenService?.TokenPremium ?? false)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
var appSettingsService = Resolver.Resolve<IAppSettingsService>();
|
||||
return appSettingsService?.OrganizationGivesPremium ?? false;
|
||||
}
|
||||
|
||||
public static void NestedTraverse<T>(List<TreeNode<T>> nodeTree, int partIndex, string[] parts,
|
||||
T obj, T parent, string delimiter) where T : ITreeNodeObject
|
||||
{
|
||||
if(parts.Length <= partIndex)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var end = partIndex == parts.Length - 1;
|
||||
var partName = parts[partIndex];
|
||||
|
||||
foreach(var n in nodeTree)
|
||||
{
|
||||
if(n.Node.Name != parts[partIndex])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if(end && n.Node.Id != obj.Id)
|
||||
{
|
||||
// Another node with the same name.
|
||||
nodeTree.Add(new TreeNode<T>(obj, partName, parent));
|
||||
return;
|
||||
}
|
||||
NestedTraverse(n.Children, partIndex + 1, parts, obj, n.Node, delimiter);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!nodeTree.Any(n => n.Node.Name == partName))
|
||||
{
|
||||
if(end)
|
||||
{
|
||||
nodeTree.Add(new TreeNode<T>(obj, partName, parent));
|
||||
return;
|
||||
}
|
||||
var newPartName = string.Concat(parts[partIndex], delimiter, parts[partIndex + 1]);
|
||||
var newParts = new List<string> { newPartName };
|
||||
var newPartsStartFrom = partIndex + 2;
|
||||
newParts.AddRange(new ArraySegment<string>(parts, newPartsStartFrom, parts.Length - newPartsStartFrom));
|
||||
NestedTraverse(nodeTree, 0, newParts.ToArray(), obj, parent, delimiter);
|
||||
}
|
||||
}
|
||||
|
||||
public static TreeNode<T> GetTreeNodeObject<T>(List<TreeNode<T>> nodeTree, string id) where T : ITreeNodeObject
|
||||
{
|
||||
foreach(var n in nodeTree)
|
||||
{
|
||||
if(n.Node.Id == id)
|
||||
{
|
||||
return n;
|
||||
}
|
||||
else if(n.Children != null)
|
||||
{
|
||||
var node = GetTreeNodeObject(n.Children, id);
|
||||
if(node != null)
|
||||
{
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static List<TreeNode<T>> GetAllNested<T>(IEnumerable<T> objs) where T : ITreeNodeObject
|
||||
{
|
||||
var nodes = new List<TreeNode<T>>();
|
||||
foreach(var o in objs)
|
||||
{
|
||||
NestedTraverse(nodes, 0, o.Name.Split('/'), o, default(T), "/");
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using System.Net.Http;
|
||||
using System;
|
||||
using System.Net.Http.Headers;
|
||||
using XLabs.Ioc;
|
||||
using Bit.App.Abstractions;
|
||||
|
||||
namespace Bit.App
|
||||
{
|
||||
public class IdentityHttpClient : HttpClient
|
||||
{
|
||||
public IdentityHttpClient()
|
||||
{
|
||||
Init();
|
||||
}
|
||||
|
||||
public IdentityHttpClient(HttpMessageHandler handler)
|
||||
: base(handler)
|
||||
{
|
||||
Init();
|
||||
}
|
||||
|
||||
private void Init()
|
||||
{
|
||||
DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
|
||||
var appSettings = Resolver.Resolve<IAppSettingsService>();
|
||||
if(!string.IsNullOrWhiteSpace(appSettings.BaseUrl))
|
||||
{
|
||||
BaseAddress = new Uri($"{appSettings.BaseUrl}/identity");
|
||||
}
|
||||
else if(!string.IsNullOrWhiteSpace(appSettings.IdentityUrl))
|
||||
{
|
||||
BaseAddress = new Uri($"{appSettings.IdentityUrl}");
|
||||
}
|
||||
else
|
||||
{
|
||||
//BaseAddress = new Uri("http://169.254.80.80:33656"); // Desktop from VS Android Emulator
|
||||
//BaseAddress = new Uri("http://192.168.1.3:33656"); // Desktop
|
||||
//BaseAddress = new Uri("https://preview-identity.bitwarden.com"); // Preview
|
||||
BaseAddress = new Uri("https://identity.bitwarden.com"); // Production
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Utilities
|
||||
{
|
||||
/**
|
||||
* Helper class to format a password with numeric encoding to separate
|
||||
* normal text from numbers and special characters.
|
||||
*/
|
||||
class PasswordFormatter
|
||||
{
|
||||
/**
|
||||
* This enum is used for the state machine when building the colorized
|
||||
* password string.
|
||||
*/
|
||||
private enum CharType
|
||||
{
|
||||
None,
|
||||
Normal,
|
||||
Number,
|
||||
Special
|
||||
}
|
||||
|
||||
public static FormattedString FormatPassword(String password)
|
||||
{
|
||||
var result = new FormattedString();
|
||||
|
||||
// Start off with an empty span to prevent possible NPEs. Due to the way the state machine
|
||||
// works, this will actually always be replaced by a new span anyway.
|
||||
var currentSpan = new Span();
|
||||
// Start with an otherwise uncovered case so we will definitely enter the "something changed"
|
||||
// state.
|
||||
var currentType = CharType.None;
|
||||
|
||||
foreach(var c in password)
|
||||
{
|
||||
// First, identify what the current char is.
|
||||
CharType charType;
|
||||
if(char.IsLetter(c))
|
||||
{
|
||||
charType = CharType.Normal;
|
||||
}
|
||||
else if(char.IsDigit(c))
|
||||
{
|
||||
charType = CharType.Number;
|
||||
}
|
||||
else
|
||||
{
|
||||
charType = CharType.Special;
|
||||
}
|
||||
|
||||
// If the char type changed, build a new span to append the text to.
|
||||
if(charType != currentType)
|
||||
{
|
||||
currentSpan = new Span();
|
||||
result.Spans.Add(currentSpan);
|
||||
currentType = charType;
|
||||
|
||||
// Switch the color if it is not a normal text. Otherwise leave the
|
||||
// default value.
|
||||
switch(currentType)
|
||||
{
|
||||
case CharType.Number:
|
||||
currentSpan.TextColor = Color.DodgerBlue;
|
||||
break;
|
||||
case CharType.Special:
|
||||
currentSpan.TextColor = Color.Firebrick;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
currentSpan.Text += c;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Enums;
|
||||
using Bit.App.Utilities;
|
||||
using Newtonsoft.Json;
|
||||
using XLabs.Ioc;
|
||||
|
||||
namespace Bit.App
|
||||
{
|
||||
public class TokenHttpRequestMessage : HttpRequestMessage
|
||||
{
|
||||
public TokenHttpRequestMessage()
|
||||
{
|
||||
var tokenService = Resolver.Resolve<ITokenService>();
|
||||
var appIdService = Resolver.Resolve<IAppIdService>();
|
||||
var deviceInfoService = Resolver.Resolve<IDeviceInfoService>();
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(tokenService.Token))
|
||||
{
|
||||
Headers.Add("Authorization", $"Bearer {tokenService.Token}");
|
||||
}
|
||||
if(!string.IsNullOrWhiteSpace(appIdService.AppId))
|
||||
{
|
||||
Headers.Add("Device-Identifier", appIdService.AppId);
|
||||
}
|
||||
|
||||
Headers.Add("Device-Type", ((int)Helpers.OnPlatform(iOS: DeviceType.iOS,
|
||||
Android: DeviceType.Android, Windows: DeviceType.UWP, platform: deviceInfoService.Type)).ToString());
|
||||
}
|
||||
|
||||
public TokenHttpRequestMessage(object requestObject)
|
||||
: this()
|
||||
{
|
||||
var stringContent = JsonConvert.SerializeObject(requestObject);
|
||||
Content = new StringContent(stringContent, Encoding.UTF8, "application/json");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user