mirror of
https://github.com/bitwarden/android.git
synced 2025-03-14 02:08:48 +03:00
[SG-516] Additional forwarded email providers for username generator - mobile (#2304)
* [SG-516] Added DuckDuckGo provider * [SG-516] Add Fastmail as generator provider * [SG-516] code clean up * [SG-516] Default to service empty if first time on screen. Order services by alphabetic order. * [SG-516] Removed unnecessary prop. * [PS-2278] Fixed inverted eye bug. * [SG-516] Add icon glyph converter * [SG-516] Fixed enum default value and ordering
This commit is contained in:
parent
b8d53b0f81
commit
68a6449339
9 changed files with 264 additions and 26 deletions
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<pages:BaseContentPage
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
|
@ -20,6 +20,7 @@
|
|||
<ResourceDictionary>
|
||||
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||
<u:LocalizableEnumConverter x:Key="localizableEnum" />
|
||||
<u:IconGlyphConverter x:Key="iconGlyphConverter" />
|
||||
<xct:EnumToBoolConverter x:Key="enumToBool"/>
|
||||
<ToolbarItem Text="{u:I18n Cancel}" Command="{Binding CloseCommand}" Order="Primary" Priority="-1"
|
||||
x:Name="_closeItem" x:Key="closeItem" />
|
||||
|
@ -251,7 +252,7 @@
|
|||
Grid.Row="1"/>
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding ShowAnonAddyHiddenValueIcon}"
|
||||
Text="{Binding ShowAnonAddyApiAccessToken, Converter={StaticResource inverseBool, iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
|
||||
Command="{Binding ToggleForwardedEmailHiddenValueCommand}"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"/>
|
||||
|
@ -280,7 +281,7 @@
|
|||
IsPassword="{Binding ShowFirefoxRelayApiAccessToken, Converter={StaticResource inverseBool}}"/>
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding ShowFirefoxRelayHiddenValueIcon}"
|
||||
Text="{Binding ShowFirefoxRelayApiAccessToken, Converter={StaticResource inverseBool, iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
|
||||
Command="{Binding ToggleForwardedEmailHiddenValueCommand}"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"/>
|
||||
|
@ -301,7 +302,49 @@
|
|||
IsPassword="{Binding ShowSimpleLoginApiKey, Converter={StaticResource inverseBool}}"/>
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding ShowSimpleLoginHiddenValueIcon}"
|
||||
Text="{Binding ShowSimpleLoginApiKey, Converter={StaticResource inverseBool, iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
|
||||
Command="{Binding ToggleForwardedEmailHiddenValueCommand}"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"/>
|
||||
</Grid>
|
||||
<!--DUCKDUCKGO OPTIONS-->
|
||||
<Grid StyleClass="box-row, box-row-input"
|
||||
IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.DuckDuckGo}}"
|
||||
Grid.RowDefinitions="Auto,*"
|
||||
Grid.ColumnDefinitions="*,Auto">
|
||||
<Label
|
||||
Text="{u:I18n APIKeyRequiredParenthesis}"
|
||||
StyleClass="box-label"/>
|
||||
<Entry
|
||||
x:Name="_duckDuckGoApiAccessTokenEntry"
|
||||
Text="{Binding DuckDuckGoApiKey}"
|
||||
StyleClass="box-value"
|
||||
Grid.Row="1"
|
||||
IsPassword="{Binding ShowDuckDuckGoApiKey, Converter={StaticResource inverseBool}}"/>
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding ShowDuckDuckGoApiKey, Converter={StaticResource inverseBool, iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
|
||||
Command="{Binding ToggleForwardedEmailHiddenValueCommand}"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"/>
|
||||
</Grid>
|
||||
<!--FASTMAIL OPTIONS-->
|
||||
<Grid StyleClass="box-row, box-row-input"
|
||||
IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.Fastmail}}"
|
||||
Grid.RowDefinitions="Auto,*"
|
||||
Grid.ColumnDefinitions="*,Auto">
|
||||
<Label
|
||||
Text="{u:I18n APIKeyRequiredParenthesis}"
|
||||
StyleClass="box-label"/>
|
||||
<Entry
|
||||
x:Name="_fastmailApiAccessTokenEntry"
|
||||
Text="{Binding FastmailApiKey}"
|
||||
StyleClass="box-value"
|
||||
Grid.Row="1"
|
||||
IsPassword="{Binding ShowFastmailApiKey, Converter={StaticResource inverseBool}}"/>
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding ShowFastmailApiKey, Converter={StaticResource iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
|
||||
Command="{Binding ToggleForwardedEmailHiddenValueCommand}"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"/>
|
||||
|
|
|
@ -52,6 +52,8 @@ namespace Bit.App.Pages
|
|||
private bool _showFirefoxRelayApiAccessToken;
|
||||
private bool _showAnonAddyApiAccessToken;
|
||||
private bool _showSimpleLoginApiKey;
|
||||
private bool _showDuckDuckGoApiKey;
|
||||
private bool _showFastmailApiKey;
|
||||
private bool _editMode;
|
||||
|
||||
public GeneratorPageViewModel()
|
||||
|
@ -79,6 +81,8 @@ namespace Bit.App.Pages
|
|||
|
||||
ForwardedEmailServiceTypeOptions = new List<ForwardedEmailServiceType> {
|
||||
ForwardedEmailServiceType.AnonAddy,
|
||||
ForwardedEmailServiceType.DuckDuckGo,
|
||||
ForwardedEmailServiceType.Fastmail,
|
||||
ForwardedEmailServiceType.FirefoxRelay,
|
||||
ForwardedEmailServiceType.SimpleLogin
|
||||
};
|
||||
|
@ -461,15 +465,9 @@ namespace Bit.App.Pages
|
|||
{
|
||||
return _showAnonAddyApiAccessToken;
|
||||
}
|
||||
set => SetProperty(ref _showAnonAddyApiAccessToken, value,
|
||||
additionalPropertyNames: new string[]
|
||||
{
|
||||
nameof(ShowAnonAddyHiddenValueIcon)
|
||||
});
|
||||
set => SetProperty(ref _showAnonAddyApiAccessToken, value);
|
||||
}
|
||||
|
||||
public string ShowAnonAddyHiddenValueIcon => _showAnonAddyApiAccessToken ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||
|
||||
public string AnonAddyDomainName
|
||||
{
|
||||
get => _usernameOptions.AnonAddyDomainName;
|
||||
|
@ -504,15 +502,9 @@ namespace Bit.App.Pages
|
|||
{
|
||||
return _showFirefoxRelayApiAccessToken;
|
||||
}
|
||||
set => SetProperty(ref _showFirefoxRelayApiAccessToken, value,
|
||||
additionalPropertyNames: new string[]
|
||||
{
|
||||
nameof(ShowFirefoxRelayHiddenValueIcon)
|
||||
});
|
||||
set => SetProperty(ref _showFirefoxRelayApiAccessToken, value);
|
||||
}
|
||||
|
||||
public string ShowFirefoxRelayHiddenValueIcon => _showFirefoxRelayApiAccessToken ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||
|
||||
public string SimpleLoginApiKey
|
||||
{
|
||||
get => _usernameOptions.SimpleLoginApiKey;
|
||||
|
@ -533,14 +525,55 @@ namespace Bit.App.Pages
|
|||
{
|
||||
return _showSimpleLoginApiKey;
|
||||
}
|
||||
set => SetProperty(ref _showSimpleLoginApiKey, value,
|
||||
additionalPropertyNames: new string[]
|
||||
{
|
||||
nameof(ShowSimpleLoginHiddenValueIcon)
|
||||
});
|
||||
set => SetProperty(ref _showSimpleLoginApiKey, value);
|
||||
}
|
||||
|
||||
public string ShowSimpleLoginHiddenValueIcon => _showSimpleLoginApiKey ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||
public string DuckDuckGoApiKey
|
||||
{
|
||||
get => _usernameOptions.DuckDuckGoApiKey;
|
||||
set
|
||||
{
|
||||
if (_usernameOptions.DuckDuckGoApiKey != value)
|
||||
{
|
||||
_usernameOptions.DuckDuckGoApiKey = value;
|
||||
TriggerPropertyChanged(nameof(DuckDuckGoApiKey));
|
||||
SaveUsernameOptionsAsync(false).FireAndForget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowDuckDuckGoApiKey
|
||||
{
|
||||
get
|
||||
{
|
||||
return _showDuckDuckGoApiKey;
|
||||
}
|
||||
set => SetProperty(ref _showDuckDuckGoApiKey, value);
|
||||
}
|
||||
|
||||
|
||||
public string FastmailApiKey
|
||||
{
|
||||
get => _usernameOptions.FastMailApiKey;
|
||||
set
|
||||
{
|
||||
if (_usernameOptions.FastMailApiKey != value)
|
||||
{
|
||||
_usernameOptions.FastMailApiKey = value;
|
||||
TriggerPropertyChanged(nameof(FastmailApiKey));
|
||||
SaveUsernameOptionsAsync(false).FireAndForget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowFastmailApiKey
|
||||
{
|
||||
get
|
||||
{
|
||||
return _showFastmailApiKey;
|
||||
}
|
||||
set => SetProperty(ref _showFastmailApiKey, value);
|
||||
}
|
||||
|
||||
public bool CapitalizeRandomWordUsername
|
||||
{
|
||||
|
@ -778,6 +811,8 @@ namespace Bit.App.Pages
|
|||
TriggerPropertyChanged(nameof(FirefoxRelayApiAccessToken));
|
||||
TriggerPropertyChanged(nameof(AnonAddyDomainName));
|
||||
TriggerPropertyChanged(nameof(AnonAddyApiAccessToken));
|
||||
TriggerPropertyChanged(nameof(DuckDuckGoApiKey));
|
||||
TriggerPropertyChanged(nameof(FastmailApiKey));
|
||||
TriggerPropertyChanged(nameof(CatchAllEmailDomain));
|
||||
TriggerPropertyChanged(nameof(ForwardedEmailServiceSelected));
|
||||
TriggerPropertyChanged(nameof(UsernameTypeSelected));
|
||||
|
@ -849,6 +884,12 @@ namespace Bit.App.Pages
|
|||
case ForwardedEmailServiceType.SimpleLogin:
|
||||
ShowSimpleLoginApiKey = !ShowSimpleLoginApiKey;
|
||||
break;
|
||||
case ForwardedEmailServiceType.DuckDuckGo:
|
||||
ShowDuckDuckGoApiKey = !ShowDuckDuckGoApiKey;
|
||||
break;
|
||||
case ForwardedEmailServiceType.Fastmail:
|
||||
ShowFastmailApiKey = !ShowFastmailApiKey;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
18
src/App/Resources/AppResources.Designer.cs
generated
18
src/App/Resources/AppResources.Designer.cs
generated
|
@ -2047,6 +2047,15 @@ namespace Bit.App.Resources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to DuckDuckGo.
|
||||
/// </summary>
|
||||
public static string DuckDuckGo {
|
||||
get {
|
||||
return ResourceManager.GetString("DuckDuckGo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Edit.
|
||||
/// </summary>
|
||||
|
@ -2542,6 +2551,15 @@ namespace Bit.App.Resources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Fastmail.
|
||||
/// </summary>
|
||||
public static string Fastmail {
|
||||
get {
|
||||
return ResourceManager.GetString("Fastmail", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Favorite.
|
||||
/// </summary>
|
||||
|
|
|
@ -2423,6 +2423,14 @@ select Add TOTP to store the key safely</value>
|
|||
<value>SimpleLogin</value>
|
||||
<comment>"SimpleLogin" is the product name and should not be translated.</comment>
|
||||
</data>
|
||||
<data name="DuckDuckGo" xml:space="preserve">
|
||||
<value>DuckDuckGo</value>
|
||||
<comment>"DuckDuckGo" is the product name and should not be translated.</comment>
|
||||
</data>
|
||||
<data name="Fastmail" xml:space="preserve">
|
||||
<value>Fastmail</value>
|
||||
<comment>"Fastmail" is the product name and should not be translated.</comment>
|
||||
</data>
|
||||
<data name="APIAccessToken" xml:space="preserve">
|
||||
<value>API access token</value>
|
||||
</data>
|
||||
|
|
|
@ -54,7 +54,7 @@ namespace Bit.App.Utilities
|
|||
case BooleanGlyphType.Checkbox:
|
||||
return value ? BitwardenIcons.CheckSquare : BitwardenIcons.Square;
|
||||
case BooleanGlyphType.Eye:
|
||||
return value ? BitwardenIcons.Eye : BitwardenIcons.EyeSlash;
|
||||
return value ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
|
|
@ -4,11 +4,16 @@ namespace Bit.Core.Enums
|
|||
{
|
||||
public enum ForwardedEmailServiceType
|
||||
{
|
||||
None = -1,
|
||||
[LocalizableEnum("AnonAddy")]
|
||||
AnonAddy = 0,
|
||||
[LocalizableEnum("FirefoxRelay")]
|
||||
FirefoxRelay = 1,
|
||||
[LocalizableEnum("SimpleLogin")]
|
||||
SimpleLogin = 2,
|
||||
[LocalizableEnum("DuckDuckGo")]
|
||||
DuckDuckGo = 3,
|
||||
[LocalizableEnum("Fastmail")]
|
||||
Fastmail = 4,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,10 @@ namespace Bit.Core.Models.Domain
|
|||
{
|
||||
public class UsernameGenerationOptions
|
||||
{
|
||||
public UsernameGenerationOptions() { }
|
||||
public UsernameGenerationOptions()
|
||||
{
|
||||
ServiceType = ForwardedEmailServiceType.None;
|
||||
}
|
||||
|
||||
public UsernameType Type { get; set; }
|
||||
public ForwardedEmailServiceType ServiceType { get; set; }
|
||||
|
@ -16,6 +19,8 @@ namespace Bit.Core.Models.Domain
|
|||
public string CatchAllEmailDomain { get; set; }
|
||||
public string FirefoxRelayApiAccessToken { get; set; }
|
||||
public string SimpleLoginApiKey { get; set; }
|
||||
public string DuckDuckGoApiKey { get; set; }
|
||||
public string FastMailApiKey { get; set; }
|
||||
public string AnonAddyApiAccessToken { get; set; }
|
||||
public string AnonAddyDomainName { get; set; }
|
||||
public string EmailWebsite { get; set; }
|
||||
|
|
|
@ -760,6 +760,14 @@ namespace Bit.Core.Services
|
|||
case ForwardedEmailServiceType.SimpleLogin:
|
||||
requestMessage.Headers.Add("Authentication", config.ApiToken);
|
||||
break;
|
||||
case ForwardedEmailServiceType.DuckDuckGo:
|
||||
requestMessage.Headers.Add("Authorization", $"Bearer {config.ApiToken}");
|
||||
break;
|
||||
case ForwardedEmailServiceType.Fastmail:
|
||||
requestMessage.Headers.Add("Authorization", $"Bearer {config.ApiToken}");
|
||||
requestMessage.Content = new StringContent(await CreateFastmailRequest(config.ApiToken),
|
||||
Encoding.UTF8, "application/json");
|
||||
break;
|
||||
}
|
||||
|
||||
HttpResponseMessage response;
|
||||
|
@ -790,12 +798,99 @@ namespace Bit.Core.Services
|
|||
return result["full_address"]?.ToString();
|
||||
case ForwardedEmailServiceType.SimpleLogin:
|
||||
return result["alias"]?.ToString();
|
||||
case ForwardedEmailServiceType.DuckDuckGo:
|
||||
return $"{result["address"]?.ToString()}@duck.com";
|
||||
case ForwardedEmailServiceType.Fastmail:
|
||||
return HandleFastMailResponse(result);
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string HandleFastMailResponse(JObject result)
|
||||
{
|
||||
if (result["methodResponses"] == null || !result["methodResponses"].HasValues ||
|
||||
!result["methodResponses"][0].HasValues)
|
||||
{
|
||||
throw new Exception("Fastmail error: could not parse response.");
|
||||
}
|
||||
if (result["methodResponses"][0][0].ToString() == "MaskedEmail/set")
|
||||
{
|
||||
if (result["methodResponses"][0][1]?["created"]?["new-masked-email"] != null)
|
||||
{
|
||||
return result["methodResponses"][0][1]?["created"]?["new-masked-email"]?["email"].ToString();
|
||||
}
|
||||
if (result["methodResponses"][0][1]?["notCreated"]?["new-masked-email"] != null)
|
||||
{
|
||||
throw new Exception("Fastmail error: " +
|
||||
result["methodResponses"][0][1]?["created"]?["new-masked-email"]?["description"].ToString());
|
||||
}
|
||||
}
|
||||
else if (result["methodResponses"][0][0].ToString() == "error")
|
||||
{
|
||||
throw new Exception("Fastmail error: " + result["methodResponses"][0][1]?["description"].ToString());
|
||||
}
|
||||
throw new Exception("Fastmail error: could not parse response.");
|
||||
}
|
||||
|
||||
private async Task<string> CreateFastmailRequest(string apiKey)
|
||||
{
|
||||
using (var httpclient = new HttpClient())
|
||||
{
|
||||
HttpResponseMessage response;
|
||||
try
|
||||
{
|
||||
httpclient.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");
|
||||
httpclient.DefaultRequestHeaders.Add("Accept", "application/json");
|
||||
response = await httpclient.GetAsync(new Uri("https://api.fastmail.com/jmap/session"));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ApiException(HandleWebError(e));
|
||||
}
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
throw new ApiException(new ErrorResponse
|
||||
{
|
||||
StatusCode = response.StatusCode,
|
||||
Message = $"Fastmail error: {(int)response.StatusCode} {response.ReasonPhrase}."
|
||||
});
|
||||
}
|
||||
var result = JObject.Parse(await response.Content.ReadAsStringAsync());
|
||||
var accountId = result["primaryAccounts"]?["https://www.fastmail.com/dev/maskedemail"]?.ToString();
|
||||
var requestJObj = new JObject
|
||||
{
|
||||
new JProperty("using",
|
||||
new JArray { "https://www.fastmail.com/dev/maskedemail", "urn:ietf:params:jmap:core" }),
|
||||
new JProperty("methodCalls",
|
||||
new JArray
|
||||
{
|
||||
new JArray
|
||||
{
|
||||
"MaskedEmail/set",
|
||||
new JObject
|
||||
{
|
||||
["accountId"] = accountId,
|
||||
["create"] = new JObject
|
||||
{
|
||||
["new-masked-email"] = new JObject
|
||||
{
|
||||
["state"] = "enabled",
|
||||
["description"] = "",
|
||||
["url"] = "",
|
||||
["emailPrefix"] = ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"0"
|
||||
}
|
||||
})
|
||||
};
|
||||
return requestJObj.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private ErrorResponse HandleWebError(Exception e)
|
||||
{
|
||||
return new ErrorResponse
|
||||
|
|
|
@ -171,6 +171,29 @@ namespace Bit.Core.Services
|
|||
ApiToken = options.SimpleLoginApiKey,
|
||||
Url = "https://app.simplelogin.io/api/alias/random/new"
|
||||
});
|
||||
case ForwardedEmailServiceType.DuckDuckGo:
|
||||
if (string.IsNullOrWhiteSpace(options.DuckDuckGoApiKey))
|
||||
{
|
||||
return Constants.DefaultUsernameGenerated;
|
||||
}
|
||||
return await _apiService.GetUsernameFromAsync(ForwardedEmailServiceType.DuckDuckGo,
|
||||
new UsernameGeneratorConfig()
|
||||
{
|
||||
ApiToken = options.DuckDuckGoApiKey,
|
||||
Url = "https://quack.duckduckgo.com/api/email/addresses"
|
||||
});
|
||||
case ForwardedEmailServiceType.Fastmail:
|
||||
if (string.IsNullOrWhiteSpace(options.FastMailApiKey))
|
||||
{
|
||||
return Constants.DefaultUsernameGenerated;
|
||||
}
|
||||
|
||||
return await _apiService.GetUsernameFromAsync(ForwardedEmailServiceType.Fastmail,
|
||||
new UsernameGeneratorConfig()
|
||||
{
|
||||
ApiToken = options.FastMailApiKey,
|
||||
Url = "https://api.fastmail.com/jmap/api/"
|
||||
});
|
||||
default:
|
||||
_logger.Value.Error($"Error UsernameGenerationService: ForwardedEmailServiceType {options.ServiceType} not implemented.");
|
||||
return Constants.DefaultUsernameGenerated;
|
||||
|
|
Loading…
Add table
Reference in a new issue