mirror of
https://github.com/bitwarden/mobile
synced 2026-01-07 02:53:56 +00:00
reset for v2
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user