mirror of
https://github.com/bitwarden/mobile
synced 2026-01-09 03:53:15 +00:00
Merged master into PM-1575-display-passkeys
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Models.Request;
|
||||
using Bit.Core.Models.Response;
|
||||
@@ -48,6 +49,7 @@ namespace Bit.Core.Abstractions
|
||||
Task<SsoPrevalidateResponse> PreValidateSso(string identifier);
|
||||
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,
|
||||
TRequest body, bool authed, bool hasResponse, Action<HttpRequestMessage> alterRequest, bool logoutOnUnauthorized = true);
|
||||
Task<HttpResponseMessage> SendAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken = default);
|
||||
void SetUrls(EnvironmentUrls urls);
|
||||
[Obsolete("Mar 25 2021: This method has been deprecated in favor of direct uploads. This method still exists for backward compatibility with old server versions.")]
|
||||
Task<CipherResponse> PostCipherAttachmentLegacyAsync(string id, MultipartFormDataContent data);
|
||||
@@ -89,9 +91,9 @@ namespace Bit.Core.Abstractions
|
||||
Task<PasswordlessLoginResponse> GetAuthResponseAsync(string id, string accessCode);
|
||||
Task<PasswordlessLoginResponse> PutAuthRequestAsync(string id, string key, string masterPasswordHash, string deviceIdentifier, bool requestApproved);
|
||||
Task<PasswordlessLoginResponse> PostCreateRequestAsync(PasswordlessCreateLoginRequest passwordlessCreateLoginRequest);
|
||||
Task<string> GetUsernameFromAsync(ForwardedEmailServiceType service, UsernameGeneratorConfig config);
|
||||
Task<bool> GetKnownDeviceAsync(string email, string deviceIdentifier);
|
||||
Task<OrganizationDomainSsoDetailsResponse> GetOrgDomainSsoDetailsAsync(string email);
|
||||
Task<ConfigResponse> GetConfigsAsync();
|
||||
Task<string> GetFastmailAccountIdAsync(string apiKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ namespace Bit.Core.Abstractions
|
||||
Task<Tuple<EncString, SymmetricCryptoKey>> MakeShareKeyAsync();
|
||||
Task<SymmetricCryptoKey> MakeSendKeyAsync(byte[] keyMaterial);
|
||||
Task<int> RandomNumberAsync(int min, int max);
|
||||
Task<string> RandomStringAsync(int length);
|
||||
Task<Tuple<SymmetricCryptoKey, EncString>> RemakeEncKeyAsync(SymmetricCryptoKey key);
|
||||
Task<EncString> RsaEncryptAsync(byte[] data, byte[] publicKey = null);
|
||||
Task<byte[]> RsaDecryptAsync(string encValue, byte[] privateKey = null);
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<None Remove="Attributes\" />
|
||||
<None Remove="MessagePack" />
|
||||
<None Remove="MessagePack.MSBuild.Tasks" />
|
||||
<None Remove="Services\EmailForwarders\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -44,5 +45,6 @@
|
||||
<ItemGroup>
|
||||
<Folder Include="Services\Logging\" />
|
||||
<Folder Include="Attributes\" />
|
||||
<Folder Include="Services\EmailForwarders\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
11
src/Core/Exceptions/ForwardedEmailInvalidSecretException.cs
Normal file
11
src/Core/Exceptions/ForwardedEmailInvalidSecretException.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
namespace Bit.Core.Exceptions
|
||||
{
|
||||
public class ForwardedEmailInvalidSecretException : Exception
|
||||
{
|
||||
public ForwardedEmailInvalidSecretException(Exception innerEx)
|
||||
: base("Invalid API Secret", innerEx)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Services.EmailForwarders;
|
||||
|
||||
namespace Bit.Core.Models.Domain
|
||||
{
|
||||
@@ -24,5 +25,33 @@ namespace Bit.Core.Models.Domain
|
||||
public string AnonAddyApiAccessToken { get; set; }
|
||||
public string AnonAddyDomainName { get; set; }
|
||||
public string EmailWebsite { get; set; }
|
||||
|
||||
public ForwarderOptions GetForwarderOptions()
|
||||
{
|
||||
if (Type != UsernameType.ForwardedEmailAlias)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (ServiceType)
|
||||
{
|
||||
case ForwardedEmailServiceType.AnonAddy:
|
||||
return new AnonAddyForwarderOptions
|
||||
{
|
||||
ApiKey = AnonAddyApiAccessToken,
|
||||
DomainName = AnonAddyDomainName
|
||||
};
|
||||
case ForwardedEmailServiceType.DuckDuckGo:
|
||||
return new ForwarderOptions { ApiKey = DuckDuckGoApiKey };
|
||||
case ForwardedEmailServiceType.Fastmail:
|
||||
return new ForwarderOptions { ApiKey = FastMailApiKey };
|
||||
case ForwardedEmailServiceType.FirefoxRelay:
|
||||
return new ForwarderOptions { ApiKey = FirefoxRelayApiAccessToken };
|
||||
case ForwardedEmailServiceType.SimpleLogin:
|
||||
return new ForwarderOptions { ApiKey = SimpleLoginApiKey };
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace Bit.Core.Models.Domain
|
||||
{
|
||||
public class UsernameGeneratorConfig
|
||||
{
|
||||
public string ApiToken { get; set; }
|
||||
public string Domain { get; set; }
|
||||
public string Url { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
@@ -763,111 +764,29 @@ namespace Bit.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> GetUsernameFromAsync(ForwardedEmailServiceType service, UsernameGeneratorConfig config)
|
||||
public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using (var requestMessage = new HttpRequestMessage())
|
||||
HttpResponseMessage response;
|
||||
try
|
||||
{
|
||||
requestMessage.Version = new Version(1, 0);
|
||||
requestMessage.Method = HttpMethod.Post;
|
||||
requestMessage.RequestUri = new Uri(config.Url);
|
||||
requestMessage.Headers.Add("Accept", "application/json");
|
||||
|
||||
switch (service)
|
||||
{
|
||||
case ForwardedEmailServiceType.AnonAddy:
|
||||
requestMessage.Headers.Add("Authorization", $"Bearer {config.ApiToken}");
|
||||
requestMessage.Content = new FormUrlEncodedContent(new Dictionary<string, string>
|
||||
{
|
||||
["domain"] = config.Domain
|
||||
});
|
||||
break;
|
||||
case ForwardedEmailServiceType.FirefoxRelay:
|
||||
requestMessage.Headers.Add("Authorization", $"Token {config.ApiToken}");
|
||||
requestMessage.Content = new StringContent(JsonConvert.SerializeObject(
|
||||
new
|
||||
{
|
||||
enabled = true,
|
||||
description = "Generated by Bitwarden."
|
||||
}), Encoding.UTF8, "application/json");
|
||||
break;
|
||||
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;
|
||||
try
|
||||
{
|
||||
response = await _httpClient.SendAsync(requestMessage);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ApiException(HandleWebError(e));
|
||||
}
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
throw new ApiException(new ErrorResponse
|
||||
{
|
||||
StatusCode = response.StatusCode,
|
||||
Message = $"{service} error: {(int)response.StatusCode} {response.ReasonPhrase}."
|
||||
});
|
||||
}
|
||||
var responseJsonString = await response.Content.ReadAsStringAsync();
|
||||
var result = JObject.Parse(responseJsonString);
|
||||
|
||||
switch (service)
|
||||
{
|
||||
case ForwardedEmailServiceType.AnonAddy:
|
||||
return result["data"]?["email"]?.ToString();
|
||||
case ForwardedEmailServiceType.FirefoxRelay:
|
||||
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;
|
||||
}
|
||||
response = await _httpClient.SendAsync(requestMessage, cancellationToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ApiException(HandleWebError(e));
|
||||
}
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
throw new ApiException(new ErrorResponse
|
||||
{
|
||||
StatusCode = response.StatusCode,
|
||||
Message = $"{requestMessage.RequestUri} error: {(int)response.StatusCode} {response.ReasonPhrase}."
|
||||
});
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
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)
|
||||
public async Task<string> GetFastmailAccountIdAsync(string apiKey)
|
||||
{
|
||||
using (var httpclient = new HttpClient())
|
||||
{
|
||||
@@ -891,36 +810,7 @@ namespace Bit.Core.Services
|
||||
});
|
||||
}
|
||||
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();
|
||||
return result["primaryAccounts"]?["https://www.fastmail.com/dev/maskedemail"]?.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ namespace Bit.Core.Services
|
||||
{
|
||||
public class CryptoService : ICryptoService
|
||||
{
|
||||
private const string RANDOM_STRING_CHARSET = "abcdefghijklmnopqrstuvwxyz1234567890";
|
||||
|
||||
private readonly IStateService _stateService;
|
||||
private readonly ICryptoFunctionService _cryptoFunctionService;
|
||||
|
||||
@@ -633,6 +635,22 @@ namespace Bit.Core.Services
|
||||
return (int)(min + (ui % diff));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes random string with length <paramref name="length"/> based on the charset <see cref="RANDOM_STRING_CHARSET"/>
|
||||
/// </summary>
|
||||
public async Task<string> RandomStringAsync(int length)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
var randomCharIndex = await RandomNumberAsync(0, RANDOM_STRING_CHARSET.Length - 1);
|
||||
sb.Append(RANDOM_STRING_CHARSET[randomCharIndex]);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
private async Task<EncryptedObject> AesEncryptAsync(byte[] data, SymmetricCryptoKey key)
|
||||
|
||||
42
src/Core/Services/EmailForwarders/AnonAddyForwarder.cs
Normal file
42
src/Core/Services/EmailForwarders/AnonAddyForwarder.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Abstractions;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Bit.Core.Services.EmailForwarders
|
||||
{
|
||||
public class AnonAddyForwarderOptions : ForwarderOptions
|
||||
{
|
||||
public string DomainName { get; set; }
|
||||
}
|
||||
|
||||
public class AnonAddyForwarder : BaseForwarder<AnonAddyForwarderOptions>
|
||||
{
|
||||
protected override string RequestUri => "https://app.anonaddy.com/api/v1/aliases";
|
||||
|
||||
protected override bool CanGenerate(AnonAddyForwarderOptions options)
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(options.ApiKey) && !string.IsNullOrWhiteSpace(options.DomainName);
|
||||
}
|
||||
|
||||
protected override void ConfigureHeaders(HttpRequestHeaders headers, AnonAddyForwarderOptions options)
|
||||
{
|
||||
headers.Add("Authorization", $"Bearer {options.ApiKey}");
|
||||
}
|
||||
|
||||
protected override Task<HttpContent> GetContentAsync(IApiService apiService, AnonAddyForwarderOptions options)
|
||||
{
|
||||
return Task.FromResult<HttpContent>(new FormUrlEncodedContent(new Dictionary<string, string>
|
||||
{
|
||||
["domain"] = options.DomainName
|
||||
}));
|
||||
}
|
||||
|
||||
protected override string HandleResponse(JObject result)
|
||||
{
|
||||
return result["data"]?["email"]?.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
63
src/Core/Services/EmailForwarders/BaseForwarder.cs
Normal file
63
src/Core/Services/EmailForwarders/BaseForwarder.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Exceptions;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Bit.Core.Services.EmailForwarders
|
||||
{
|
||||
public abstract class BaseForwarder<T>
|
||||
where T : ForwarderOptions
|
||||
{
|
||||
protected abstract string RequestUri { get; }
|
||||
|
||||
public async Task<string> GenerateAsync(IApiService apiService, T options)
|
||||
{
|
||||
if (!CanGenerate(options))
|
||||
{
|
||||
return Constants.DefaultUsernameGenerated;
|
||||
}
|
||||
|
||||
using (var requestMessage = new HttpRequestMessage())
|
||||
{
|
||||
requestMessage.Version = new Version(1, 0);
|
||||
requestMessage.Method = HttpMethod.Post;
|
||||
requestMessage.RequestUri = new Uri(RequestUri);
|
||||
requestMessage.Headers.Add("Accept", "application/json");
|
||||
|
||||
ConfigureHeaders(requestMessage.Headers, options);
|
||||
requestMessage.Content = await GetContentAsync(apiService, options);
|
||||
|
||||
try
|
||||
{
|
||||
var response = await apiService.SendAsync(requestMessage);
|
||||
|
||||
var responseJsonString = await response.Content.ReadAsStringAsync();
|
||||
|
||||
return HandleResponse(JObject.Parse(responseJsonString));
|
||||
}
|
||||
catch (ApiException ex)
|
||||
{
|
||||
if (IsRequestSecretInvalid(ex))
|
||||
{
|
||||
throw new ForwardedEmailInvalidSecretException(ex);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual bool CanGenerate(T options) => !string.IsNullOrWhiteSpace(options.ApiKey);
|
||||
|
||||
protected abstract void ConfigureHeaders(HttpRequestHeaders headers, T options);
|
||||
|
||||
protected abstract Task<HttpContent> GetContentAsync(IApiService apiService, T options);
|
||||
|
||||
protected abstract string HandleResponse(JObject result);
|
||||
|
||||
protected virtual bool IsRequestSecretInvalid(ApiException ex) => ex.Error?.StatusCode == System.Net.HttpStatusCode.Unauthorized;
|
||||
}
|
||||
}
|
||||
25
src/Core/Services/EmailForwarders/DuckDuckGoForwarder.cs
Normal file
25
src/Core/Services/EmailForwarders/DuckDuckGoForwarder.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Abstractions;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Bit.Core.Services.EmailForwarders
|
||||
{
|
||||
public class DuckDuckGoForwarder : BaseForwarder<ForwarderOptions>
|
||||
{
|
||||
protected override string RequestUri => "https://quack.duckduckgo.com/api/email/addresses";
|
||||
|
||||
protected override void ConfigureHeaders(HttpRequestHeaders headers, ForwarderOptions options)
|
||||
{
|
||||
headers.Add("Authorization", $"Bearer {options.ApiKey}");
|
||||
}
|
||||
|
||||
protected override Task<HttpContent> GetContentAsync(IApiService apiService, ForwarderOptions options) => Task.FromResult<HttpContent>(null);
|
||||
|
||||
protected override string HandleResponse(JObject result)
|
||||
{
|
||||
return $"{result["address"]?.ToString()}@duck.com";
|
||||
}
|
||||
}
|
||||
}
|
||||
98
src/Core/Services/EmailForwarders/FastmailForwarder.cs
Normal file
98
src/Core/Services/EmailForwarders/FastmailForwarder.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Exceptions;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Bit.Core.Services.EmailForwarders
|
||||
{
|
||||
public class FastmailForwarder : BaseForwarder<ForwarderOptions>
|
||||
{
|
||||
protected override string RequestUri => "https://api.fastmail.com/jmap/api/";
|
||||
|
||||
protected override void ConfigureHeaders(HttpRequestHeaders headers, ForwarderOptions options)
|
||||
{
|
||||
headers.Add("Authorization", $"Bearer {options.ApiKey}");
|
||||
}
|
||||
|
||||
protected override async Task<HttpContent> GetContentAsync(IApiService apiService, ForwarderOptions options)
|
||||
{
|
||||
string accountId = null;
|
||||
try
|
||||
{
|
||||
accountId = await apiService.GetFastmailAccountIdAsync(options.ApiKey);
|
||||
}
|
||||
catch (ApiException ex)
|
||||
{
|
||||
if (IsRequestSecretInvalid(ex))
|
||||
{
|
||||
throw new ForwardedEmailInvalidSecretException(ex);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
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 new StringContent(requestJObj.ToString(), Encoding.UTF8, "application/json");
|
||||
}
|
||||
|
||||
protected override string HandleResponse(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.");
|
||||
}
|
||||
|
||||
protected override bool IsRequestSecretInvalid(ApiException ex) => base.IsRequestSecretInvalid(ex) || ex.Error?.StatusCode == System.Net.HttpStatusCode.Forbidden;
|
||||
}
|
||||
}
|
||||
36
src/Core/Services/EmailForwarders/FirefoxRelayForwarder.cs
Normal file
36
src/Core/Services/EmailForwarders/FirefoxRelayForwarder.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Abstractions;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Bit.Core.Services.EmailForwarders
|
||||
{
|
||||
public class FirefoxRelayForwarder : BaseForwarder<ForwarderOptions>
|
||||
{
|
||||
protected override string RequestUri => "https://relay.firefox.com/api/v1/relayaddresses/";
|
||||
|
||||
protected override void ConfigureHeaders(HttpRequestHeaders headers, ForwarderOptions options)
|
||||
{
|
||||
headers.Add("Authorization", $"Token {options.ApiKey}");
|
||||
}
|
||||
|
||||
protected override Task<HttpContent> GetContentAsync(IApiService apiService, ForwarderOptions options)
|
||||
{
|
||||
return Task.FromResult<HttpContent>(new StringContent(
|
||||
JsonConvert.SerializeObject(
|
||||
new
|
||||
{
|
||||
enabled = true,
|
||||
description = "Generated by Bitwarden."
|
||||
}), Encoding.UTF8, "application/json"));
|
||||
}
|
||||
|
||||
protected override string HandleResponse(JObject result)
|
||||
{
|
||||
return result["full_address"]?.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
7
src/Core/Services/EmailForwarders/ForwarderOptions.cs
Normal file
7
src/Core/Services/EmailForwarders/ForwarderOptions.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Bit.Core.Services.EmailForwarders
|
||||
{
|
||||
public class ForwarderOptions
|
||||
{
|
||||
public string ApiKey { get; set; }
|
||||
}
|
||||
}
|
||||
25
src/Core/Services/EmailForwarders/SimpleLoginForwarder.cs
Normal file
25
src/Core/Services/EmailForwarders/SimpleLoginForwarder.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Abstractions;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Bit.Core.Services.EmailForwarders
|
||||
{
|
||||
public class SimpleLoginForwarder : BaseForwarder<ForwarderOptions>
|
||||
{
|
||||
protected override string RequestUri => "https://app.simplelogin.io/api/alias/random/new";
|
||||
|
||||
protected override void ConfigureHeaders(HttpRequestHeaders headers, ForwarderOptions options)
|
||||
{
|
||||
headers.Add("Authentication", options.ApiKey);
|
||||
}
|
||||
|
||||
protected override Task<HttpContent> GetContentAsync(IApiService apiService, ForwarderOptions options) => Task.FromResult<HttpContent>(null);
|
||||
|
||||
protected override string HandleResponse(JObject result)
|
||||
{
|
||||
return result["alias"]?.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
using System.Threading.Tasks;
|
||||
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
|
||||
@@ -12,7 +14,7 @@ namespace Bit.Core.Services
|
||||
private readonly ICryptoService _cryptoService;
|
||||
private readonly IApiService _apiService;
|
||||
private readonly IStateService _stateService;
|
||||
readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
|
||||
readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>();
|
||||
private UsernameGenerationOptions _optionsCache;
|
||||
|
||||
public UsernameGenerationService(
|
||||
@@ -104,7 +106,7 @@ namespace Bit.Core.Services
|
||||
|
||||
if (options.PlusAddressedEmailType == UsernameEmailType.Random)
|
||||
{
|
||||
var randomString = await RandomStringAsync(8);
|
||||
var randomString = await _cryptoService.RandomStringAsync(8);
|
||||
return options.PlusAddressedEmail.Insert(atIndex, $"+{randomString}");
|
||||
}
|
||||
else
|
||||
@@ -124,7 +126,7 @@ namespace Bit.Core.Services
|
||||
|
||||
if (options.CatchAllEmailType == UsernameEmailType.Random)
|
||||
{
|
||||
var randomString = await RandomStringAsync(8);
|
||||
var randomString = await _cryptoService.RandomStringAsync(8);
|
||||
return string.Format(CATCH_ALL_EMAIL_DOMAIN_FORMAT, randomString, catchAllEmailDomain);
|
||||
}
|
||||
|
||||
@@ -133,85 +135,34 @@ namespace Bit.Core.Services
|
||||
|
||||
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.AnonAddy:
|
||||
if (string.IsNullOrWhiteSpace(options.AnonAddyApiAccessToken) || string.IsNullOrWhiteSpace(options.AnonAddyDomainName))
|
||||
{
|
||||
return Constants.DefaultUsernameGenerated;
|
||||
}
|
||||
return await _apiService.GetUsernameFromAsync(ForwardedEmailServiceType.AnonAddy,
|
||||
new UsernameGeneratorConfig()
|
||||
{
|
||||
ApiToken = options.AnonAddyApiAccessToken,
|
||||
Domain = options.AnonAddyDomainName,
|
||||
Url = "https://app.anonaddy.com/api/v1/aliases"
|
||||
});
|
||||
|
||||
case ForwardedEmailServiceType.FirefoxRelay:
|
||||
if (string.IsNullOrWhiteSpace(options.FirefoxRelayApiAccessToken))
|
||||
{
|
||||
return Constants.DefaultUsernameGenerated;
|
||||
}
|
||||
return await _apiService.GetUsernameFromAsync(ForwardedEmailServiceType.FirefoxRelay,
|
||||
new UsernameGeneratorConfig()
|
||||
{
|
||||
ApiToken = options.FirefoxRelayApiAccessToken,
|
||||
Url = "https://relay.firefox.com/api/v1/relayaddresses/"
|
||||
});
|
||||
|
||||
simpleForwarder = new FirefoxRelayForwarder();
|
||||
break;
|
||||
case ForwardedEmailServiceType.SimpleLogin:
|
||||
if (string.IsNullOrWhiteSpace(options.SimpleLoginApiKey))
|
||||
{
|
||||
return Constants.DefaultUsernameGenerated;
|
||||
}
|
||||
return await _apiService.GetUsernameFromAsync(ForwardedEmailServiceType.SimpleLogin,
|
||||
new UsernameGeneratorConfig()
|
||||
{
|
||||
ApiToken = options.SimpleLoginApiKey,
|
||||
Url = "https://app.simplelogin.io/api/alias/random/new"
|
||||
});
|
||||
simpleForwarder = new SimpleLoginForwarder();
|
||||
break;
|
||||
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"
|
||||
});
|
||||
simpleForwarder = new DuckDuckGoForwarder();
|
||||
break;
|
||||
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/"
|
||||
});
|
||||
simpleForwarder = new FastmailForwarder();
|
||||
break;
|
||||
default:
|
||||
_logger.Value.Error($"Error UsernameGenerationService: ForwardedEmailServiceType {options.ServiceType} not implemented.");
|
||||
return Constants.DefaultUsernameGenerated;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> RandomStringAsync(int length)
|
||||
{
|
||||
var str = "";
|
||||
var charSet = "abcdefghijklmnopqrstuvwxyz1234567890";
|
||||
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
var randomCharIndex = await _cryptoService.RandomNumberAsync(0, charSet.Length - 1);
|
||||
str += charSet[randomCharIndex];
|
||||
}
|
||||
|
||||
return str;
|
||||
return await simpleForwarder.GenerateAsync(_apiService, options.GetForwarderOptions());
|
||||
}
|
||||
|
||||
private string Capitalize(string str)
|
||||
|
||||
Reference in New Issue
Block a user