1
0
mirror of https://github.com/bitwarden/mobile synced 2026-01-07 02:53:56 +00:00

reset for v2

This commit is contained in:
Kyle Spearrin
2019-03-27 16:23:00 -04:00
parent 5a7f106e3e
commit 297beac169
1180 changed files with 0 additions and 126197 deletions

View File

@@ -1,41 +0,0 @@
using System;
using Bit.App.Abstractions;
namespace Bit.App.Services
{
public class AppIdService : IAppIdService
{
private const string AppIdKey = "appId";
private const string AnonymousAppIdKey = "anonymousAppId";
private readonly ISecureStorageService _secureStorageService;
private Guid? _appId;
private Guid? _anonymousAppId;
public AppIdService(ISecureStorageService secureStorageService)
{
_secureStorageService = secureStorageService;
}
public string AppId => GetAppId(AppIdKey, ref _appId);
public string AnonymousAppId => GetAppId(AnonymousAppIdKey, ref _anonymousAppId);
private string GetAppId(string key, ref Guid? appId)
{
if(appId.HasValue)
{
return appId.Value.ToString();
}
var appIdBytes = _secureStorageService.Retrieve(key);
if(appIdBytes != null)
{
appId = new Guid(appIdBytes);
return appId.Value.ToString();
}
appId = Guid.NewGuid();
_secureStorageService.Store(key, appId.Value.ToByteArray());
return appId.Value.ToString();
}
}
}

View File

@@ -1,236 +0,0 @@
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 int FailedPinAttempts
{
get
{
return _settings.GetValueOrDefault(Constants.FailedPinAttempts, 0);
}
set
{
_settings.AddOrUpdateValue(Constants.FailedPinAttempts, value);
}
}
public DateTime LastActivity
{
get
{
return _settings.GetValueOrDefault(Constants.LastActivityDate, DateTime.MinValue);
}
set
{
_settings.AddOrUpdateValue(Constants.LastActivityDate, value);
}
}
public DateTime LastCacheClear
{
get
{
return _settings.GetValueOrDefault(Constants.LastCacheClearDate, DateTime.MinValue);
}
set
{
_settings.AddOrUpdateValue(Constants.LastCacheClearDate, value);
}
}
public bool AutofillPersistNotification
{
get
{
return _settings.GetValueOrDefault(Constants.AutofillPersistNotification, false);
}
set
{
_settings.AddOrUpdateValue(Constants.AutofillPersistNotification, value);
}
}
public bool AutofillPasswordField
{
get
{
return _settings.GetValueOrDefault(Constants.AutofillPasswordField, false);
}
set
{
_settings.AddOrUpdateValue(Constants.AutofillPasswordField, value);
}
}
public bool DisableWebsiteIcons
{
get
{
return _settings.GetValueOrDefault(Constants.SettingDisableWebsiteIcons, false);
}
set
{
_settings.AddOrUpdateValue(Constants.SettingDisableWebsiteIcons, value);
}
}
public string SecurityStamp
{
get
{
return _settings.GetValueOrDefault(Constants.SecurityStamp, null);
}
set
{
_settings.AddOrUpdateValue(Constants.SecurityStamp, value);
}
}
public string BaseUrl
{
get
{
return _settings.GetValueOrDefault(Constants.BaseUrl, null);
}
set
{
if(value == null)
{
_settings.Remove(Constants.BaseUrl);
return;
}
_settings.AddOrUpdateValue(Constants.BaseUrl, value);
}
}
public string WebVaultUrl
{
get
{
return _settings.GetValueOrDefault(Constants.WebVaultUrl, null);
}
set
{
if(value == null)
{
_settings.Remove(Constants.WebVaultUrl);
return;
}
_settings.AddOrUpdateValue(Constants.WebVaultUrl, value);
}
}
public string ApiUrl
{
get
{
return _settings.GetValueOrDefault(Constants.ApiUrl, null);
}
set
{
if(value == null)
{
_settings.Remove(Constants.ApiUrl);
return;
}
_settings.AddOrUpdateValue(Constants.ApiUrl, value);
}
}
public string IdentityUrl
{
get
{
return _settings.GetValueOrDefault(Constants.IdentityUrl, null);
}
set
{
if(value == null)
{
_settings.Remove(Constants.IdentityUrl);
return;
}
_settings.AddOrUpdateValue(Constants.IdentityUrl, value);
}
}
public string IconsUrl
{
get => _settings.GetValueOrDefault(Constants.IconsUrl, null);
set
{
if(value == null)
{
_settings.Remove(Constants.IconsUrl);
return;
}
_settings.AddOrUpdateValue(Constants.IconsUrl, value);
}
}
public bool ClearCiphersCache
{
get
{
return _settings.GetValueOrDefault(Constants.ClearCiphersCache, false);
}
set
{
_settings.AddOrUpdateValue(Constants.ClearCiphersCache, value);
}
}
public bool ClearExtensionCiphersCache
{
get
{
return _settings.GetValueOrDefault(Constants.ClearExtensionCiphersCache, false);
}
set
{
_settings.AddOrUpdateValue(Constants.ClearExtensionCiphersCache, value);
}
}
public bool OrganizationGivesPremium
{
get
{
return _settings.GetValueOrDefault(Constants.OrgGivesPremium, false);
}
set
{
_settings.AddOrUpdateValue(Constants.OrgGivesPremium, value);
}
}
}
}

View File

@@ -1,390 +0,0 @@
using System;
using System.Text;
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 Bit.App.Enums;
using Xamarin.Forms;
using Bit.App.Pages;
using Bit.App.Controls;
using XLabs.Ioc;
namespace Bit.App.Services
{
public class AuthService : IAuthService
{
private const string EmailKey = "email";
private const string KdfKey = "kdf";
private const string KdfIterationsKey = "kdfIterations";
private const string UserIdKey = "userId";
private const string PreviousUserIdKey = "previousUserId";
private const string PinKey = "pin";
private readonly ISecureStorageService _secureStorage;
private readonly ITokenService _tokenService;
private readonly ISettings _settings;
private readonly IAppSettingsService _appSettingsService;
private readonly ICryptoService _cryptoService;
private readonly IConnectApiRepository _connectApiRepository;
private readonly IAccountsApiRepository _accountsApiRepository;
private readonly IAppIdService _appIdService;
private readonly IDeviceInfoService _deviceInfoService;
private readonly IDeviceApiRepository _deviceApiRepository;
private readonly IGoogleAnalyticsService _googleAnalyticsService;
private string _email;
private KdfType? _kdf;
private int? _kdfIterations;
private string _userId;
private string _previousUserId;
private string _pin;
public AuthService(
ISecureStorageService secureStorage,
ITokenService tokenService,
ISettings settings,
IAppSettingsService appSettingsService,
ICryptoService cryptoService,
IConnectApiRepository connectApiRepository,
IAccountsApiRepository accountsApiRepository,
IAppIdService appIdService,
IDeviceInfoService deviceInfoService,
IDeviceApiRepository deviceApiRepository,
IGoogleAnalyticsService googleAnalyticsService)
{
_secureStorage = secureStorage;
_tokenService = tokenService;
_settings = settings;
_appSettingsService = appSettingsService;
_cryptoService = cryptoService;
_connectApiRepository = connectApiRepository;
_accountsApiRepository = accountsApiRepository;
_appIdService = appIdService;
_deviceInfoService = deviceInfoService;
_deviceApiRepository = deviceApiRepository;
_googleAnalyticsService = googleAnalyticsService;
}
public string UserId
{
get
{
if(!string.IsNullOrWhiteSpace(_userId))
{
return _userId;
}
var userId = _settings.GetValueOrDefault(UserIdKey, string.Empty);
if(!string.IsNullOrWhiteSpace(userId))
{
_userId = userId;
}
return _userId;
}
set
{
if(value != null)
{
_settings.AddOrUpdateValue(UserIdKey, value);
}
else
{
PreviousUserId = _userId;
_settings.Remove(UserIdKey);
}
_userId = value;
}
}
public string PreviousUserId
{
get
{
if(!string.IsNullOrWhiteSpace(_previousUserId))
{
return _previousUserId;
}
var previousUserId = _settings.GetValueOrDefault(PreviousUserIdKey, string.Empty);
if(!string.IsNullOrWhiteSpace(previousUserId))
{
_previousUserId = previousUserId;
}
return _previousUserId;
}
private set
{
if(value != null)
{
_settings.AddOrUpdateValue(PreviousUserIdKey, value);
_previousUserId = value;
}
}
}
public bool UserIdChanged => PreviousUserId != UserId;
public string Email
{
get
{
if(!string.IsNullOrWhiteSpace(_email))
{
return _email;
}
var email = _settings.GetValueOrDefault(EmailKey, string.Empty);
if(!string.IsNullOrWhiteSpace(email))
{
_email = email;
}
return _email;
}
set
{
if(value != null)
{
_settings.AddOrUpdateValue(EmailKey, value);
}
else
{
_settings.Remove(EmailKey);
}
_email = value;
}
}
public KdfType Kdf
{
get
{
if(!_kdf.HasValue)
{
_kdf = (KdfType)_settings.GetValueOrDefault(KdfKey, (short)KdfType.PBKDF2_SHA256);
}
return _kdf.Value;
}
set
{
_settings.AddOrUpdateValue(KdfKey, (short)value);
_kdf = value;
}
}
public int KdfIterations
{
get
{
if(!_kdfIterations.HasValue)
{
_kdfIterations = _settings.GetValueOrDefault(KdfIterationsKey, 5000);
}
return _kdfIterations.Value;
}
set
{
_settings.AddOrUpdateValue(KdfIterationsKey, value);
_kdfIterations = value;
}
}
public bool IsAuthenticated
{
get
{
return _cryptoService.Key != null &&
!string.IsNullOrWhiteSpace(_tokenService.Token) &&
!string.IsNullOrWhiteSpace(UserId);
}
}
public string PIN
{
get
{
if(_pin != null)
{
return _pin;
}
var pinBytes = _secureStorage.Retrieve(PinKey);
if(pinBytes == null)
{
return null;
}
_pin = Encoding.UTF8.GetString(pinBytes, 0, pinBytes.Length);
return _pin;
}
set
{
if(value != null)
{
var pinBytes = Encoding.UTF8.GetBytes(value);
_secureStorage.Store(PinKey, pinBytes);
}
else
{
_secureStorage.Delete(PinKey);
}
_pin = value;
}
}
public bool BelongsToOrganization(string orgId)
{
return !string.IsNullOrWhiteSpace(orgId) && (_cryptoService.OrgKeys?.ContainsKey(orgId) ?? false);
}
public void LogOut(string logoutMessage = null)
{
CipherService.CachedCiphers = null;
_tokenService.Token = null;
UserId = null;
Email = null;
_cryptoService.ClearKeys();
_settings.Remove(Constants.SecurityStamp);
_settings.Remove(Constants.PushLastRegistrationDate);
_settings.Remove(Constants.Locked);
Task.Run(async () => await _deviceApiRepository.PutClearTokenAsync(_appIdService.AppId));
_googleAnalyticsService.TrackAppEvent("LoggedOut");
MessagingCenter.Send(Application.Current, "LoggedOut");
Device.BeginInvokeOnMainThread(() => Application.Current.MainPage = new ExtendedNavigationPage(new HomePage()));
if(!string.IsNullOrWhiteSpace(logoutMessage))
{
Resolver.Resolve<IDeviceActionService>()?.Toast(logoutMessage);
}
}
public async Task<FullLoginResult> TokenPostAsync(string email, string masterPassword)
{
Kdf = KdfType.PBKDF2_SHA256;
KdfIterations = 5000;
var preloginResponse = await _accountsApiRepository.PostPreloginAsync(
new PreloginRequest { Email = email });
if(preloginResponse.Succeeded)
{
Kdf = preloginResponse.Result.Kdf;
KdfIterations = preloginResponse.Result.KdfIterations;
}
var result = new FullLoginResult();
var normalizedEmail = email.Trim().ToLower();
var key = _cryptoService.MakeKeyFromPassword(masterPassword, normalizedEmail, Kdf, KdfIterations);
var request = new TokenRequest
{
Email = normalizedEmail,
MasterPasswordHash = _cryptoService.HashPasswordBase64(key, masterPassword),
Device = new DeviceRequest(_appIdService, _deviceInfoService)
};
var twoFactorToken = _tokenService.GetTwoFactorToken(normalizedEmail);
if(!string.IsNullOrWhiteSpace(twoFactorToken))
{
request.Token = twoFactorToken;
request.Provider = TwoFactorProviderType.Remember;
request.Remember = false;
}
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.TwoFactorProviders2 != null && response.Result.TwoFactorProviders2.Count > 0)
{
result.Key = key;
result.MasterPasswordHash = request.MasterPasswordHash;
result.TwoFactorProviders = response.Result.TwoFactorProviders2;
return result;
}
await ProcessLoginSuccessAsync(key, response.Result);
return result;
}
public async Task<LoginResult> TokenPostTwoFactorAsync(TwoFactorProviderType type, string token, bool remember,
string email, string masterPasswordHash, SymmetricCryptoKey key)
{
var result = new LoginResult();
var request = new TokenRequest
{
Remember = remember,
Email = email.Trim().ToLower(),
MasterPasswordHash = masterPasswordHash,
Token = token,
Provider = type,
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.Key != null)
{
_cryptoService.SetEncKey(new CipherString(response.Key));
}
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);
_appSettingsService.FailedPinAttempts = 0;
_appSettingsService.OrganizationGivesPremium = false;
if(response.PrivateKey != null)
{
var profile = await _accountsApiRepository.GetProfileAsync();
if(profile.Succeeded)
{
_cryptoService.SetOrgKeys(profile.Result);
_appSettingsService.OrganizationGivesPremium =
profile.Result?.Organizations?.Any(o => o.UsersGetPremium && o.Enabled) ?? false;
}
}
if(!string.IsNullOrWhiteSpace(response.TwoFactorToken))
{
_tokenService.SetTwoFactorToken(_tokenService.TokenEmail, response.TwoFactorToken);
}
}
}
}

View File

@@ -1,575 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Models;
using Bit.App.Models.Api;
using Bit.App.Models.Data;
using System.Net.Http;
using Bit.App.Utilities;
using System.Text.RegularExpressions;
using Xamarin.Forms;
namespace Bit.App.Services
{
public class CipherService : ICipherService
{
public static List<Cipher> CachedCiphers = null;
private readonly string[] _ignoredSearchTerms = new string[] { "com", "net", "org", "android",
"io", "co", "uk", "au", "nz", "fr", "de", "tv", "info", "app", "apps", "eu", "me", "dev", "jp", "mobile" };
private readonly ICipherRepository _cipherRepository;
private readonly ICipherCollectionRepository _cipherCollectionRepository;
private readonly IAttachmentRepository _attachmentRepository;
private readonly IAuthService _authService;
private readonly ICipherApiRepository _cipherApiRepository;
private readonly ISettingsService _settingsService;
private readonly ICryptoService _cryptoService;
private readonly IAppSettingsService _appSettingsService;
private readonly IDeviceInfoService _deviceInfoService;
public CipherService(
ICipherRepository cipherRepository,
ICipherCollectionRepository cipherCollectionRepository,
IAttachmentRepository attachmentRepository,
IAuthService authService,
ICipherApiRepository cipherApiRepository,
ISettingsService settingsService,
ICryptoService cryptoService,
IAppSettingsService appSettingsService,
IDeviceInfoService deviceInfoService)
{
_cipherRepository = cipherRepository;
_cipherCollectionRepository = cipherCollectionRepository;
_attachmentRepository = attachmentRepository;
_authService = authService;
_cipherApiRepository = cipherApiRepository;
_settingsService = settingsService;
_cryptoService = cryptoService;
_appSettingsService = appSettingsService;
_deviceInfoService = deviceInfoService;
}
public async Task<Cipher> GetByIdAsync(string id)
{
var data = await _cipherRepository.GetByIdAsync(id);
if(data == null || data.UserId != _authService.UserId)
{
return null;
}
var attachments = await _attachmentRepository.GetAllByCipherIdAsync(id);
var cipher = new Cipher(data, attachments);
return cipher;
}
public async Task<IEnumerable<Cipher>> GetAllAsync()
{
if(!_deviceInfoService.IsExtension && _appSettingsService.ClearCiphersCache)
{
CachedCiphers = null;
_appSettingsService.ClearCiphersCache = false;
}
if(_deviceInfoService.IsExtension && _appSettingsService.ClearExtensionCiphersCache)
{
CachedCiphers = null;
_appSettingsService.ClearExtensionCiphersCache = false;
}
if(CachedCiphers != null)
{
return CachedCiphers;
}
var attachmentData = await _attachmentRepository.GetAllByUserIdAsync(_authService.UserId);
var attachmentDict = attachmentData.GroupBy(a => a.LoginId).ToDictionary(g => g.Key, g => g.ToList());
var data = await _cipherRepository.GetAllByUserIdAsync(_authService.UserId);
CachedCiphers = data
.Select(f => new Cipher(f, attachmentDict.ContainsKey(f.Id) ? attachmentDict[f.Id] : null))
.ToList();
return CachedCiphers;
}
public async Task<IEnumerable<Cipher>> GetAllAsync(bool favorites)
{
var ciphers = await GetAllAsync();
return ciphers.Where(c => c.Favorite == favorites);
}
public async Task<IEnumerable<Cipher>> GetAllByFolderAsync(string folderId)
{
var ciphers = await GetAllAsync();
return ciphers.Where(c => c.FolderId == folderId);
}
public async Task<IEnumerable<Cipher>> GetAllByCollectionAsync(string collectionId)
{
var assoc = await _cipherCollectionRepository.GetAllByUserIdCollectionAsync(_authService.UserId, collectionId);
var cipherIds = new HashSet<string>(assoc.Select(c => c.CipherId));
var ciphers = await GetAllAsync();
return ciphers.Where(c => cipherIds.Contains(c.Id));
}
public async Task<Tuple<IEnumerable<Cipher>, IEnumerable<Cipher>, IEnumerable<Cipher>>> GetAllAsync(
string uriString)
{
if(string.IsNullOrWhiteSpace(uriString))
{
return null;
}
string domainName = null;
var mobileApp = UriIsMobileApp(uriString);
if(!mobileApp &&
(!Uri.TryCreate(uriString, UriKind.Absolute, out Uri uri) ||
!DomainName.TryParseBaseDomain(uri.Host, out domainName)))
{
return null;
}
var mobileAppInfo = InfoFromMobileAppUri(uriString);
var mobileAppWebUriString = mobileAppInfo?.Item1;
var mobileAppSearchTerms = mobileAppInfo?.Item2;
var eqDomains = (await _settingsService.GetEquivalentDomainsAsync()).Select(d => d.ToArray());
var matchingDomains = new List<string>();
var matchingFuzzyDomains = new List<string>();
foreach(var eqDomain in eqDomains)
{
if(mobileApp)
{
if(Array.IndexOf(eqDomain, uriString) >= 0)
{
matchingDomains.AddRange(eqDomain.Select(d => d).ToList());
}
else if(mobileAppWebUriString != null && Array.IndexOf(eqDomain, mobileAppWebUriString) >= 0)
{
matchingFuzzyDomains.AddRange(eqDomain.Select(d => d).ToList());
}
}
else if(Array.IndexOf(eqDomain, domainName) >= 0)
{
matchingDomains.AddRange(eqDomain.Select(d => d).ToList());
}
}
if(!matchingDomains.Any())
{
matchingDomains.Add(mobileApp ? uriString : domainName);
}
if(mobileApp && mobileAppWebUriString != null &&
!matchingFuzzyDomains.Any() && !matchingDomains.Contains(mobileAppWebUriString))
{
matchingFuzzyDomains.Add(mobileAppWebUriString);
}
var matchingDomainsArray = matchingDomains.ToArray();
var matchingFuzzyDomainsArray = matchingFuzzyDomains.ToArray();
var matchingLogins = new List<Cipher>();
var matchingFuzzyLogins = new List<Cipher>();
var others = new List<Cipher>();
var ciphers = await GetAllAsync();
foreach(var cipher in ciphers)
{
if(cipher.Type != Enums.CipherType.Login)
{
others.Add(cipher);
continue;
}
if(cipher.Login?.Uris == null || !cipher.Login.Uris.Any())
{
continue;
}
foreach(var u in cipher.Login.Uris)
{
var loginUriString = u.Uri?.Decrypt(cipher.OrganizationId);
if(string.IsNullOrWhiteSpace(loginUriString))
{
break;
}
var match = false;
switch(u.Match)
{
case null:
case Enums.UriMatchType.Domain:
match = CheckDefaultUriMatch(cipher, loginUriString, matchingLogins, matchingFuzzyLogins,
matchingDomainsArray, matchingFuzzyDomainsArray, mobileApp, mobileAppSearchTerms);
break;
case Enums.UriMatchType.Host:
var urlHost = Helpers.GetUrlHost(uriString);
match = urlHost != null && urlHost == Helpers.GetUrlHost(loginUriString);
if(match)
{
AddMatchingLogin(cipher, matchingLogins, matchingFuzzyLogins);
}
break;
case Enums.UriMatchType.Exact:
match = uriString == loginUriString;
if(match)
{
AddMatchingLogin(cipher, matchingLogins, matchingFuzzyLogins);
}
break;
case Enums.UriMatchType.StartsWith:
match = uriString.StartsWith(loginUriString);
if(match)
{
AddMatchingLogin(cipher, matchingLogins, matchingFuzzyLogins);
}
break;
case Enums.UriMatchType.RegularExpression:
var regex = new Regex(loginUriString, RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1));
match = regex.IsMatch(uriString);
if(match)
{
AddMatchingLogin(cipher, matchingLogins, matchingFuzzyLogins);
}
break;
case Enums.UriMatchType.Never:
default:
break;
}
if(match)
{
break;
}
}
}
return new Tuple<IEnumerable<Cipher>, IEnumerable<Cipher>, IEnumerable<Cipher>>(
matchingLogins, matchingFuzzyLogins, others);
}
public async Task<ApiResult<CipherResponse>> SaveAsync(Cipher cipher)
{
ApiResult<CipherResponse> response = null;
var request = new CipherRequest(cipher);
if(cipher.Id == null)
{
response = await _cipherApiRepository.PostAsync(request);
}
else
{
response = await _cipherApiRepository.PutAsync(cipher.Id, request);
}
if(response.Succeeded)
{
var data = new CipherData(response.Result, _authService.UserId);
await UpsertDataAsync(data, true, cipher.Id == null);
cipher.Id = data.Id;
}
else if(response.StatusCode == System.Net.HttpStatusCode.Forbidden
|| response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
_authService.LogOut();
}
return response;
}
public async Task UpsertDataAsync(CipherData cipher, bool sendMessage, bool created)
{
await _cipherRepository.UpsertAsync(cipher);
CachedCiphers = null;
_appSettingsService.ClearCiphersCache = true;
_appSettingsService.ClearExtensionCiphersCache = true;
if(sendMessage && Application.Current != null)
{
MessagingCenter.Send(Application.Current, "UpsertedCipher",
new Tuple<string, bool>(cipher.Id, created));
}
}
public async Task<ApiResult> DeleteAsync(string id)
{
var response = await _cipherApiRepository.DeleteAsync(id);
if(response.Succeeded)
{
await DeleteDataAsync(id, true);
}
else if(response.StatusCode == System.Net.HttpStatusCode.Forbidden
|| response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
_authService.LogOut();
}
return response;
}
public async Task DeleteDataAsync(string id, bool sendMessage)
{
if(sendMessage)
{
var cipherData = await _cipherRepository.GetByIdAsync(id);
if(cipherData != null && Application.Current != null)
{
MessagingCenter.Send(Application.Current, "DeletedCipher", new Cipher(cipherData));
}
}
await _cipherRepository.DeleteAsync(id);
CachedCiphers = null;
_appSettingsService.ClearCiphersCache = true;
_appSettingsService.ClearExtensionCiphersCache = true;
}
public async Task<byte[]> DownloadAndDecryptAttachmentAsync(string url, CipherString key, string orgId = null)
{
using(var client = new HttpClient())
{
try
{
var response = await client.GetAsync(new Uri(url)).ConfigureAwait(false);
if(!response.IsSuccessStatusCode)
{
return null;
}
var data = await response.Content.ReadAsByteArrayAsync();
if(data == null)
{
return null;
}
SymmetricCryptoKey regularKey = !string.IsNullOrWhiteSpace(orgId) ?
_cryptoService.GetOrgKey(orgId) : null;
SymmetricCryptoKey dataKey = null;
if(key != null)
{
var decDataKey = _cryptoService.DecryptToBytes(key, regularKey);
dataKey = new SymmetricCryptoKey(decDataKey);
}
else
{
dataKey = regularKey;
}
return _cryptoService.DecryptToBytes(data, dataKey);
}
catch
{
return null;
}
}
}
public async Task<ApiResult<CipherResponse>> EncryptAndSaveAttachmentAsync(Cipher cipher, byte[] data, string fileName)
{
var key = cipher.OrganizationId != null ? _cryptoService.GetOrgKey(cipher.OrganizationId) : null;
var encFileName = fileName.Encrypt(cipher.OrganizationId);
var dataKey = _cryptoService.MakeEncKey(key);
var encBytes = _cryptoService.EncryptToBytes(data, dataKey.Item1);
var response = await _cipherApiRepository.PostAttachmentAsync(cipher.Id, encBytes,
dataKey.Item2.EncryptedString, encFileName.EncryptedString);
if(response.Succeeded)
{
var attachmentData = response.Result.Attachments.Select(a => new AttachmentData(a, cipher.Id));
await UpsertAttachmentDataAsync(attachmentData);
cipher.Attachments = response.Result.Attachments.Select(a => new Attachment(a));
}
else if(response.StatusCode == System.Net.HttpStatusCode.Forbidden
|| response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
_authService.LogOut();
}
return response;
}
public async Task UpsertAttachmentDataAsync(IEnumerable<AttachmentData> attachments)
{
foreach(var attachment in attachments)
{
await _attachmentRepository.UpsertAsync(attachment);
}
CachedCiphers = null;
_appSettingsService.ClearCiphersCache = true;
_appSettingsService.ClearExtensionCiphersCache = true;
}
public async Task<ApiResult> DeleteAttachmentAsync(Cipher cipher, string attachmentId)
{
var response = await _cipherApiRepository.DeleteAttachmentAsync(cipher.Id, attachmentId);
if(response.Succeeded)
{
await DeleteAttachmentDataAsync(attachmentId);
}
else if(response.StatusCode == System.Net.HttpStatusCode.Forbidden
|| response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
_authService.LogOut();
}
return response;
}
public async Task DeleteAttachmentDataAsync(string attachmentId)
{
await _attachmentRepository.DeleteAsync(attachmentId);
CachedCiphers = null;
_appSettingsService.ClearCiphersCache = true;
_appSettingsService.ClearExtensionCiphersCache = true;
}
private Tuple<string, string[]> InfoFromMobileAppUri(string mobileAppUriString)
{
if(UriIsAndroidApp(mobileAppUriString))
{
return InfoFromAndroidAppUri(mobileAppUriString);
}
else if(UriIsiOSApp(mobileAppUriString))
{
return InfoFromiOSAppUri(mobileAppUriString);
}
return null;
}
private Tuple<string, string[]> InfoFromAndroidAppUri(string androidAppUriString)
{
if(!UriIsAndroidApp(androidAppUriString))
{
return null;
}
var androidUriParts = androidAppUriString.Replace(Constants.AndroidAppProtocol, string.Empty).Split('.');
if(androidUriParts.Length >= 2)
{
var webUri = string.Join(".", androidUriParts[1], androidUriParts[0]);
var searchTerms = androidUriParts.Where(p => !_ignoredSearchTerms.Contains(p))
.Select(p => p.ToLowerInvariant()).ToArray();
return new Tuple<string, string[]>(webUri, searchTerms);
}
return null;
}
private Tuple<string, string[]> InfoFromiOSAppUri(string iosAppUriString)
{
if(!UriIsiOSApp(iosAppUriString))
{
return null;
}
var webUri = iosAppUriString.Replace(Constants.iOSAppProtocol, string.Empty);
return new Tuple<string, string[]>(webUri, null);
}
private bool UriIsMobileApp(string uriString)
{
return UriIsAndroidApp(uriString) || UriIsiOSApp(uriString);
}
private bool UriIsAndroidApp(string uriString)
{
return uriString.StartsWith(Constants.AndroidAppProtocol);
}
private bool UriIsiOSApp(string uriString)
{
return uriString.StartsWith(Constants.iOSAppProtocol);
}
private bool CheckDefaultUriMatch(Cipher cipher, string loginUriString, List<Cipher> matchingLogins,
List<Cipher> matchingFuzzyLogins, string[] matchingDomainsArray, string[] matchingFuzzyDomainsArray,
bool mobileApp, string[] mobileAppSearchTerms)
{
if(Array.IndexOf(matchingDomainsArray, loginUriString) >= 0)
{
AddMatchingLogin(cipher, matchingLogins, matchingFuzzyLogins);
return true;
}
else if(mobileApp && Array.IndexOf(matchingFuzzyDomainsArray, loginUriString) >= 0)
{
AddMatchingFuzzyLogin(cipher, matchingLogins, matchingFuzzyLogins);
return false;
}
else if(!mobileApp)
{
var info = InfoFromMobileAppUri(loginUriString);
if(info?.Item1 != null && Array.IndexOf(matchingDomainsArray, info.Item1) >= 0)
{
AddMatchingFuzzyLogin(cipher, matchingLogins, matchingFuzzyLogins);
return false;
}
}
if(!loginUriString.Contains("://") && loginUriString.Contains("."))
{
loginUriString = "http://" + loginUriString;
}
string loginDomainName = null;
if(Uri.TryCreate(loginUriString, UriKind.Absolute, out Uri loginUri)
&& DomainName.TryParseBaseDomain(loginUri.Host, out loginDomainName))
{
loginDomainName = loginDomainName.ToLowerInvariant();
if(Array.IndexOf(matchingDomainsArray, loginDomainName) >= 0)
{
AddMatchingLogin(cipher, matchingLogins, matchingFuzzyLogins);
return true;
}
else if(mobileApp && Array.IndexOf(matchingFuzzyDomainsArray, loginDomainName) >= 0)
{
AddMatchingFuzzyLogin(cipher, matchingLogins, matchingFuzzyLogins);
return false;
}
}
if(mobileApp && mobileAppSearchTerms != null && mobileAppSearchTerms.Length > 0)
{
var addedFromSearchTerm = false;
var loginNameString = cipher.Name == null ? null :
cipher.Name.Decrypt(cipher.OrganizationId)?.ToLowerInvariant();
foreach(var term in mobileAppSearchTerms)
{
addedFromSearchTerm = (loginDomainName != null && loginDomainName.Contains(term)) ||
(loginNameString != null && loginNameString.Contains(term));
if(!addedFromSearchTerm)
{
var domainTerm = loginDomainName?.Split('.')[0];
addedFromSearchTerm =
(domainTerm != null && domainTerm.Length > 2 && term.Contains(domainTerm)) ||
(loginNameString != null && term.Contains(loginNameString));
}
if(addedFromSearchTerm)
{
AddMatchingFuzzyLogin(cipher, matchingLogins, matchingFuzzyLogins);
return false;
}
}
}
return false;
}
private void AddMatchingLogin(Cipher cipher, List<Cipher> matchingLogins, List<Cipher> matchingFuzzyLogins)
{
if(matchingFuzzyLogins.Contains(cipher))
{
matchingFuzzyLogins.Remove(cipher);
}
matchingLogins.Add(cipher);
}
private void AddMatchingFuzzyLogin(Cipher cipher, List<Cipher> matchingLogins, List<Cipher> matchingFuzzyLogins)
{
if(!matchingFuzzyLogins.Contains(cipher) && !matchingLogins.Contains(cipher))
{
matchingFuzzyLogins.Add(cipher);
}
}
}
}

View File

@@ -1,52 +0,0 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Models;
namespace Bit.App.Services
{
public class CollectionService : ICollectionService
{
private readonly ICollectionRepository _collectionRepository;
private readonly ICipherCollectionRepository _cipherCollectionRepository;
private readonly IAuthService _authService;
public CollectionService(
ICollectionRepository collectionRepository,
ICipherCollectionRepository cipherCollectionRepository,
IAuthService authService)
{
_collectionRepository = collectionRepository;
_cipherCollectionRepository = cipherCollectionRepository;
_authService = authService;
}
public async Task<Collection> GetByIdAsync(string id)
{
var data = await _collectionRepository.GetByIdAsync(id);
if(data == null || data.UserId != _authService.UserId)
{
return null;
}
var collection = new Collection(data);
return collection;
}
public async Task<IEnumerable<Collection>> GetAllAsync()
{
var data = await _collectionRepository.GetAllByUserIdAsync(_authService.UserId);
var collections = data.Select(c => new Collection(c));
return collections;
}
public async Task<IEnumerable<Tuple<string, string>>> GetAllCipherAssociationsAsync()
{
var data = await _cipherCollectionRepository.GetAllByUserIdAsync(_authService.UserId);
var assocs = data.Select(cc => new Tuple<string, string>(cc.CipherId, cc.CollectionId));
return assocs;
}
}
}

View File

@@ -1,518 +0,0 @@
using System;
using System.Diagnostics;
using System.Text;
using Bit.App.Abstractions;
using Bit.App.Models;
using PCLCrypto;
using System.Linq;
using Bit.App.Enums;
using System.Collections.Generic;
using Newtonsoft.Json;
using Plugin.Settings.Abstractions;
using Bit.App.Models.Api;
using Bit.App.Utilities;
namespace Bit.App.Services
{
public class CryptoService : ICryptoService
{
private const string KeyKey = "key";
private const string PrivateKeyKey = "encPrivateKey";
private const string EncKeyKey = "encKey";
private const string OrgKeysKey = "encOrgKeys";
private const int InitializationVectorSize = 16;
private readonly ISettings _settings;
private readonly ISecureStorageService _secureStorage;
private readonly IKeyDerivationService _keyDerivationService;
private SymmetricCryptoKey _key;
private SymmetricCryptoKey _encKey;
private SymmetricCryptoKey _legacyEtmKey;
private IDictionary<string, SymmetricCryptoKey> _orgKeys;
private byte[] _privateKey;
public CryptoService(
ISettings settings,
ISecureStorageService secureStorage,
IKeyDerivationService keyDerivationService)
{
_settings = settings;
_secureStorage = secureStorage;
_keyDerivationService = keyDerivationService;
}
public SymmetricCryptoKey Key
{
get
{
if(_key == null && _secureStorage.Contains(KeyKey))
{
var keyBytes = _secureStorage.Retrieve(KeyKey);
if(keyBytes != null)
{
_key = new SymmetricCryptoKey(keyBytes);
}
}
return _key;
}
set
{
if(value != null)
{
_secureStorage.Store(KeyKey, value.Key);
}
else
{
_secureStorage.Delete(KeyKey);
}
_key = value;
_legacyEtmKey = null;
}
}
public SymmetricCryptoKey EncKey
{
get
{
if(_encKey == null && _settings.Contains(EncKeyKey))
{
var encKey = _settings.GetValueOrDefault(EncKeyKey, null);
var encKeyCs = new CipherString(encKey);
try
{
byte[] decEncKey = null;
if(encKeyCs.EncryptionType == EncryptionType.AesCbc256_B64)
{
decEncKey = DecryptToBytes(encKeyCs, Key);
}
else if(encKeyCs.EncryptionType == EncryptionType.AesCbc256_HmacSha256_B64)
{
var newKey = StretchKey(Key);
decEncKey = DecryptToBytes(encKeyCs, newKey);
}
else
{
throw new Exception("Unsupported EncKey type");
}
if(decEncKey != null)
{
_encKey = new SymmetricCryptoKey(decEncKey);
}
}
catch
{
_encKey = null;
Debug.WriteLine($"Cannot set enc key. Decryption failed.");
}
}
return _encKey;
}
}
public byte[] PrivateKey
{
get
{
if(_privateKey == null && _settings.Contains(PrivateKeyKey))
{
var encPrivateKey = _settings.GetValueOrDefault(PrivateKeyKey, null);
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(OrgKeysKey, null);
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 SetEncKey(CipherString encKeyEnc)
{
if(encKeyEnc != null)
{
_settings.AddOrUpdateValue(EncKeyKey, encKeyEnc.EncryptedString);
}
else if(_settings.Contains(EncKeyKey))
{
_settings.Remove(EncKeyKey);
}
_encKey = null;
}
public void SetPrivateKey(CipherString privateKeyEnc)
{
if(privateKeyEnc != null)
{
_settings.AddOrUpdateValue(PrivateKeyKey, privateKeyEnc.EncryptedString);
}
else if(_settings.Contains(PrivateKeyKey))
{
_settings.Remove(PrivateKeyKey);
}
_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;
}
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);
SetEncKey(null);
}
public CipherString Encrypt(string plaintextValue, SymmetricCryptoKey key = null)
{
if(plaintextValue == null)
{
throw new ArgumentNullException(nameof(plaintextValue));
}
var plaintextBytes = Encoding.UTF8.GetBytes(plaintextValue);
return Encrypt(plaintextBytes, key);
}
public CipherString Encrypt(byte[] plainBytes, SymmetricCryptoKey key = null)
{
if(key == null)
{
key = EncKey ?? Key;
}
if(key == null)
{
throw new ArgumentNullException(nameof(key));
}
if(plainBytes == null)
{
throw new ArgumentNullException(nameof(plainBytes));
}
return Crypto.AesCbcEncrypt(plainBytes, key);
}
public byte[] EncryptToBytes(byte[] plainBytes, SymmetricCryptoKey key = null)
{
if(key == null)
{
key = EncKey ?? Key;
}
if(key == null)
{
throw new ArgumentNullException(nameof(key));
}
if(plainBytes == null)
{
throw new ArgumentNullException(nameof(plainBytes));
}
return Crypto.AesCbcEncryptToBytes(plainBytes, key);
}
public string Decrypt(CipherString encyptedValue, SymmetricCryptoKey key = null)
{
try
{
var bytes = DecryptToBytes(encyptedValue, key);
return Encoding.UTF8.GetString(bytes, 0, bytes.Length).TrimEnd('\0');
}
catch(Exception e)
{
Debug.WriteLine("Could not decrypt '{0}'. {1}", encyptedValue, e.Message);
return "[error: cannot decrypt]";
}
}
public byte[] DecryptToBytes(CipherString encyptedValue, SymmetricCryptoKey key = null)
{
if(key == null)
{
key = EncKey ?? Key;
}
if(key == null)
{
throw new ArgumentNullException(nameof(key));
}
if(encyptedValue == null)
{
throw new ArgumentNullException(nameof(encyptedValue));
}
if(encyptedValue.EncryptionType == EncryptionType.AesCbc128_HmacSha256_B64 &&
key.EncryptionType == EncryptionType.AesCbc256_B64)
{
// Old encrypt-then-mac scheme, swap out the key
if(_legacyEtmKey == null)
{
_legacyEtmKey = new SymmetricCryptoKey(key.Key, EncryptionType.AesCbc128_HmacSha256_B64);
}
key = _legacyEtmKey;
}
return Crypto.AesCbcDecrypt(encyptedValue, key);
}
public byte[] DecryptToBytes(byte[] encyptedValue, SymmetricCryptoKey key = null)
{
if(key == null)
{
key = EncKey ?? Key;
}
if(key == null)
{
throw new ArgumentNullException(nameof(key));
}
if(encyptedValue == null || encyptedValue.Length == 0)
{
throw new ArgumentNullException(nameof(encyptedValue));
}
byte[] ct, iv, mac = null;
var encType = (EncryptionType)encyptedValue[0];
switch(encType)
{
case EncryptionType.AesCbc128_HmacSha256_B64:
case EncryptionType.AesCbc256_HmacSha256_B64:
if(encyptedValue.Length <= 49)
{
throw new InvalidOperationException("Invalid value length.");
}
iv = new ArraySegment<byte>(encyptedValue, 1, 16).ToArray();
mac = new ArraySegment<byte>(encyptedValue, 17, 32).ToArray();
ct = new ArraySegment<byte>(encyptedValue, 49, encyptedValue.Length - 49).ToArray();
break;
case EncryptionType.AesCbc256_B64:
if(encyptedValue.Length <= 17)
{
throw new InvalidOperationException("Invalid value length.");
}
iv = new ArraySegment<byte>(encyptedValue, 1, 16).ToArray();
ct = new ArraySegment<byte>(encyptedValue, 17, encyptedValue.Length - 17).ToArray();
break;
default:
throw new InvalidOperationException("Invalid encryption type.");
}
return Crypto.AesCbcDecrypt(encType, ct, iv, mac, key);
}
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:
case EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64:
provider = WinRTCrypto.AsymmetricKeyAlgorithmProvider.OpenAlgorithm(AsymmetricAlgorithm.RsaOaepSha256);
break;
case EncryptionType.Rsa2048_OaepSha1_B64:
case EncryptionType.Rsa2048_OaepSha1_HmacSha256_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;
}
public SymmetricCryptoKey MakeKeyFromPassword(string password, string salt, KdfType kdf, int kdfIterations)
{
if(password == null)
{
throw new ArgumentNullException(nameof(password));
}
if(salt == null)
{
throw new ArgumentNullException(nameof(salt));
}
var passwordBytes = Encoding.UTF8.GetBytes(NormalizePassword(password));
var saltBytes = Encoding.UTF8.GetBytes(salt);
byte[] keyBytes = null;
if(kdf == KdfType.PBKDF2_SHA256)
{
if(kdfIterations < 5000)
{
throw new Exception("PBKDF2 iteration minimum is 5000.");
}
keyBytes = _keyDerivationService.DeriveKey(passwordBytes, saltBytes, (uint)kdfIterations);
}
else
{
throw new Exception("Unknown Kdf.");
}
return new SymmetricCryptoKey(keyBytes);
}
public byte[] HashPassword(SymmetricCryptoKey key, string password)
{
if(key == null)
{
throw new ArgumentNullException(nameof(key));
}
if(password == null)
{
throw new ArgumentNullException(nameof(password));
}
var passwordBytes = Encoding.UTF8.GetBytes(NormalizePassword(password));
var hash = _keyDerivationService.DeriveKey(key.Key, passwordBytes, 1);
return hash;
}
public string HashPasswordBase64(SymmetricCryptoKey key, string password)
{
var hash = HashPassword(key, password);
return Convert.ToBase64String(hash);
}
public Tuple<SymmetricCryptoKey, CipherString> MakeEncKey(SymmetricCryptoKey key)
{
var theKey = key ?? EncKey ?? Key;
var encKey = Crypto.RandomBytes(64);
if(theKey.Key.Length == 32)
{
var newKey = StretchKey(theKey);
return new Tuple<SymmetricCryptoKey, CipherString>(
new SymmetricCryptoKey(encKey), Encrypt(encKey, newKey));
}
else if(theKey.Key.Length == 64)
{
return new Tuple<SymmetricCryptoKey, CipherString>(
new SymmetricCryptoKey(encKey), Encrypt(encKey, theKey));
}
throw new Exception("Invalid key size.");
}
// Some users like to copy/paste passwords from external files. Sometimes this can lead to two different
// values on mobiles apps vs the web. For example, on Android an EditText will accept a new line character
// (\n), whereas whenever you paste a new line character on the web in a HTML input box it is converted
// to a space ( ). Normalize those values so that they are the same on all platforms.
private string NormalizePassword(string password)
{
return password
.Replace("\r\n", " ") // Windows-style new line => space
.Replace("\n", " ") // New line => space
.Replace(" ", " "); // No-break space (00A0) => space
}
private SymmetricCryptoKey StretchKey(SymmetricCryptoKey key)
{
var newKey = new byte[64];
var encKey = Crypto.HkdfExpand(key.Key, Encoding.UTF8.GetBytes("enc"), 32);
var macKey = Crypto.HkdfExpand(key.Key, Encoding.UTF8.GetBytes("mac"), 32);
encKey.CopyTo(newKey, 0);
macKey.CopyTo(newKey, 32);
return new SymmetricCryptoKey(newKey);
}
}
}

View File

@@ -1,27 +0,0 @@
using System;
using Bit.App.Abstractions;
using Bit.App.Models.Data;
using SQLite;
namespace Bit.App.Services
{
public class DatabaseService : IDatabaseService
{
protected readonly SQLiteConnection _connection;
public DatabaseService(ISqlService sqlService)
{
_connection = sqlService.GetConnection();
}
public void CreateTables()
{
_connection.CreateTable<FolderData>();
_connection.CreateTable<CollectionData>();
_connection.CreateTable<CipherData>();
_connection.CreateTable<CipherCollectionData>();
_connection.CreateTable<AttachmentData>();
_connection.CreateTable<SettingsData>();
}
}
}

View File

@@ -1,100 +0,0 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Models;
using Bit.App.Models.Data;
using Bit.App.Models.Api;
using Xamarin.Forms;
namespace Bit.App.Services
{
public class FolderService : IFolderService
{
private readonly IFolderRepository _folderRepository;
private readonly IAuthService _authService;
private readonly IFolderApiRepository _folderApiRepository;
public FolderService(
IFolderRepository folderRepository,
IAuthService authService,
IFolderApiRepository folderApiRepository)
{
_folderRepository = folderRepository;
_authService = authService;
_folderApiRepository = folderApiRepository;
}
public async Task<Folder> GetByIdAsync(string id)
{
var data = await _folderRepository.GetByIdAsync(id);
if(data == null || data.UserId != _authService.UserId)
{
return null;
}
var folder = new Folder(data);
return folder;
}
public async Task<IEnumerable<Folder>> GetAllAsync()
{
var data = await _folderRepository.GetAllByUserIdAsync(_authService.UserId);
var folders = data.Select(f => new Folder(f));
return folders;
}
public async Task<ApiResult<FolderResponse>> SaveAsync(Folder folder)
{
ApiResult<FolderResponse> response = null;
var request = new FolderRequest(folder);
if(folder.Id == null)
{
response = await _folderApiRepository.PostAsync(request);
}
else
{
response = await _folderApiRepository.PutAsync(folder.Id, request);
}
if(response.Succeeded)
{
var data = new FolderData(response.Result, _authService.UserId);
if(folder.Id == null)
{
await _folderRepository.InsertAsync(data);
folder.Id = data.Id;
}
else
{
await _folderRepository.UpdateAsync(data);
}
}
else if(response.StatusCode == System.Net.HttpStatusCode.Forbidden
|| response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
_authService.LogOut();
}
return response;
}
public async Task<ApiResult> DeleteAsync(string folderId)
{
var response = await _folderApiRepository.DeleteAsync(folderId);
if(response.Succeeded)
{
await _folderRepository.DeleteAsync(folderId);
}
else if(response.StatusCode == System.Net.HttpStatusCode.Forbidden
|| response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
_authService.LogOut();
}
return response;
}
}
}

View File

@@ -1,142 +0,0 @@
using System;
using Bit.App.Abstractions;
using Plugin.Settings.Abstractions;
using Plugin.Fingerprint.Abstractions;
using Bit.App.Enums;
using System.Threading.Tasks;
using Bit.App.Controls;
using Bit.App.Pages;
using Xamarin.Forms;
using System.Linq;
using System.Diagnostics;
namespace Bit.App.Services
{
public class LockService : ILockService
{
private readonly ISettings _settings;
private readonly IAppSettingsService _appSettings;
private readonly IAuthService _authService;
private readonly IFingerprint _fingerprint;
private Stopwatch _stopwatch = null;
public LockService(
ISettings settings,
IAppSettingsService appSettings,
IAuthService authService,
IFingerprint fingerprint)
{
_settings = settings;
_appSettings = appSettings;
_authService = authService;
_fingerprint = fingerprint;
}
public void UpdateLastActivity()
{
_stopwatch?.Restart();
}
public async Task<LockType> GetLockTypeAsync(bool forceLock, bool onlyIfAlreadyLocked = false)
{
// Only lock if they are logged in
if(!_authService.IsAuthenticated)
{
return LockType.None;
}
// Are we forcing a lock? (i.e. clicking a button to lock the app manually, immediately)
if(!forceLock && !_appSettings.Locked)
{
// Lock seconds tells if they want to lock the app or not
var lockSeconds = _settings.GetValueOrDefault(Constants.SettingLockSeconds, 60 * 15);
var neverLock = lockSeconds == -1;
// Has it been longer than lockSeconds since the last time the app was used?
if(neverLock || (_stopwatch != null && _stopwatch.Elapsed.TotalSeconds < lockSeconds))
{
return LockType.None;
}
}
if(onlyIfAlreadyLocked && !_appSettings.Locked)
{
return LockType.None;
}
// What method are we using to unlock?
var fingerprintUnlock = _settings.GetValueOrDefault(Constants.SettingFingerprintUnlockOn, false);
var pinUnlock = _settings.GetValueOrDefault(Constants.SettingPinUnlockOn, false);
var fingerprintAvailability = await _fingerprint.GetAvailabilityAsync();
if(fingerprintUnlock && fingerprintAvailability == FingerprintAvailability.Available)
{
return LockType.Fingerprint;
}
else if(pinUnlock && !string.IsNullOrWhiteSpace(_authService.PIN))
{
return LockType.PIN;
}
else
{
return LockType.Password;
}
}
public async Task CheckLockAsync(bool forceLock, bool onlyIfAlreadyLocked = false)
{
if(TopPageIsLock())
{
return;
}
var lockType = await GetLockTypeAsync(forceLock, onlyIfAlreadyLocked);
if(lockType == LockType.None)
{
return;
}
if(_stopwatch == null)
{
_stopwatch = Stopwatch.StartNew();
}
_appSettings.Locked = true;
switch(lockType)
{
case LockType.Fingerprint:
await Application.Current.MainPage.Navigation.PushModalAsync(
new ExtendedNavigationPage(new LockFingerprintPage(!forceLock)), false);
break;
case LockType.PIN:
await Application.Current.MainPage.Navigation.PushModalAsync(
new ExtendedNavigationPage(new LockPinPage()), false);
break;
case LockType.Password:
await Application.Current.MainPage.Navigation.PushModalAsync(
new ExtendedNavigationPage(new LockPasswordPage()), false);
break;
default:
break;
}
}
public bool TopPageIsLock()
{
var currentPage = Application.Current.MainPage.Navigation.ModalStack.LastOrDefault() as ExtendedNavigationPage;
if((currentPage?.CurrentPage as LockFingerprintPage) != null)
{
return true;
}
if((currentPage?.CurrentPage as LockPinPage) != null)
{
return true;
}
if((currentPage?.CurrentPage as LockPasswordPage) != null)
{
return true;
}
return false;
}
}
}

View File

@@ -1,40 +0,0 @@
using System;
using Bit.App.Abstractions;
namespace Bit.Android.Services
{
public class NoopGoogleAnalyticsService : IGoogleAnalyticsService
{
public void TrackAppEvent(string eventName, string label = null)
{
}
public void TrackExtensionEvent(string eventName, string label = null)
{
}
public void TrackAutofillExtensionEvent(string eventName, string label = null)
{
}
public void TrackEvent(string category, string eventName, string label = null)
{
}
public void TrackException(string message, bool fatal)
{
}
public void TrackPage(string pageName)
{
}
public void Dispatch(Action completionHandler = null)
{
}
public void SetAppOptOut(bool optOut)
{
}
}
}

View File

@@ -1,29 +0,0 @@
using Newtonsoft.Json.Linq;
using Bit.App.Abstractions;
namespace Bit.App.Services
{
public class NoopPushNotificationListener : IPushNotificationListener
{
public void OnMessage(JObject value, string deviceType)
{
}
public void OnRegistered(string token, string deviceType)
{
}
public void OnUnregistered(string deviceType)
{
}
public void OnError(string message, string deviceType)
{
}
public bool ShouldShowNotification()
{
return false;
}
}
}

View File

@@ -1,17 +0,0 @@
using Bit.App.Abstractions;
namespace Bit.App.Services
{
public class NoopPushNotificationService : IPushNotificationService
{
public string Token => null;
public void Register()
{
}
public void Unregister()
{
}
}
}

View File

@@ -1,188 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Bit.App.Abstractions;
using Plugin.Settings.Abstractions;
using PCLCrypto;
namespace Bit.App.Services
{
public class PasswordGenerationService : IPasswordGenerationService
{
private readonly ISettings _settings;
public PasswordGenerationService(ISettings settings)
{
_settings = settings;
}
public string GeneratePassword(
int? length = null,
bool? uppercase = null,
bool? lowercase = null,
bool? numbers = null,
bool? special = null,
bool? ambiguous = null,
int? minUppercase = null,
int? minLowercase = null,
int? minNumbers = null,
int? minSpecial = null)
{
int minUppercaseValue = minUppercase.GetValueOrDefault(0),
minLowercaseValue = minLowercase.GetValueOrDefault(0),
minNumbersValue = minNumbers.GetValueOrDefault(_settings.GetValueOrDefault(Constants.PasswordGeneratorMinNumbers, 1)),
minSpecialValue = minSpecial.GetValueOrDefault(_settings.GetValueOrDefault(Constants.PasswordGeneratorMinSpecial, 1)),
lengthValue = length.GetValueOrDefault(_settings.GetValueOrDefault(Constants.PasswordGeneratorLength, 10));
bool uppercaseValue = uppercase.GetValueOrDefault(_settings.GetValueOrDefault(Constants.PasswordGeneratorUppercase, true)),
lowercaseValue = lowercase.GetValueOrDefault(_settings.GetValueOrDefault(Constants.PasswordGeneratorLowercase, true)),
numbersValue = numbers.GetValueOrDefault(_settings.GetValueOrDefault(Constants.PasswordGeneratorNumbers, true)),
specialValue = special.GetValueOrDefault(_settings.GetValueOrDefault(Constants.PasswordGeneratorSpecial, true)),
ambiguousValue = ambiguous.GetValueOrDefault(_settings.GetValueOrDefault(Constants.PasswordGeneratorAmbiguous, false));
// Sanitize
if(uppercaseValue && minUppercaseValue <= 0)
{
minUppercaseValue = 1;
}
if(lowercaseValue && minLowercaseValue <= 0)
{
minLowercaseValue = 1;
}
if(numbersValue && minNumbersValue <= 0)
{
minNumbersValue = 1;
}
if(specialValue && minSpecialValue <= 0)
{
minSpecialValue = 1;
}
if(lengthValue < 1)
{
lengthValue = 10;
}
var minLength = minUppercaseValue + minLowercaseValue + minNumbersValue + minSpecialValue;
if(lengthValue < minLength)
{
lengthValue = minLength;
}
var positionsBuilder = new StringBuilder();
if(lowercaseValue && minLowercaseValue > 0)
{
for(int i = 0; i < minLowercaseValue; i++)
{
positionsBuilder.Append("l");
}
}
if(uppercaseValue && minUppercaseValue > 0)
{
for(int i = 0; i < minUppercaseValue; i++)
{
positionsBuilder.Append("u");
}
}
if(numbersValue && minNumbersValue > 0)
{
for(int i = 0; i < minNumbersValue; i++)
{
positionsBuilder.Append("n");
}
}
if(specialValue && minSpecialValue > 0)
{
for(int i = 0; i < minSpecialValue; i++)
{
positionsBuilder.Append("s");
}
}
while(positionsBuilder.Length < lengthValue)
{
positionsBuilder.Append("a");
}
// Shuffle
var positions = positionsBuilder.ToString().ToCharArray().OrderBy(a => Next(int.MaxValue)).ToArray();
// Build out other character sets
var allCharSet = string.Empty;
var lowercaseCharSet = "abcdefghijkmnopqrstuvwxyz";
if(ambiguousValue)
{
lowercaseCharSet = string.Concat(lowercaseCharSet, "l");
}
if(lowercaseValue)
{
allCharSet = string.Concat(allCharSet, lowercaseCharSet);
}
var uppercaseCharSet = "ABCDEFGHIJKLMNPQRSTUVWXYZ";
if(ambiguousValue)
{
uppercaseCharSet = string.Concat(uppercaseCharSet, "O");
}
if(uppercaseValue)
{
allCharSet = string.Concat(allCharSet, uppercaseCharSet);
}
var numberCharSet = "23456789";
if(ambiguousValue)
{
numberCharSet = string.Concat(numberCharSet, "01");
}
if(numbersValue)
{
allCharSet = string.Concat(allCharSet, numberCharSet);
}
var specialCharSet = "!@#$%^&*";
if(specialValue)
{
allCharSet = string.Concat(allCharSet, specialCharSet);
}
var password = new StringBuilder();
for(var i = 0; i < lengthValue; i++)
{
string positionChars = string.Empty;
switch(positions[i])
{
case 'l':
positionChars = lowercaseCharSet;
break;
case 'u':
positionChars = uppercaseCharSet;
break;
case 'n':
positionChars = numberCharSet;
break;
case 's':
positionChars = specialCharSet;
break;
case 'a':
positionChars = allCharSet;
break;
}
var randomCharIndex = Next(positionChars.Length);
password.Append(positionChars[randomCharIndex]);
}
return password.ToString();
}
private int Next(int maxValue)
{
if(maxValue == 0)
{
return 0;
}
return (int)(WinRTCrypto.CryptographicBuffer.GenerateRandomNumber() % maxValue);
}
}
}

View File

@@ -1,206 +0,0 @@
#if !FDROID
using System.Diagnostics;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Bit.App.Abstractions;
using Bit.App.Models;
using Plugin.Settings.Abstractions;
using System;
using XLabs.Ioc;
using Xamarin.Forms;
namespace Bit.App.Services
{
public class PushNotificationListener : IPushNotificationListener
{
private bool _showNotification;
private bool _resolved;
private ISyncService _syncService;
private IDeviceApiRepository _deviceApiRepository;
private IAuthService _authService;
private IAppIdService _appIdService;
private ISettings _settings;
private void Resolve()
{
if(_resolved)
{
return;
}
_syncService = Resolver.Resolve<ISyncService>();
_deviceApiRepository = Resolver.Resolve<IDeviceApiRepository>();
_authService = Resolver.Resolve<IAuthService>();
_appIdService = Resolver.Resolve<IAppIdService>();
_settings = Resolver.Resolve<ISettings>();
_resolved = true;
}
public void OnMessage(JObject value, string deviceType)
{
Resolve();
if(value == null)
{
return;
}
_showNotification = false;
Debug.WriteLine("Message Arrived: {0}", JsonConvert.SerializeObject(value));
if(!_authService.IsAuthenticated)
{
return;
}
PushNotificationDataPayload data = null;
if(deviceType == Device.Android)
{
data = value.ToObject<PushNotificationDataPayload>();
}
else
{
if(!value.TryGetValue("data", StringComparison.OrdinalIgnoreCase, out JToken dataToken) ||
dataToken == null)
{
return;
}
data = dataToken.ToObject<PushNotificationDataPayload>();
}
if(data?.Payload == null)
{
return;
}
switch(data.Type)
{
case Enums.PushType.SyncCipherUpdate:
case Enums.PushType.SyncCipherCreate:
var cipherCreateUpdateMessage = JsonConvert.DeserializeObject<SyncCipherPushNotification>(data.Payload);
if(cipherCreateUpdateMessage.OrganizationId == null &&
cipherCreateUpdateMessage.UserId != _authService.UserId)
{
break;
}
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 = JsonConvert.DeserializeObject<SyncFolderPushNotification>(data.Payload);
if(folderCreateUpdateMessage.UserId != _authService.UserId)
{
break;
}
_syncService.SyncFolderAsync(folderCreateUpdateMessage.Id);
break;
case Enums.PushType.SyncFolderDelete:
var folderDeleteMessage = JsonConvert.DeserializeObject<SyncFolderPushNotification>(data.Payload);
if(folderDeleteMessage.UserId != _authService.UserId)
{
break;
}
_syncService.SyncDeleteFolderAsync(folderDeleteMessage.Id, folderDeleteMessage.RevisionDate);
break;
case Enums.PushType.SyncLoginDelete:
var loginDeleteMessage = JsonConvert.DeserializeObject<SyncCipherPushNotification>(data.Payload);
if(loginDeleteMessage.OrganizationId == null &&
loginDeleteMessage.UserId != _authService.UserId)
{
break;
}
else if(loginDeleteMessage.OrganizationId != null &&
!_authService.BelongsToOrganization(loginDeleteMessage.OrganizationId))
{
break;
}
_syncService.SyncDeleteCipherAsync(loginDeleteMessage.Id);
break;
case Enums.PushType.SyncCiphers:
case Enums.PushType.SyncVault:
var cipherMessage = JsonConvert.DeserializeObject<UserPushNotification>(data.Payload);
if(cipherMessage.UserId != _authService.UserId)
{
break;
}
_syncService.FullSyncAsync(true);
break;
case Enums.PushType.SyncSettings:
var domainMessage = JsonConvert.DeserializeObject<UserPushNotification>(data.Payload);
if(domainMessage.UserId != _authService.UserId)
{
break;
}
_syncService.SyncSettingsAsync();
break;
case Enums.PushType.SyncOrgKeys:
var orgKeysMessage = JsonConvert.DeserializeObject<UserPushNotification>(data.Payload);
if(orgKeysMessage.UserId != _authService.UserId)
{
break;
}
_syncService.SyncProfileAsync();
break;
case Enums.PushType.LogOut:
var logOutMessage = JsonConvert.DeserializeObject<UserPushNotification>(data.Payload);
if(logOutMessage.UserId == _authService.UserId)
{
_authService.LogOut(null);
}
break;
default:
break;
}
}
public async void OnRegistered(string token, string deviceType)
{
Resolve();
Debug.WriteLine(string.Format("Push Notification - Device Registered - Token : {0}", token));
if(!_authService.IsAuthenticated)
{
return;
}
var response = await _deviceApiRepository.PutTokenAsync(_appIdService.AppId,
new Models.Api.DeviceTokenRequest(token));
if(response.Succeeded)
{
Debug.WriteLine("Registered device with server.");
_settings.AddOrUpdateValue(Constants.PushLastRegistrationDate, DateTime.UtcNow);
if(deviceType == Device.Android)
{
_settings.AddOrUpdateValue(Constants.PushCurrentToken, token);
}
}
else
{
Debug.WriteLine("Failed to register device.");
}
}
public void OnUnregistered(string deviceType)
{
Debug.WriteLine("Push Notification - Device Unnregistered");
}
public void OnError(string message, string deviceType)
{
Debug.WriteLine(string.Format("Push notification error - {0}", message));
}
public bool ShouldShowNotification()
{
return _showNotification;
}
}
}
#endif

View File

@@ -1,36 +0,0 @@
using Bit.App.Abstractions;
using Plugin.Settings.Abstractions;
using System.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace Bit.App.Services
{
public class SettingsService : ISettingsService
{
private readonly ISettingsRepository _settingsRepository;
private readonly ISettings _settings;
private readonly IAuthService _authService;
public SettingsService(
ISettingsRepository settingsRepository,
ISettings settings,
IAuthService authService)
{
_settingsRepository = settingsRepository;
_settings = settings;
_authService = authService;
}
public async Task<IEnumerable<IEnumerable<string>>> GetEquivalentDomainsAsync()
{
var settings = await _settingsRepository.GetByIdAsync(_authService.UserId);
if(string.IsNullOrWhiteSpace(settings?.EquivalentDomains))
{
return new List<string[]>();
}
return JsonConvert.DeserializeObject<IEnumerable<IEnumerable<string>>>(settings.EquivalentDomains);
}
}
}

View File

@@ -1,595 +0,0 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Models.Data;
using Plugin.Settings.Abstractions;
using Bit.App.Models.Api;
using System.Collections.Generic;
using Xamarin.Forms;
using Newtonsoft.Json;
using Bit.App.Models;
namespace Bit.App.Services
{
public class SyncService : ISyncService
{
private readonly ICipherApiRepository _cipherApiRepository;
private readonly IFolderApiRepository _folderApiRepository;
private readonly IAccountsApiRepository _accountsApiRepository;
private readonly ISettingsApiRepository _settingsApiRepository;
private readonly ISyncApiRepository _syncApiRepository;
private readonly IFolderRepository _folderRepository;
private readonly ICollectionRepository _collectionRepository;
private readonly ICipherCollectionRepository _cipherCollectionRepository;
private readonly ICipherService _cipherService;
private readonly IAttachmentRepository _attachmentRepository;
private readonly ISettingsRepository _settingsRepository;
private readonly IAuthService _authService;
private readonly ICryptoService _cryptoService;
private readonly ISettings _settings;
private readonly IAppSettingsService _appSettingsService;
public SyncService(
ICipherApiRepository cipherApiRepository,
IFolderApiRepository folderApiRepository,
IAccountsApiRepository accountsApiRepository,
ISettingsApiRepository settingsApiRepository,
ISyncApiRepository syncApiRepository,
IFolderRepository folderRepository,
ICollectionRepository collectionRepository,
ICipherCollectionRepository cipherCollectionRepository,
ICipherService cipherService,
IAttachmentRepository attachmentRepository,
ISettingsRepository settingsRepository,
IAuthService authService,
ICryptoService cryptoService,
ISettings settings,
IAppSettingsService appSettingsService)
{
_cipherApiRepository = cipherApiRepository;
_folderApiRepository = folderApiRepository;
_accountsApiRepository = accountsApiRepository;
_settingsApiRepository = settingsApiRepository;
_syncApiRepository = syncApiRepository;
_folderRepository = folderRepository;
_collectionRepository = collectionRepository;
_cipherCollectionRepository = cipherCollectionRepository;
_cipherService = cipherService;
_attachmentRepository = attachmentRepository;
_settingsRepository = settingsRepository;
_authService = authService;
_cryptoService = cryptoService;
_settings = settings;
_appSettingsService = appSettingsService;
}
public bool SyncInProgress { get; private set; }
public async Task<bool> SyncCipherAsync(string id)
{
if(!_authService.IsAuthenticated)
{
return false;
}
SyncStarted();
var cipher = await _cipherApiRepository.GetByIdAsync(id).ConfigureAwait(false);
if(!CheckSuccess(cipher))
{
return false;
}
try
{
var existingCipher = await _cipherService.GetByIdAsync(cipher.Result.Id);
var cipherData = new CipherData(cipher.Result, _authService.UserId);
await _cipherService.UpsertDataAsync(cipherData, true, existingCipher == null).ConfigureAwait(false);
var localAttachments = (await _attachmentRepository.GetAllByCipherIdAsync(cipherData.Id)
.ConfigureAwait(false));
if(cipher.Result.Attachments != null)
{
var attachmentData = cipher.Result.Attachments.Select(a => new AttachmentData(a, cipherData.Id));
await _cipherService.UpsertAttachmentDataAsync(attachmentData).ConfigureAwait(false);
}
if(localAttachments != null)
{
foreach(var attachment in localAttachments
.Where(a => !cipher.Result.Attachments.Any(sa => sa.Id == a.Id)))
{
try
{
await _cipherService.DeleteAttachmentDataAsync(attachment.Id).ConfigureAwait(false);
}
catch(SQLite.SQLiteException) { }
}
}
}
catch(SQLite.SQLiteException)
{
SyncCompleted(false);
return false;
}
SyncCompleted(true);
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)
{
return false;
}
SyncStarted();
try
{
await _folderRepository.DeleteWithCipherUpdateAsync(id, revisionDate).ConfigureAwait(false);
SyncCompleted(true);
return true;
}
catch(SQLite.SQLiteException)
{
SyncCompleted(false);
return false;
}
}
public async Task<bool> SyncDeleteCipherAsync(string id)
{
if(!_authService.IsAuthenticated)
{
return false;
}
SyncStarted();
try
{
await _cipherService.DeleteDataAsync(id, true).ConfigureAwait(false);
SyncCompleted(true);
return true;
}
catch(SQLite.SQLiteException)
{
SyncCompleted(false);
return false;
}
}
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, !string.IsNullOrWhiteSpace(_appSettingsService.SecurityStamp) &&
_appSettingsService.SecurityStamp != profile.Result.SecurityStamp))
{
return false;
}
await SyncProfileKeysAsync(profile.Result);
SyncCompleted(true);
return true;
}
public async Task<bool> FullSyncAsync(TimeSpan syncThreshold, bool forceSync = false)
{
var lastSync = _settings.GetValueOrDefault(Constants.LastSync, DateTime.MinValue);
if(DateTime.UtcNow - lastSync < syncThreshold)
{
return false;
}
return await FullSyncAsync(forceSync).ConfigureAwait(false);
}
public async Task<bool> FullSyncAsync(bool forceSync = false)
{
if(!_authService.IsAuthenticated)
{
return false;
}
if(!forceSync && !(await NeedsToSyncAsync()))
{
_settings.AddOrUpdateValue(Constants.LastSync, DateTime.UtcNow);
return false;
}
SyncStarted();
var now = DateTime.UtcNow;
var syncResponse = await _syncApiRepository.Get();
if(!CheckSuccess(syncResponse,
!string.IsNullOrWhiteSpace(_appSettingsService.SecurityStamp) &&
syncResponse.Result?.Profile != null &&
_appSettingsService.SecurityStamp != syncResponse.Result.Profile.SecurityStamp))
{
return false;
}
var ciphersDict = syncResponse.Result.Ciphers.ToDictionary(s => s.Id);
var foldersDict = syncResponse.Result.Folders.ToDictionary(f => f.Id);
var collectionsDict = syncResponse.Result.Collections?.ToDictionary(c => c.Id);
var cipherTask = SyncCiphersAsync(ciphersDict);
var folderTask = SyncFoldersAsync(foldersDict);
var collectionsTask = SyncCollectionsAsync(collectionsDict);
var domainsTask = SyncDomainsAsync(syncResponse.Result.Domains);
var profileTask = SyncProfileKeysAsync(syncResponse.Result.Profile);
await Task.WhenAll(cipherTask, folderTask, collectionsTask, domainsTask, profileTask).ConfigureAwait(false);
if(folderTask.Exception != null || cipherTask.Exception != null || collectionsTask.Exception != null ||
domainsTask.Exception != null || profileTask.Exception != null)
{
SyncCompleted(false);
MessagingCenter.Send(Application.Current, "FullSyncCompleted", false);
return false;
}
_settings.AddOrUpdateValue(Constants.LastSync, now);
SyncCompleted(true);
MessagingCenter.Send(Application.Current, "FullSyncCompleted", true);
return true;
}
private async Task<bool> NeedsToSyncAsync()
{
if(!_settings.Contains(Constants.LastSync))
{
return true;
}
var lastSync = _settings.GetValueOrDefault(Constants.LastSync, DateTime.MinValue);
var accountRevisionDate = await _accountsApiRepository.GetAccountRevisionDateAsync();
if(accountRevisionDate.Succeeded && accountRevisionDate.Result.HasValue &&
accountRevisionDate.Result.Value > lastSync)
{
return true;
}
if(Application.Current != null && (accountRevisionDate.StatusCode == System.Net.HttpStatusCode.Forbidden
|| accountRevisionDate.StatusCode == System.Net.HttpStatusCode.Unauthorized))
{
_authService.LogOut();
}
return false;
}
private async Task SyncFoldersAsync(IDictionary<string, FolderResponse> serverFolders)
{
if(!_authService.IsAuthenticated)
{
return;
}
var localFolders = (await _folderRepository.GetAllByUserIdAsync(_authService.UserId)
.ConfigureAwait(false))
.GroupBy(f => f.Id)
.Select(f => f.First())
.ToDictionary(f => f.Id);
foreach(var serverFolder in serverFolders)
{
if(!_authService.IsAuthenticated)
{
return;
}
try
{
var data = new FolderData(serverFolder.Value, _authService.UserId);
await _folderRepository.UpsertAsync(data).ConfigureAwait(false);
}
catch(SQLite.SQLiteException) { }
}
foreach(var folder in localFolders.Where(localFolder => !serverFolders.ContainsKey(localFolder.Key)))
{
try
{
await _folderRepository.DeleteAsync(folder.Value.Id).ConfigureAwait(false);
}
catch(SQLite.SQLiteException) { }
}
}
private async Task SyncCollectionsAsync(IDictionary<string, CollectionResponse> serverCollections)
{
if(!_authService.IsAuthenticated)
{
return;
}
var localCollections = (await _collectionRepository.GetAllByUserIdAsync(_authService.UserId)
.ConfigureAwait(false))
.GroupBy(f => f.Id)
.Select(f => f.First())
.ToDictionary(f => f.Id);
if(serverCollections != null)
{
foreach(var serverCollection in serverCollections)
{
if(!_authService.IsAuthenticated)
{
return;
}
try
{
var data = new CollectionData(serverCollection.Value, _authService.UserId);
await _collectionRepository.UpsertAsync(data).ConfigureAwait(false);
}
catch(SQLite.SQLiteException) { }
}
}
foreach(var collection in localCollections.Where(lc => !serverCollections.ContainsKey(lc.Key)))
{
try
{
await _collectionRepository.DeleteAsync(collection.Value.Id).ConfigureAwait(false);
}
catch(SQLite.SQLiteException) { }
}
}
private async Task SyncCiphersAsync(IDictionary<string, CipherResponse> serverCiphers)
{
if(!_authService.IsAuthenticated)
{
return;
}
var localCiphers = (await _cipherService.GetAllAsync()
.ConfigureAwait(false))
.GroupBy(s => s.Id)
.Select(s => s.First())
.ToDictionary(s => s.Id);
var localAttachments = (await _attachmentRepository.GetAllByUserIdAsync(_authService.UserId)
.ConfigureAwait(false))
.GroupBy(a => a.LoginId)
.ToDictionary(g => g.Key);
var cipherCollections = new List<CipherCollectionData>();
foreach(var serverCipher in serverCiphers)
{
if(!_authService.IsAuthenticated)
{
return;
}
var collectionForThisCipher = serverCipher.Value.CollectionIds?.Select(cid => new CipherCollectionData
{
CipherId = serverCipher.Value.Id,
CollectionId = cid,
UserId = _authService.UserId
}).ToList();
if(collectionForThisCipher != null && collectionForThisCipher.Any())
{
cipherCollections.AddRange(collectionForThisCipher);
}
try
{
var localCipher = localCiphers.ContainsKey(serverCipher.Value.Id) ?
localCiphers[serverCipher.Value.Id] : null;
var data = new CipherData(serverCipher.Value, _authService.UserId);
await _cipherService.UpsertDataAsync(data, false, false).ConfigureAwait(false);
if(serverCipher.Value.Attachments != null)
{
var attachmentData = serverCipher.Value.Attachments.Select(a => new AttachmentData(a, data.Id));
await _cipherService.UpsertAttachmentDataAsync(attachmentData).ConfigureAwait(false);
}
if(localCipher != null && localAttachments != null && localAttachments.ContainsKey(localCipher.Id))
{
foreach(var attachment in localAttachments[localCipher.Id]
.Where(a => !serverCipher.Value.Attachments.Any(sa => sa.Id == a.Id)))
{
try
{
await _cipherService.DeleteAttachmentDataAsync(attachment.Id).ConfigureAwait(false);
}
catch(SQLite.SQLiteException) { }
}
}
}
catch(SQLite.SQLiteException) { }
}
foreach(var cipher in localCiphers.Where(local => !serverCiphers.ContainsKey(local.Key)))
{
try
{
await _cipherService.DeleteDataAsync(cipher.Value.Id, false).ConfigureAwait(false);
}
catch(SQLite.SQLiteException) { }
}
try
{
await _cipherCollectionRepository.DeleteByUserIdAsync(_authService.UserId).ConfigureAwait(false);
}
catch(SQLite.SQLiteException) { }
foreach(var cipherCollection in cipherCollections)
{
try
{
await _cipherCollectionRepository.InsertAsync(cipherCollection).ConfigureAwait(false);
}
catch(SQLite.SQLiteException) { }
}
}
private async Task SyncDomainsAsync(DomainsResponse serverDomains)
{
if(serverDomains == null)
{
return;
}
var eqDomains = new List<IEnumerable<string>>();
if(serverDomains.EquivalentDomains != null)
{
eqDomains.AddRange(serverDomains.EquivalentDomains);
}
if(serverDomains.GlobalEquivalentDomains != null)
{
eqDomains.AddRange(serverDomains.GlobalEquivalentDomains.Select(d => d.Domains));
}
try
{
await _settingsRepository.UpsertAsync(new SettingsData
{
Id = _authService.UserId,
EquivalentDomains = JsonConvert.SerializeObject(eqDomains)
});
}
catch(SQLite.SQLiteException) { }
}
private Task SyncProfileKeysAsync(ProfileResponse profile)
{
if(profile == null)
{
return Task.FromResult(0);
}
if(!string.IsNullOrWhiteSpace(profile.Key))
{
_cryptoService.SetEncKey(new CipherString(profile.Key));
}
if(!string.IsNullOrWhiteSpace(profile.PrivateKey))
{
_cryptoService.SetPrivateKey(new CipherString(profile.PrivateKey));
}
if(!string.IsNullOrWhiteSpace(profile.SecurityStamp))
{
_appSettingsService.SecurityStamp = profile.SecurityStamp;
}
_cryptoService.SetOrgKeys(profile);
_appSettingsService.OrganizationGivesPremium =
profile.Organizations?.Any(o => o.UsersGetPremium && o.Enabled) ?? false;
return Task.FromResult(0);
}
private void SyncStarted()
{
if(Application.Current == null)
{
return;
}
SyncInProgress = true;
MessagingCenter.Send(Application.Current, "SyncStarted");
}
private void SyncCompleted(bool successfully)
{
if(Application.Current == null)
{
return;
}
SyncInProgress = false;
MessagingCenter.Send(Application.Current, "SyncCompleted", successfully);
}
private bool CheckSuccess<T>(ApiResult<T> result, bool logout = false)
{
if(!result.Succeeded || logout)
{
SyncCompleted(false);
if(Application.Current != null && (logout ||
result.StatusCode == System.Net.HttpStatusCode.Forbidden ||
result.StatusCode == System.Net.HttpStatusCode.Unauthorized))
{
_authService.LogOut();
}
return false;
}
return true;
}
}
}

View File

@@ -1,214 +0,0 @@
using System;
using Bit.App.Abstractions;
using System.Text;
using Newtonsoft.Json.Linq;
using Bit.App.Utilities;
namespace Bit.App.Services
{
public class TokenService : ITokenService
{
private const string TokenKey = "accessToken";
private const string RefreshTokenKey = "refreshToken";
private const string TwoFactorTokenKeyFormat = "twoFactorToken_{0}";
private readonly ISecureStorageService _secureStorage;
private string _token;
private JObject _decodedToken;
private string _refreshToken;
public TokenService(ISecureStorageService secureStorage)
{
_secureStorage = secureStorage;
}
public string Token
{
get
{
if(_token != null)
{
return _token;
}
var tokenBytes = _secureStorage.Retrieve(TokenKey);
if(tokenBytes == null)
{
return null;
}
_token = Encoding.UTF8.GetString(tokenBytes, 0, tokenBytes.Length);
return _token;
}
set
{
if(value != null)
{
var tokenBytes = Encoding.UTF8.GetBytes(value);
_secureStorage.Store(TokenKey, tokenBytes);
}
else
{
_secureStorage.Delete(TokenKey);
RefreshToken = null;
}
_decodedToken = null;
_token = value;
}
}
public DateTime TokenExpiration
{
get
{
var decoded = DecodeToken();
if(decoded?["exp"] == null)
{
throw new InvalidOperationException("No exp in token.");
}
return Helpers.Epoc.AddSeconds(Convert.ToDouble(decoded["exp"].Value<long>()));
}
}
public string TokenIssuer
{
get
{
var decoded = DecodeToken();
if(decoded?["iss"] == null)
{
throw new InvalidOperationException("No issuer in token.");
}
return decoded?["iss"].Value<string>();
}
}
public bool TokenExpired => DateTime.UtcNow < TokenExpiration;
public TimeSpan TokenTimeRemaining => TokenExpiration - DateTime.UtcNow;
public bool TokenNeedsRefresh => TokenTimeRemaining.TotalMinutes < 5;
public string TokenUserId => DecodeToken()?["sub"]?.Value<string>();
public string TokenEmail => DecodeToken()?["email"]?.Value<string>();
public string TokenName => DecodeToken()?["name"]?.Value<string>();
public bool TokenPremium => DecodeToken()?["premium"]?.Value<bool>() ?? false;
public string RefreshToken
{
get
{
if(_refreshToken != null)
{
return _refreshToken;
}
var tokenBytes = _secureStorage.Retrieve(RefreshTokenKey);
if(tokenBytes == null)
{
return null;
}
_refreshToken = Encoding.UTF8.GetString(tokenBytes, 0, tokenBytes.Length);
return _refreshToken;
}
set
{
if(value != null)
{
var tokenBytes = Encoding.UTF8.GetBytes(value);
_secureStorage.Store(RefreshTokenKey, tokenBytes);
}
else
{
_secureStorage.Delete(RefreshTokenKey);
}
_refreshToken = value;
}
}
public string GetTwoFactorToken(string email)
{
var emailEncoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(email));
var tokenBytes = _secureStorage.Retrieve(string.Format(TwoFactorTokenKeyFormat, emailEncoded));
if(tokenBytes == null)
{
return null;
}
return Encoding.UTF8.GetString(tokenBytes, 0, tokenBytes.Length);
}
public void SetTwoFactorToken(string email, string token)
{
var emailEncoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(email));
var key = string.Format(TwoFactorTokenKeyFormat, emailEncoded);
if(token != null)
{
var tokenBytes = Encoding.UTF8.GetBytes(token);
_secureStorage.Store(key, tokenBytes);
}
else if(_secureStorage.Contains(key))
{
_secureStorage.Delete(key);
}
}
public JObject DecodeToken()
{
if(_decodedToken != null)
{
return _decodedToken;
}
if(Token == null)
{
throw new InvalidOperationException($"{nameof(Token)} not found.");
}
var parts = Token.Split('.');
if(parts.Length != 3)
{
throw new InvalidOperationException($"{nameof(Token)} must have 3 parts");
}
var decodedBytes = Base64UrlDecode(parts[1]);
if(decodedBytes == null || decodedBytes.Length < 1)
{
throw new InvalidOperationException($"{nameof(Token)} must have 3 parts");
}
_decodedToken = JObject.Parse(Encoding.UTF8.GetString(decodedBytes, 0, decodedBytes.Length));
return _decodedToken;
}
private static byte[] Base64UrlDecode(string input)
{
var output = input;
// 62nd char of encoding
output = output.Replace('-', '+');
// 63rd char of encoding
output = output.Replace('_', '/');
// Pad with trailing '='s
switch(output.Length % 4)
{
case 0:
// No pad chars in this case
break;
case 2:
// Two pad chars
output += "=="; break;
case 3:
// One pad char
output += "="; break;
default:
throw new InvalidOperationException("Illegal base64url string!");
}
// Standard base64 decoder
return Convert.FromBase64String(output);
}
}
}