The mobile app vault (iOS and Android).
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

185 lines
6.9 KiB

using System.Text;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Bit.Core.Services.EmailForwarders;
using Bit.Core.Utilities;
namespace Bit.Core.Services
{
public class UsernameGenerationService : IUsernameGenerationService
{
private const string CATCH_ALL_EMAIL_DOMAIN_FORMAT = "{0}@{1}";
private readonly ICryptoService _cryptoService;
private readonly IApiService _apiService;
private readonly IStateService _stateService;
readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>();
private UsernameGenerationOptions _optionsCache;
public UsernameGenerationService(
ICryptoService cryptoService,
IApiService apiService,
IStateService stateService)
{
_cryptoService = cryptoService;
_apiService = apiService;
_stateService = stateService;
}
public async Task<string> GenerateAsync(UsernameGenerationOptions options)
{
switch (options.Type)
{
case UsernameType.PlusAddressedEmail:
return await GeneratePlusAddressedEmailAsync(options);
case UsernameType.CatchAllEmail:
return await GenerateCatchAllAsync(options);
case UsernameType.ForwardedEmailAlias:
return await GenerateForwardedEmailAliasAsync(options);
case UsernameType.RandomWord:
return await GenerateRandomWordAsync(options);
default:
_logger.Value.Error($"Error UsernameGenerationService: UsernameType {options.Type} not implemented.");
return string.Empty;
}
}
public async Task<UsernameGenerationOptions> GetOptionsAsync()
{
if (_optionsCache == null)
{
var options = await _stateService.GetUsernameGenerationOptionsAsync();
_optionsCache = options ?? new UsernameGenerationOptions();
}
return _optionsCache;
}
public async Task SaveOptionsAsync(UsernameGenerationOptions options)
{
await _stateService.SetUsernameGenerationOptionsAsync(options);
_optionsCache = options;
}
public void ClearCache()
{
_optionsCache = null;
}
private async Task<string> GenerateRandomWordAsync(UsernameGenerationOptions options)
{
var listLength = EEFLongWordList.Instance.List.Count - 1;
var wordIndex = await _cryptoService.RandomNumberAsync(0, listLength);
var randomWord = EEFLongWordList.Instance.List[wordIndex];
if (string.IsNullOrWhiteSpace(randomWord))
{
_logger.Value.Error($"Error UsernameGenerationService: EEFLongWordList has NullOrWhiteSpace value at {wordIndex} index.");
return Constants.DefaultUsernameGenerated;
}
if (options.CapitalizeRandomWordUsername)
{
randomWord = Capitalize(randomWord);
}
if (options.IncludeNumberRandomWordUsername)
{
randomWord = await AppendRandomNumberToRandomWordAsync(randomWord);
}
return randomWord;
}
private async Task<string> GeneratePlusAddressedEmailAsync(UsernameGenerationOptions options)
{
if (string.IsNullOrWhiteSpace(options.PlusAddressedEmail) || options.PlusAddressedEmail.Length < 3)
{
return Constants.DefaultUsernameGenerated;
}
var atIndex = options.PlusAddressedEmail.IndexOf("@");
if (atIndex < 1 || atIndex >= options.PlusAddressedEmail.Length - 1)
{
return options.PlusAddressedEmail;
}
if (options.PlusAddressedEmailType == UsernameEmailType.Random)
{
var randomString = await _cryptoService.RandomStringAsync(8);
return options.PlusAddressedEmail.Insert(atIndex, $"+{randomString}");
}
else
{
return options.PlusAddressedEmail.Insert(atIndex, $"+{options.EmailWebsite}");
}
}
private async Task<string> GenerateCatchAllAsync(UsernameGenerationOptions options)
{
var catchAllEmailDomain = options.CatchAllEmailDomain;
if (string.IsNullOrWhiteSpace(catchAllEmailDomain))
{
return Constants.DefaultUsernameGenerated;
}
if (options.CatchAllEmailType == UsernameEmailType.Random)
{
var randomString = await _cryptoService.RandomStringAsync(8);
return string.Format(CATCH_ALL_EMAIL_DOMAIN_FORMAT, randomString, catchAllEmailDomain);
}
return string.Format(CATCH_ALL_EMAIL_DOMAIN_FORMAT, options.EmailWebsite, catchAllEmailDomain);
}
private async Task<string> GenerateForwardedEmailAliasAsync(UsernameGenerationOptions options)
{
if (options.ServiceType == ForwardedEmailServiceType.AnonAddy)
{
return await new AnonAddyForwarder()
.GenerateAsync(_apiService, (AnonAddyForwarderOptions)options.GetForwarderOptions());
}
BaseForwarder<ForwarderOptions> simpleForwarder = null;
switch (options.ServiceType)
{
case ForwardedEmailServiceType.FirefoxRelay:
simpleForwarder = new FirefoxRelayForwarder();
break;
case ForwardedEmailServiceType.SimpleLogin:
simpleForwarder = new SimpleLoginForwarder();
break;
case ForwardedEmailServiceType.DuckDuckGo:
simpleForwarder = new DuckDuckGoForwarder();
break;
case ForwardedEmailServiceType.Fastmail:
simpleForwarder = new FastmailForwarder();
break;
default:
_logger.Value.Error($"Error UsernameGenerationService: ForwardedEmailServiceType {options.ServiceType} not implemented.");
return Constants.DefaultUsernameGenerated;
}
return await simpleForwarder.GenerateAsync(_apiService, options.GetForwarderOptions());
}
private string Capitalize(string str)
{
return char.ToUpper(str[0]) + str.Substring(1);
}
private async Task<string> AppendRandomNumberToRandomWordAsync(string word)
{
if (string.IsNullOrWhiteSpace(word))
{
return word;
}
var randomNumber = await _cryptoService.RandomNumberAsync(1, 9999);
return word + randomNumber.ToString("0000");
}
}
}