1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-03 09:02:48 -05:00

Turn on file scoped namespaces (#2225)

This commit is contained in:
Justin Baur
2022-08-29 14:53:16 -04:00
committed by GitHub
parent 7c4521e0b4
commit 34fb4cca2a
1206 changed files with 73816 additions and 75022 deletions

View File

@ -2,57 +2,56 @@
using Bit.Core.Exceptions;
using Bit.Core.Services;
namespace Bit.Core.Utilities
namespace Bit.Core.Utilities;
public static class BillingHelpers
{
public static class BillingHelpers
internal static async Task<string> AdjustStorageAsync(IPaymentService paymentService, IStorableSubscriber storableSubscriber,
short storageAdjustmentGb, string storagePlanId)
{
internal static async Task<string> AdjustStorageAsync(IPaymentService paymentService, IStorableSubscriber storableSubscriber,
short storageAdjustmentGb, string storagePlanId)
if (storableSubscriber == null)
{
if (storableSubscriber == null)
{
throw new ArgumentNullException(nameof(storableSubscriber));
}
if (string.IsNullOrWhiteSpace(storableSubscriber.GatewayCustomerId))
{
throw new BadRequestException("No payment method found.");
}
if (string.IsNullOrWhiteSpace(storableSubscriber.GatewaySubscriptionId))
{
throw new BadRequestException("No subscription found.");
}
if (!storableSubscriber.MaxStorageGb.HasValue)
{
throw new BadRequestException("No access to storage.");
}
var newStorageGb = (short)(storableSubscriber.MaxStorageGb.Value + storageAdjustmentGb);
if (newStorageGb < 1)
{
newStorageGb = 1;
}
if (newStorageGb > 100)
{
throw new BadRequestException("Maximum storage is 100 GB.");
}
var remainingStorage = storableSubscriber.StorageBytesRemaining(newStorageGb);
if (remainingStorage < 0)
{
throw new BadRequestException("You are currently using " +
$"{CoreHelpers.ReadableBytesSize(storableSubscriber.Storage.GetValueOrDefault(0))} of storage. " +
"Delete some stored data first.");
}
var additionalStorage = newStorageGb - 1;
var paymentIntentClientSecret = await paymentService.AdjustStorageAsync(storableSubscriber,
additionalStorage, storagePlanId);
storableSubscriber.MaxStorageGb = newStorageGb;
return paymentIntentClientSecret;
throw new ArgumentNullException(nameof(storableSubscriber));
}
if (string.IsNullOrWhiteSpace(storableSubscriber.GatewayCustomerId))
{
throw new BadRequestException("No payment method found.");
}
if (string.IsNullOrWhiteSpace(storableSubscriber.GatewaySubscriptionId))
{
throw new BadRequestException("No subscription found.");
}
if (!storableSubscriber.MaxStorageGb.HasValue)
{
throw new BadRequestException("No access to storage.");
}
var newStorageGb = (short)(storableSubscriber.MaxStorageGb.Value + storageAdjustmentGb);
if (newStorageGb < 1)
{
newStorageGb = 1;
}
if (newStorageGb > 100)
{
throw new BadRequestException("Maximum storage is 100 GB.");
}
var remainingStorage = storableSubscriber.StorageBytesRemaining(newStorageGb);
if (remainingStorage < 0)
{
throw new BadRequestException("You are currently using " +
$"{CoreHelpers.ReadableBytesSize(storableSubscriber.Storage.GetValueOrDefault(0))} of storage. " +
"Delete some stored data first.");
}
var additionalStorage = newStorageGb - 1;
var paymentIntentClientSecret = await paymentService.AdjustStorageAsync(storableSubscriber,
additionalStorage, storagePlanId);
storableSubscriber.MaxStorageGb = newStorageGb;
return paymentIntentClientSecret;
}
}

View File

@ -1,28 +1,27 @@
using Bit.Core.Settings;
namespace Bit.Core.Utilities
namespace Bit.Core.Utilities;
public class BitPayClient
{
public class BitPayClient
private readonly BitPayLight.BitPay _bpClient;
public BitPayClient(GlobalSettings globalSettings)
{
private readonly BitPayLight.BitPay _bpClient;
public BitPayClient(GlobalSettings globalSettings)
if (CoreHelpers.SettingHasValue(globalSettings.BitPay.Token))
{
if (CoreHelpers.SettingHasValue(globalSettings.BitPay.Token))
{
_bpClient = new BitPayLight.BitPay(globalSettings.BitPay.Token,
globalSettings.BitPay.Production ? BitPayLight.Env.Prod : BitPayLight.Env.Test);
}
}
public Task<BitPayLight.Models.Invoice.Invoice> GetInvoiceAsync(string id)
{
return _bpClient.GetInvoice(id);
}
public Task<BitPayLight.Models.Invoice.Invoice> CreateInvoiceAsync(BitPayLight.Models.Invoice.Invoice invoice)
{
return _bpClient.CreateInvoice(invoice);
_bpClient = new BitPayLight.BitPay(globalSettings.BitPay.Token,
globalSettings.BitPay.Production ? BitPayLight.Env.Prod : BitPayLight.Env.Test);
}
}
public Task<BitPayLight.Models.Invoice.Invoice> GetInvoiceAsync(string id)
{
return _bpClient.GetInvoice(id);
}
public Task<BitPayLight.Models.Invoice.Invoice> CreateInvoiceAsync(BitPayLight.Models.Invoice.Invoice invoice)
{
return _bpClient.CreateInvoice(invoice);
}
}

View File

@ -5,32 +5,31 @@ using Bit.Core.Services;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
namespace Bit.Core.Utilities
namespace Bit.Core.Utilities;
public class CaptchaProtectedAttribute : ActionFilterAttribute
{
public class CaptchaProtectedAttribute : ActionFilterAttribute
public string ModelParameterName { get; set; } = "model";
public override void OnActionExecuting(ActionExecutingContext context)
{
public string ModelParameterName { get; set; } = "model";
var currentContext = context.HttpContext.RequestServices.GetRequiredService<ICurrentContext>();
var captchaValidationService = context.HttpContext.RequestServices.GetRequiredService<ICaptchaValidationService>();
public override void OnActionExecuting(ActionExecutingContext context)
if (captchaValidationService.RequireCaptchaValidation(currentContext, null))
{
var currentContext = context.HttpContext.RequestServices.GetRequiredService<ICurrentContext>();
var captchaValidationService = context.HttpContext.RequestServices.GetRequiredService<ICaptchaValidationService>();
var captchaResponse = (context.ActionArguments[ModelParameterName] as ICaptchaProtectedModel)?.CaptchaResponse;
if (captchaValidationService.RequireCaptchaValidation(currentContext, null))
if (string.IsNullOrWhiteSpace(captchaResponse))
{
var captchaResponse = (context.ActionArguments[ModelParameterName] as ICaptchaProtectedModel)?.CaptchaResponse;
throw new BadRequestException(captchaValidationService.SiteKeyResponseKeyName, captchaValidationService.SiteKey);
}
if (string.IsNullOrWhiteSpace(captchaResponse))
{
throw new BadRequestException(captchaValidationService.SiteKeyResponseKeyName, captchaValidationService.SiteKey);
}
var captchaValidationResponse = captchaValidationService.ValidateCaptchaResponseAsync(captchaResponse,
currentContext.IpAddress, null).GetAwaiter().GetResult();
if (!captchaValidationResponse.Success || captchaValidationResponse.IsBot)
{
throw new BadRequestException("Captcha is invalid. Please refresh and try again");
}
var captchaValidationResponse = captchaValidationService.ValidateCaptchaResponseAsync(captchaResponse,
currentContext.IpAddress, null).GetAwaiter().GetResult();
if (!captchaValidationResponse.Success || captchaValidationResponse.IsBot)
{
throw new BadRequestException("Captcha is invalid. Please refresh and try again");
}
}
}

View File

@ -1,12 +1,11 @@
using System.Security.Claims;
namespace Bit.Core.Utilities
namespace Bit.Core.Utilities;
public static class ClaimsExtensions
{
public static class ClaimsExtensions
public static bool HasSsoIdP(this IEnumerable<Claim> claims)
{
public static bool HasSsoIdP(this IEnumerable<Claim> claims)
{
return claims.Any(c => c.Type == "idp" && c.Value == "sso");
}
return claims.Any(c => c.Type == "idp" && c.Value == "sso");
}
}

File diff suppressed because it is too large Load Diff

View File

@ -2,21 +2,20 @@
using Bit.Core.Settings;
using Microsoft.AspNetCore.Http;
namespace Bit.Core.Utilities
namespace Bit.Core.Utilities;
public class CurrentContextMiddleware
{
public class CurrentContextMiddleware
private readonly RequestDelegate _next;
public CurrentContextMiddleware(RequestDelegate next)
{
private readonly RequestDelegate _next;
_next = next;
}
public CurrentContextMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext, ICurrentContext currentContext, GlobalSettings globalSettings)
{
await currentContext.BuildAsync(httpContext, globalSettings);
await _next.Invoke(httpContext);
}
public async Task Invoke(HttpContext httpContext, ICurrentContext currentContext, GlobalSettings globalSettings)
{
await currentContext.BuildAsync(httpContext, globalSettings);
await _next.Invoke(httpContext);
}
}

View File

@ -6,86 +6,85 @@ using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Bit.Core.Utilities
namespace Bit.Core.Utilities;
public class CustomIpRateLimitMiddleware : IpRateLimitMiddleware
{
public class CustomIpRateLimitMiddleware : IpRateLimitMiddleware
private readonly IBlockIpService _blockIpService;
private readonly ILogger<CustomIpRateLimitMiddleware> _logger;
private readonly IDistributedCache _distributedCache;
private readonly IpRateLimitOptions _options;
public CustomIpRateLimitMiddleware(
IDistributedCache distributedCache,
IBlockIpService blockIpService,
RequestDelegate next,
IProcessingStrategy processingStrategy,
IRateLimitConfiguration rateLimitConfiguration,
IOptions<IpRateLimitOptions> options,
IIpPolicyStore policyStore,
ILogger<CustomIpRateLimitMiddleware> logger)
: base(next, processingStrategy, options, policyStore, rateLimitConfiguration, logger)
{
private readonly IBlockIpService _blockIpService;
private readonly ILogger<CustomIpRateLimitMiddleware> _logger;
private readonly IDistributedCache _distributedCache;
private readonly IpRateLimitOptions _options;
_distributedCache = distributedCache;
_blockIpService = blockIpService;
_options = options.Value;
_logger = logger;
}
public CustomIpRateLimitMiddleware(
IDistributedCache distributedCache,
IBlockIpService blockIpService,
RequestDelegate next,
IProcessingStrategy processingStrategy,
IRateLimitConfiguration rateLimitConfiguration,
IOptions<IpRateLimitOptions> options,
IIpPolicyStore policyStore,
ILogger<CustomIpRateLimitMiddleware> logger)
: base(next, processingStrategy, options, policyStore, rateLimitConfiguration, logger)
public override Task ReturnQuotaExceededResponse(HttpContext httpContext, RateLimitRule rule, string retryAfter)
{
var message = string.IsNullOrWhiteSpace(_options.QuotaExceededMessage)
? $"Slow down! Too many requests. Try again in {rule.Period}."
: _options.QuotaExceededMessage;
httpContext.Response.Headers["Retry-After"] = retryAfter;
httpContext.Response.StatusCode = _options.HttpStatusCode;
var errorModel = new ErrorResponseModel { Message = message };
return httpContext.Response.WriteAsJsonAsync(errorModel, httpContext.RequestAborted);
}
protected override void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity,
RateLimitCounter counter, RateLimitRule rule)
{
base.LogBlockedRequest(httpContext, identity, counter, rule);
var key = $"blockedIp_{identity.ClientIp}";
_distributedCache.TryGetValue(key, out int blockedCount);
blockedCount++;
if (blockedCount > 10)
{
_distributedCache = distributedCache;
_blockIpService = blockIpService;
_options = options.Value;
_logger = logger;
_blockIpService.BlockIpAsync(identity.ClientIp, false);
_logger.LogInformation(Constants.BypassFiltersEventId, null,
"Banned {0}. \nInfo: \n{1}", identity.ClientIp, GetRequestInfo(httpContext));
}
public override Task ReturnQuotaExceededResponse(HttpContext httpContext, RateLimitRule rule, string retryAfter)
else
{
var message = string.IsNullOrWhiteSpace(_options.QuotaExceededMessage)
? $"Slow down! Too many requests. Try again in {rule.Period}."
: _options.QuotaExceededMessage;
httpContext.Response.Headers["Retry-After"] = retryAfter;
httpContext.Response.StatusCode = _options.HttpStatusCode;
var errorModel = new ErrorResponseModel { Message = message };
return httpContext.Response.WriteAsJsonAsync(errorModel, httpContext.RequestAborted);
}
protected override void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity,
RateLimitCounter counter, RateLimitRule rule)
{
base.LogBlockedRequest(httpContext, identity, counter, rule);
var key = $"blockedIp_{identity.ClientIp}";
_distributedCache.TryGetValue(key, out int blockedCount);
blockedCount++;
if (blockedCount > 10)
{
_blockIpService.BlockIpAsync(identity.ClientIp, false);
_logger.LogInformation(Constants.BypassFiltersEventId, null,
"Banned {0}. \nInfo: \n{1}", identity.ClientIp, GetRequestInfo(httpContext));
}
else
{
_logger.LogInformation(Constants.BypassFiltersEventId, null,
"Request blocked {0}. \nInfo: \n{1}", identity.ClientIp, GetRequestInfo(httpContext));
_distributedCache.Set(key, blockedCount,
new DistributedCacheEntryOptions().SetSlidingExpiration(new TimeSpan(0, 5, 0)));
}
}
private string GetRequestInfo(HttpContext httpContext)
{
if (httpContext == null || httpContext.Request == null)
{
return null;
}
var s = string.Empty;
foreach (var header in httpContext.Request.Headers)
{
s += $"Header \"{header.Key}\": {header.Value} \n";
}
foreach (var query in httpContext.Request.Query)
{
s += $"Query \"{query.Key}\": {query.Value} \n";
}
return s;
_logger.LogInformation(Constants.BypassFiltersEventId, null,
"Request blocked {0}. \nInfo: \n{1}", identity.ClientIp, GetRequestInfo(httpContext));
_distributedCache.Set(key, blockedCount,
new DistributedCacheEntryOptions().SetSlidingExpiration(new TimeSpan(0, 5, 0)));
}
}
private string GetRequestInfo(HttpContext httpContext)
{
if (httpContext == null || httpContext.Request == null)
{
return null;
}
var s = string.Empty;
foreach (var header in httpContext.Request.Headers)
{
s += $"Header \"{header.Key}\": {header.Value} \n";
}
foreach (var query in httpContext.Request.Query)
{
s += $"Query \"{query.Key}\": {query.Value} \n";
}
return s;
}
}

View File

@ -1,48 +1,47 @@
using System.Text.Json;
using Microsoft.Extensions.Caching.Distributed;
namespace Bit.Core.Utilities
namespace Bit.Core.Utilities;
public static class DistributedCacheExtensions
{
public static class DistributedCacheExtensions
public static void Set<T>(this IDistributedCache cache, string key, T value)
{
public static void Set<T>(this IDistributedCache cache, string key, T value)
{
Set(cache, key, value, new DistributedCacheEntryOptions());
}
Set(cache, key, value, new DistributedCacheEntryOptions());
}
public static void Set<T>(this IDistributedCache cache, string key, T value,
DistributedCacheEntryOptions options)
{
var bytes = JsonSerializer.SerializeToUtf8Bytes(value);
cache.Set(key, bytes, options);
}
public static void Set<T>(this IDistributedCache cache, string key, T value,
DistributedCacheEntryOptions options)
{
var bytes = JsonSerializer.SerializeToUtf8Bytes(value);
cache.Set(key, bytes, options);
}
public static Task SetAsync<T>(this IDistributedCache cache, string key, T value)
{
return SetAsync(cache, key, value, new DistributedCacheEntryOptions());
}
public static Task SetAsync<T>(this IDistributedCache cache, string key, T value)
{
return SetAsync(cache, key, value, new DistributedCacheEntryOptions());
}
public static Task SetAsync<T>(this IDistributedCache cache, string key, T value,
DistributedCacheEntryOptions options)
{
var bytes = JsonSerializer.SerializeToUtf8Bytes(value);
return cache.SetAsync(key, bytes, options);
}
public static Task SetAsync<T>(this IDistributedCache cache, string key, T value,
DistributedCacheEntryOptions options)
{
var bytes = JsonSerializer.SerializeToUtf8Bytes(value);
return cache.SetAsync(key, bytes, options);
}
public static bool TryGetValue<T>(this IDistributedCache cache, string key, out T value)
public static bool TryGetValue<T>(this IDistributedCache cache, string key, out T value)
{
var val = cache.Get(key);
value = default;
if (val == null) return false;
try
{
var val = cache.Get(key);
value = default;
if (val == null) return false;
try
{
value = JsonSerializer.Deserialize<T>(val);
}
catch
{
return false;
}
return true;
value = JsonSerializer.Deserialize<T>(val);
}
catch
{
return false;
}
return true;
}
}

View File

@ -16,294 +16,293 @@ using System.Text.Json;
using System.Text.RegularExpressions;
using System.Web;
namespace Bit.Core.Utilities.Duo
namespace Bit.Core.Utilities.Duo;
public class DuoApi
{
public class DuoApi
private const string UrlScheme = "https";
private const string UserAgent = "Bitwarden_DuoAPICSharp/1.0 (.NET Core)";
private readonly string _host;
private readonly string _ikey;
private readonly string _skey;
public DuoApi(string ikey, string skey, string host)
{
private const string UrlScheme = "https";
private const string UserAgent = "Bitwarden_DuoAPICSharp/1.0 (.NET Core)";
_ikey = ikey;
_skey = skey;
_host = host;
private readonly string _host;
private readonly string _ikey;
private readonly string _skey;
public DuoApi(string ikey, string skey, string host)
if (!ValidHost(host))
{
_ikey = ikey;
_skey = skey;
_host = host;
throw new DuoException("Invalid Duo host configured.", new ArgumentException(nameof(host)));
}
}
if (!ValidHost(host))
{
throw new DuoException("Invalid Duo host configured.", new ArgumentException(nameof(host)));
}
public static bool ValidHost(string host)
{
if (Uri.TryCreate($"https://{host}", UriKind.Absolute, out var uri))
{
return (string.IsNullOrWhiteSpace(uri.PathAndQuery) || uri.PathAndQuery == "/") &&
uri.Host.StartsWith("api-") &&
(uri.Host.EndsWith(".duosecurity.com") || uri.Host.EndsWith(".duofederal.com"));
}
return false;
}
public static string CanonicalizeParams(Dictionary<string, string> parameters)
{
var ret = new List<string>();
foreach (var pair in parameters)
{
var p = string.Format("{0}={1}", HttpUtility.UrlEncode(pair.Key), HttpUtility.UrlEncode(pair.Value));
// Signatures require upper-case hex digits.
p = Regex.Replace(p, "(%[0-9A-Fa-f][0-9A-Fa-f])", c => c.Value.ToUpperInvariant());
// Escape only the expected characters.
p = Regex.Replace(p, "([!'()*])", c => "%" + Convert.ToByte(c.Value[0]).ToString("X"));
p = p.Replace("%7E", "~");
// UrlEncode converts space (" ") to "+". The
// signature algorithm requires "%20" instead. Actual
// + has already been replaced with %2B.
p = p.Replace("+", "%20");
ret.Add(p);
}
public static bool ValidHost(string host)
ret.Sort(StringComparer.Ordinal);
return string.Join("&", ret.ToArray());
}
protected string CanonicalizeRequest(string method, string path, string canonParams, string date)
{
string[] lines = {
date,
method.ToUpperInvariant(),
_host.ToLower(),
path,
canonParams,
};
return string.Join("\n", lines);
}
public string Sign(string method, string path, string canonParams, string date)
{
var canon = CanonicalizeRequest(method, path, canonParams, date);
var sig = HmacSign(canon);
var auth = string.Concat(_ikey, ':', sig);
return string.Concat("Basic ", Encode64(auth));
}
public string ApiCall(string method, string path, Dictionary<string, string> parameters = null)
{
return ApiCall(method, path, parameters, 0, out var statusCode);
}
/// <param name="timeout">The request timeout, in milliseconds.
/// Specify 0 to use the system-default timeout. Use caution if
/// you choose to specify a custom timeout - some API
/// calls (particularly in the Auth APIs) will not
/// return a response until an out-of-band authentication process
/// has completed. In some cases, this may take as much as a
/// small number of minutes.</param>
public string ApiCall(string method, string path, Dictionary<string, string> parameters, int timeout,
out HttpStatusCode statusCode)
{
if (parameters == null)
{
if (Uri.TryCreate($"https://{host}", UriKind.Absolute, out var uri))
{
return (string.IsNullOrWhiteSpace(uri.PathAndQuery) || uri.PathAndQuery == "/") &&
uri.Host.StartsWith("api-") &&
(uri.Host.EndsWith(".duosecurity.com") || uri.Host.EndsWith(".duofederal.com"));
}
return false;
parameters = new Dictionary<string, string>();
}
public static string CanonicalizeParams(Dictionary<string, string> parameters)
var canonParams = CanonicalizeParams(parameters);
var query = string.Empty;
if (!method.Equals("POST") && !method.Equals("PUT"))
{
var ret = new List<string>();
foreach (var pair in parameters)
if (parameters.Count > 0)
{
var p = string.Format("{0}={1}", HttpUtility.UrlEncode(pair.Key), HttpUtility.UrlEncode(pair.Value));
// Signatures require upper-case hex digits.
p = Regex.Replace(p, "(%[0-9A-Fa-f][0-9A-Fa-f])", c => c.Value.ToUpperInvariant());
// Escape only the expected characters.
p = Regex.Replace(p, "([!'()*])", c => "%" + Convert.ToByte(c.Value[0]).ToString("X"));
p = p.Replace("%7E", "~");
// UrlEncode converts space (" ") to "+". The
// signature algorithm requires "%20" instead. Actual
// + has already been replaced with %2B.
p = p.Replace("+", "%20");
ret.Add(p);
}
ret.Sort(StringComparer.Ordinal);
return string.Join("&", ret.ToArray());
}
protected string CanonicalizeRequest(string method, string path, string canonParams, string date)
{
string[] lines = {
date,
method.ToUpperInvariant(),
_host.ToLower(),
path,
canonParams,
};
return string.Join("\n", lines);
}
public string Sign(string method, string path, string canonParams, string date)
{
var canon = CanonicalizeRequest(method, path, canonParams, date);
var sig = HmacSign(canon);
var auth = string.Concat(_ikey, ':', sig);
return string.Concat("Basic ", Encode64(auth));
}
public string ApiCall(string method, string path, Dictionary<string, string> parameters = null)
{
return ApiCall(method, path, parameters, 0, out var statusCode);
}
/// <param name="timeout">The request timeout, in milliseconds.
/// Specify 0 to use the system-default timeout. Use caution if
/// you choose to specify a custom timeout - some API
/// calls (particularly in the Auth APIs) will not
/// return a response until an out-of-band authentication process
/// has completed. In some cases, this may take as much as a
/// small number of minutes.</param>
public string ApiCall(string method, string path, Dictionary<string, string> parameters, int timeout,
out HttpStatusCode statusCode)
{
if (parameters == null)
{
parameters = new Dictionary<string, string>();
}
var canonParams = CanonicalizeParams(parameters);
var query = string.Empty;
if (!method.Equals("POST") && !method.Equals("PUT"))
{
if (parameters.Count > 0)
{
query = "?" + canonParams;
}
}
var url = string.Format("{0}://{1}{2}{3}", UrlScheme, _host, path, query);
var dateString = RFC822UtcNow();
var auth = Sign(method, path, canonParams, dateString);
var request = (HttpWebRequest)WebRequest.Create(url);
request.Method = method;
request.Accept = "application/json";
request.Headers.Add("Authorization", auth);
request.Headers.Add("X-Duo-Date", dateString);
request.UserAgent = UserAgent;
if (method.Equals("POST") || method.Equals("PUT"))
{
var data = Encoding.UTF8.GetBytes(canonParams);
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = data.Length;
using (var requestStream = request.GetRequestStream())
{
requestStream.Write(data, 0, data.Length);
}
}
if (timeout > 0)
{
request.Timeout = timeout;
}
// Do the request and process the result.
HttpWebResponse response;
try
{
response = (HttpWebResponse)request.GetResponse();
}
catch (WebException ex)
{
response = (HttpWebResponse)ex.Response;
if (response == null)
{
throw;
}
}
using (var reader = new StreamReader(response.GetResponseStream()))
{
statusCode = response.StatusCode;
return reader.ReadToEnd();
query = "?" + canonParams;
}
}
var url = string.Format("{0}://{1}{2}{3}", UrlScheme, _host, path, query);
public T JSONApiCall<T>(string method, string path, Dictionary<string, string> parameters = null)
where T : class
var dateString = RFC822UtcNow();
var auth = Sign(method, path, canonParams, dateString);
var request = (HttpWebRequest)WebRequest.Create(url);
request.Method = method;
request.Accept = "application/json";
request.Headers.Add("Authorization", auth);
request.Headers.Add("X-Duo-Date", dateString);
request.UserAgent = UserAgent;
if (method.Equals("POST") || method.Equals("PUT"))
{
return JSONApiCall<T>(method, path, parameters, 0);
var data = Encoding.UTF8.GetBytes(canonParams);
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = data.Length;
using (var requestStream = request.GetRequestStream())
{
requestStream.Write(data, 0, data.Length);
}
}
if (timeout > 0)
{
request.Timeout = timeout;
}
/// <param name="timeout">The request timeout, in milliseconds.
/// Specify 0 to use the system-default timeout. Use caution if
/// you choose to specify a custom timeout - some API
/// calls (particularly in the Auth APIs) will not
/// return a response until an out-of-band authentication process
/// has completed. In some cases, this may take as much as a
/// small number of minutes.</param>
public T JSONApiCall<T>(string method, string path, Dictionary<string, string> parameters, int timeout)
where T : class
// Do the request and process the result.
HttpWebResponse response;
try
{
var res = ApiCall(method, path, parameters, timeout, out var statusCode);
try
{
// TODO: We should deserialize this into our own DTO and not work on dictionaries.
var dict = JsonSerializer.Deserialize<Dictionary<string, object>>(res);
if (dict["stat"].ToString() == "OK")
{
return JsonSerializer.Deserialize<T>(dict["response"].ToString());
}
var check = ToNullableInt(dict["code"].ToString());
var code = check.GetValueOrDefault(0);
var messageDetail = string.Empty;
if (dict.ContainsKey("message_detail"))
{
messageDetail = dict["message_detail"].ToString();
}
throw new ApiException(code, (int)statusCode, dict["message"].ToString(), messageDetail);
}
catch (ApiException)
response = (HttpWebResponse)request.GetResponse();
}
catch (WebException ex)
{
response = (HttpWebResponse)ex.Response;
if (response == null)
{
throw;
}
catch (Exception e)
{
throw new BadResponseException((int)statusCode, e);
}
}
private int? ToNullableInt(string s)
using (var reader = new StreamReader(response.GetResponseStream()))
{
int i;
if (int.TryParse(s, out i))
{
return i;
}
return null;
}
private string HmacSign(string data)
{
var keyBytes = Encoding.ASCII.GetBytes(_skey);
var dataBytes = Encoding.ASCII.GetBytes(data);
using (var hmac = new HMACSHA1(keyBytes))
{
var hash = hmac.ComputeHash(dataBytes);
var hex = BitConverter.ToString(hash);
return hex.Replace("-", string.Empty).ToLower();
}
}
private static string Encode64(string plaintext)
{
var plaintextBytes = Encoding.ASCII.GetBytes(plaintext);
return Convert.ToBase64String(plaintextBytes);
}
private static string RFC822UtcNow()
{
// Can't use the "zzzz" format because it adds a ":"
// between the offset's hours and minutes.
var dateString = DateTime.UtcNow.ToString("ddd, dd MMM yyyy HH:mm:ss", CultureInfo.InvariantCulture);
var offset = 0;
var zone = "+" + offset.ToString(CultureInfo.InvariantCulture).PadLeft(2, '0');
dateString += " " + zone.PadRight(5, '0');
return dateString;
statusCode = response.StatusCode;
return reader.ReadToEnd();
}
}
public class DuoException : Exception
public T JSONApiCall<T>(string method, string path, Dictionary<string, string> parameters = null)
where T : class
{
public int HttpStatus { get; private set; }
public DuoException(string message, Exception inner)
: base(message, inner)
{ }
public DuoException(int httpStatus, string message, Exception inner)
: base(message, inner)
{
HttpStatus = httpStatus;
}
return JSONApiCall<T>(method, path, parameters, 0);
}
public class ApiException : DuoException
/// <param name="timeout">The request timeout, in milliseconds.
/// Specify 0 to use the system-default timeout. Use caution if
/// you choose to specify a custom timeout - some API
/// calls (particularly in the Auth APIs) will not
/// return a response until an out-of-band authentication process
/// has completed. In some cases, this may take as much as a
/// small number of minutes.</param>
public T JSONApiCall<T>(string method, string path, Dictionary<string, string> parameters, int timeout)
where T : class
{
public int Code { get; private set; }
public string ApiMessage { get; private set; }
public string ApiMessageDetail { get; private set; }
public ApiException(int code, int httpStatus, string apiMessage, string apiMessageDetail)
: base(httpStatus, FormatMessage(code, apiMessage, apiMessageDetail), null)
var res = ApiCall(method, path, parameters, timeout, out var statusCode);
try
{
Code = code;
ApiMessage = apiMessage;
ApiMessageDetail = apiMessageDetail;
}
private static string FormatMessage(int code, string apiMessage, string apiMessageDetail)
{
return string.Format("Duo API Error {0}: '{1}' ('{2}')", code, apiMessage, apiMessageDetail);
}
}
public class BadResponseException : DuoException
{
public BadResponseException(int httpStatus, Exception inner)
: base(httpStatus, FormatMessage(httpStatus, inner), inner)
{ }
private static string FormatMessage(int httpStatus, Exception inner)
{
var innerMessage = "(null)";
if (inner != null)
// TODO: We should deserialize this into our own DTO and not work on dictionaries.
var dict = JsonSerializer.Deserialize<Dictionary<string, object>>(res);
if (dict["stat"].ToString() == "OK")
{
innerMessage = string.Format("'{0}'", inner.Message);
return JsonSerializer.Deserialize<T>(dict["response"].ToString());
}
return string.Format("Got error {0} with HTTP Status {1}", innerMessage, httpStatus);
var check = ToNullableInt(dict["code"].ToString());
var code = check.GetValueOrDefault(0);
var messageDetail = string.Empty;
if (dict.ContainsKey("message_detail"))
{
messageDetail = dict["message_detail"].ToString();
}
throw new ApiException(code, (int)statusCode, dict["message"].ToString(), messageDetail);
}
catch (ApiException)
{
throw;
}
catch (Exception e)
{
throw new BadResponseException((int)statusCode, e);
}
}
private int? ToNullableInt(string s)
{
int i;
if (int.TryParse(s, out i))
{
return i;
}
return null;
}
private string HmacSign(string data)
{
var keyBytes = Encoding.ASCII.GetBytes(_skey);
var dataBytes = Encoding.ASCII.GetBytes(data);
using (var hmac = new HMACSHA1(keyBytes))
{
var hash = hmac.ComputeHash(dataBytes);
var hex = BitConverter.ToString(hash);
return hex.Replace("-", string.Empty).ToLower();
}
}
private static string Encode64(string plaintext)
{
var plaintextBytes = Encoding.ASCII.GetBytes(plaintext);
return Convert.ToBase64String(plaintextBytes);
}
private static string RFC822UtcNow()
{
// Can't use the "zzzz" format because it adds a ":"
// between the offset's hours and minutes.
var dateString = DateTime.UtcNow.ToString("ddd, dd MMM yyyy HH:mm:ss", CultureInfo.InvariantCulture);
var offset = 0;
var zone = "+" + offset.ToString(CultureInfo.InvariantCulture).PadLeft(2, '0');
dateString += " " + zone.PadRight(5, '0');
return dateString;
}
}
public class DuoException : Exception
{
public int HttpStatus { get; private set; }
public DuoException(string message, Exception inner)
: base(message, inner)
{ }
public DuoException(int httpStatus, string message, Exception inner)
: base(message, inner)
{
HttpStatus = httpStatus;
}
}
public class ApiException : DuoException
{
public int Code { get; private set; }
public string ApiMessage { get; private set; }
public string ApiMessageDetail { get; private set; }
public ApiException(int code, int httpStatus, string apiMessage, string apiMessageDetail)
: base(httpStatus, FormatMessage(code, apiMessage, apiMessageDetail), null)
{
Code = code;
ApiMessage = apiMessage;
ApiMessageDetail = apiMessageDetail;
}
private static string FormatMessage(int code, string apiMessage, string apiMessageDetail)
{
return string.Format("Duo API Error {0}: '{1}' ('{2}')", code, apiMessage, apiMessageDetail);
}
}
public class BadResponseException : DuoException
{
public BadResponseException(int httpStatus, Exception inner)
: base(httpStatus, FormatMessage(httpStatus, inner), inner)
{ }
private static string FormatMessage(int httpStatus, Exception inner)
{
var innerMessage = "(null)";
if (inner != null)
{
innerMessage = string.Format("'{0}'", inner.Message);
}
return string.Format("Got error {0} with HTTP Status {1}", innerMessage, httpStatus);
}
}

View File

@ -36,206 +36,205 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
using System.Security.Cryptography;
using System.Text;
namespace Bit.Core.Utilities.Duo
namespace Bit.Core.Utilities.Duo;
public static class DuoWeb
{
public static class DuoWeb
private const string DuoProfix = "TX";
private const string AppPrefix = "APP";
private const string AuthPrefix = "AUTH";
private const int DuoExpire = 300;
private const int AppExpire = 3600;
private const int IKeyLength = 20;
private const int SKeyLength = 40;
private const int AKeyLength = 40;
public static string ErrorUser = "ERR|The username passed to sign_request() is invalid.";
public static string ErrorIKey = "ERR|The Duo integration key passed to sign_request() is invalid.";
public static string ErrorSKey = "ERR|The Duo secret key passed to sign_request() is invalid.";
public static string ErrorAKey = "ERR|The application secret key passed to sign_request() must be at least " +
"40 characters.";
public static string ErrorUnknown = "ERR|An unknown error has occurred.";
// throw on invalid bytes
private static Encoding _encoding = new UTF8Encoding(false, true);
private static DateTime _epoc = new DateTime(1970, 1, 1);
/// <summary>
/// Generate a signed request for Duo authentication.
/// The returned value should be passed into the Duo.init() call
/// in the rendered web page used for Duo authentication.
/// </summary>
/// <param name="ikey">Duo integration key</param>
/// <param name="skey">Duo secret key</param>
/// <param name="akey">Application secret key</param>
/// <param name="username">Primary-authenticated username</param>
/// <param name="currentTime">(optional) The current UTC time</param>
/// <returns>signed request</returns>
public static string SignRequest(string ikey, string skey, string akey, string username,
DateTime? currentTime = null)
{
private const string DuoProfix = "TX";
private const string AppPrefix = "APP";
private const string AuthPrefix = "AUTH";
private const int DuoExpire = 300;
private const int AppExpire = 3600;
private const int IKeyLength = 20;
private const int SKeyLength = 40;
private const int AKeyLength = 40;
string duoSig;
string appSig;
public static string ErrorUser = "ERR|The username passed to sign_request() is invalid.";
public static string ErrorIKey = "ERR|The Duo integration key passed to sign_request() is invalid.";
public static string ErrorSKey = "ERR|The Duo secret key passed to sign_request() is invalid.";
public static string ErrorAKey = "ERR|The application secret key passed to sign_request() must be at least " +
"40 characters.";
public static string ErrorUnknown = "ERR|An unknown error has occurred.";
var currentTimeValue = currentTime ?? DateTime.UtcNow;
// throw on invalid bytes
private static Encoding _encoding = new UTF8Encoding(false, true);
private static DateTime _epoc = new DateTime(1970, 1, 1);
/// <summary>
/// Generate a signed request for Duo authentication.
/// The returned value should be passed into the Duo.init() call
/// in the rendered web page used for Duo authentication.
/// </summary>
/// <param name="ikey">Duo integration key</param>
/// <param name="skey">Duo secret key</param>
/// <param name="akey">Application secret key</param>
/// <param name="username">Primary-authenticated username</param>
/// <param name="currentTime">(optional) The current UTC time</param>
/// <returns>signed request</returns>
public static string SignRequest(string ikey, string skey, string akey, string username,
DateTime? currentTime = null)
if (username == string.Empty)
{
string duoSig;
string appSig;
var currentTimeValue = currentTime ?? DateTime.UtcNow;
if (username == string.Empty)
{
return ErrorUser;
}
if (username.Contains("|"))
{
return ErrorUser;
}
if (ikey.Length != IKeyLength)
{
return ErrorIKey;
}
if (skey.Length != SKeyLength)
{
return ErrorSKey;
}
if (akey.Length < AKeyLength)
{
return ErrorAKey;
}
try
{
duoSig = SignVals(skey, username, ikey, DuoProfix, DuoExpire, currentTimeValue);
appSig = SignVals(akey, username, ikey, AppPrefix, AppExpire, currentTimeValue);
}
catch
{
return ErrorUnknown;
}
return $"{duoSig}:{appSig}";
return ErrorUser;
}
if (username.Contains("|"))
{
return ErrorUser;
}
if (ikey.Length != IKeyLength)
{
return ErrorIKey;
}
if (skey.Length != SKeyLength)
{
return ErrorSKey;
}
if (akey.Length < AKeyLength)
{
return ErrorAKey;
}
/// <summary>
/// Validate the signed response returned from Duo.
/// Returns the username of the authenticated user, or null.
/// </summary>
/// <param name="ikey">Duo integration key</param>
/// <param name="skey">Duo secret key</param>
/// <param name="akey">Application secret key</param>
/// <param name="sigResponse">The signed response POST'ed to the server</param>
/// <param name="currentTime">(optional) The current UTC time</param>
/// <returns>authenticated username, or null</returns>
public static string VerifyResponse(string ikey, string skey, string akey, string sigResponse,
DateTime? currentTime = null)
try
{
string authUser = null;
string appUser = null;
var currentTimeValue = currentTime ?? DateTime.UtcNow;
try
{
var sigs = sigResponse.Split(':');
var authSig = sigs[0];
var appSig = sigs[1];
authUser = ParseVals(skey, authSig, AuthPrefix, ikey, currentTimeValue);
appUser = ParseVals(akey, appSig, AppPrefix, ikey, currentTimeValue);
}
catch
{
return null;
}
if (authUser != appUser)
{
return null;
}
return authUser;
duoSig = SignVals(skey, username, ikey, DuoProfix, DuoExpire, currentTimeValue);
appSig = SignVals(akey, username, ikey, AppPrefix, AppExpire, currentTimeValue);
}
catch
{
return ErrorUnknown;
}
private static string SignVals(string key, string username, string ikey, string prefix, long expire,
DateTime currentTime)
return $"{duoSig}:{appSig}";
}
/// <summary>
/// Validate the signed response returned from Duo.
/// Returns the username of the authenticated user, or null.
/// </summary>
/// <param name="ikey">Duo integration key</param>
/// <param name="skey">Duo secret key</param>
/// <param name="akey">Application secret key</param>
/// <param name="sigResponse">The signed response POST'ed to the server</param>
/// <param name="currentTime">(optional) The current UTC time</param>
/// <returns>authenticated username, or null</returns>
public static string VerifyResponse(string ikey, string skey, string akey, string sigResponse,
DateTime? currentTime = null)
{
string authUser = null;
string appUser = null;
var currentTimeValue = currentTime ?? DateTime.UtcNow;
try
{
var ts = (long)(currentTime - _epoc).TotalSeconds;
expire = ts + expire;
var val = $"{username}|{ikey}|{expire.ToString()}";
var cookie = $"{prefix}|{Encode64(val)}";
var sig = Sign(key, cookie);
return $"{cookie}|{sig}";
var sigs = sigResponse.Split(':');
var authSig = sigs[0];
var appSig = sigs[1];
authUser = ParseVals(skey, authSig, AuthPrefix, ikey, currentTimeValue);
appUser = ParseVals(akey, appSig, AppPrefix, ikey, currentTimeValue);
}
catch
{
return null;
}
private static string ParseVals(string key, string val, string prefix, string ikey, DateTime currentTime)
if (authUser != appUser)
{
var ts = (long)(currentTime - _epoc).TotalSeconds;
var parts = val.Split('|');
if (parts.Length != 3)
{
return null;
}
var uPrefix = parts[0];
var uB64 = parts[1];
var uSig = parts[2];
var sig = Sign(key, $"{uPrefix}|{uB64}");
if (Sign(key, sig) != Sign(key, uSig))
{
return null;
}
if (uPrefix != prefix)
{
return null;
}
var cookie = Decode64(uB64);
var cookieParts = cookie.Split('|');
if (cookieParts.Length != 3)
{
return null;
}
var username = cookieParts[0];
var uIKey = cookieParts[1];
var expire = cookieParts[2];
if (uIKey != ikey)
{
return null;
}
var expireTs = Convert.ToInt32(expire);
if (ts >= expireTs)
{
return null;
}
return username;
return null;
}
private static string Sign(string skey, string data)
{
var keyBytes = Encoding.ASCII.GetBytes(skey);
var dataBytes = Encoding.ASCII.GetBytes(data);
return authUser;
}
using (var hmac = new HMACSHA1(keyBytes))
{
var hash = hmac.ComputeHash(dataBytes);
var hex = BitConverter.ToString(hash);
return hex.Replace("-", "").ToLower();
}
private static string SignVals(string key, string username, string ikey, string prefix, long expire,
DateTime currentTime)
{
var ts = (long)(currentTime - _epoc).TotalSeconds;
expire = ts + expire;
var val = $"{username}|{ikey}|{expire.ToString()}";
var cookie = $"{prefix}|{Encode64(val)}";
var sig = Sign(key, cookie);
return $"{cookie}|{sig}";
}
private static string ParseVals(string key, string val, string prefix, string ikey, DateTime currentTime)
{
var ts = (long)(currentTime - _epoc).TotalSeconds;
var parts = val.Split('|');
if (parts.Length != 3)
{
return null;
}
private static string Encode64(string plaintext)
var uPrefix = parts[0];
var uB64 = parts[1];
var uSig = parts[2];
var sig = Sign(key, $"{uPrefix}|{uB64}");
if (Sign(key, sig) != Sign(key, uSig))
{
var plaintextBytes = _encoding.GetBytes(plaintext);
return Convert.ToBase64String(plaintextBytes);
return null;
}
private static string Decode64(string encoded)
if (uPrefix != prefix)
{
var plaintextBytes = Convert.FromBase64String(encoded);
return _encoding.GetString(plaintextBytes);
return null;
}
var cookie = Decode64(uB64);
var cookieParts = cookie.Split('|');
if (cookieParts.Length != 3)
{
return null;
}
var username = cookieParts[0];
var uIKey = cookieParts[1];
var expire = cookieParts[2];
if (uIKey != ikey)
{
return null;
}
var expireTs = Convert.ToInt32(expire);
if (ts >= expireTs)
{
return null;
}
return username;
}
private static string Sign(string skey, string data)
{
var keyBytes = Encoding.ASCII.GetBytes(skey);
var dataBytes = Encoding.ASCII.GetBytes(data);
using (var hmac = new HMACSHA1(keyBytes))
{
var hash = hmac.ComputeHash(dataBytes);
var hex = BitConverter.ToString(hash);
return hex.Replace("-", "").ToLower();
}
}
private static string Encode64(string plaintext)
{
var plaintextBytes = _encoding.GetBytes(plaintext);
return Convert.ToBase64String(plaintextBytes);
}
private static string Decode64(string encoded)
{
var plaintextBytes = Convert.FromBase64String(encoded);
return _encoding.GetString(plaintextBytes);
}
}

View File

@ -1,17 +1,16 @@
using System.ComponentModel.DataAnnotations;
namespace Bit.Core.Utilities
{
public class EncryptedStringLengthAttribute : StringLengthAttribute
{
public EncryptedStringLengthAttribute(int maximumLength)
: base(maximumLength)
{ }
namespace Bit.Core.Utilities;
public override string FormatErrorMessage(string name)
{
return string.Format("The field {0} exceeds the maximum encrypted value length of {1} characters.",
name, MaximumLength);
}
public class EncryptedStringLengthAttribute : StringLengthAttribute
{
public EncryptedStringLengthAttribute(int maximumLength)
: base(maximumLength)
{ }
public override string FormatErrorMessage(string name)
{
return string.Format("The field {0} exceeds the maximum encrypted value length of {1} characters.",
name, MaximumLength);
}
}

View File

@ -1,138 +1,137 @@
using System.ComponentModel.DataAnnotations;
namespace Bit.Core.Utilities
namespace Bit.Core.Utilities;
/// <summary>
/// Validates a string that is in encrypted form: "head.b64iv=|b64ct=|b64mac="
/// </summary>
public class EncryptedStringAttribute : ValidationAttribute
{
/// <summary>
/// Validates a string that is in encrypted form: "head.b64iv=|b64ct=|b64mac="
/// </summary>
public class EncryptedStringAttribute : ValidationAttribute
public EncryptedStringAttribute()
: base("{0} is not a valid encrypted string.")
{ }
public override bool IsValid(object value)
{
public EncryptedStringAttribute()
: base("{0} is not a valid encrypted string.")
{ }
public override bool IsValid(object value)
if (value == null)
{
if (value == null)
{
return true;
}
return true;
}
try
{
var encString = value?.ToString();
if (string.IsNullOrWhiteSpace(encString))
{
return false;
}
var headerPieces = encString.Split('.');
string[] encStringPieces = null;
var encType = Enums.EncryptionType.AesCbc256_B64;
if (headerPieces.Length == 1)
{
encStringPieces = headerPieces[0].Split('|');
if (encStringPieces.Length == 3)
{
encType = Enums.EncryptionType.AesCbc128_HmacSha256_B64;
}
else
{
encType = Enums.EncryptionType.AesCbc256_B64;
}
}
else if (headerPieces.Length == 2)
{
encStringPieces = headerPieces[1].Split('|');
if (!Enum.TryParse(headerPieces[0], out encType))
{
return false;
}
}
switch (encType)
{
case Enums.EncryptionType.AesCbc256_B64:
case Enums.EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64:
case Enums.EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64:
if (encStringPieces.Length != 2)
{
return false;
}
break;
case Enums.EncryptionType.AesCbc128_HmacSha256_B64:
case Enums.EncryptionType.AesCbc256_HmacSha256_B64:
if (encStringPieces.Length != 3)
{
return false;
}
break;
case Enums.EncryptionType.Rsa2048_OaepSha256_B64:
case Enums.EncryptionType.Rsa2048_OaepSha1_B64:
if (encStringPieces.Length != 1)
{
return false;
}
break;
default:
return false;
}
switch (encType)
{
case Enums.EncryptionType.AesCbc256_B64:
case Enums.EncryptionType.AesCbc128_HmacSha256_B64:
case Enums.EncryptionType.AesCbc256_HmacSha256_B64:
var iv = Convert.FromBase64String(encStringPieces[0]);
var ct = Convert.FromBase64String(encStringPieces[1]);
if (iv.Length < 1 || ct.Length < 1)
{
return false;
}
if (encType == Enums.EncryptionType.AesCbc128_HmacSha256_B64 ||
encType == Enums.EncryptionType.AesCbc256_HmacSha256_B64)
{
var mac = Convert.FromBase64String(encStringPieces[2]);
if (mac.Length < 1)
{
return false;
}
}
break;
case Enums.EncryptionType.Rsa2048_OaepSha256_B64:
case Enums.EncryptionType.Rsa2048_OaepSha1_B64:
case Enums.EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64:
case Enums.EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64:
var rsaCt = Convert.FromBase64String(encStringPieces[0]);
if (rsaCt.Length < 1)
{
return false;
}
if (encType == Enums.EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64 ||
encType == Enums.EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64)
{
var mac = Convert.FromBase64String(encStringPieces[1]);
if (mac.Length < 1)
{
return false;
}
}
break;
default:
return false;
}
}
catch
try
{
var encString = value?.ToString();
if (string.IsNullOrWhiteSpace(encString))
{
return false;
}
return true;
var headerPieces = encString.Split('.');
string[] encStringPieces = null;
var encType = Enums.EncryptionType.AesCbc256_B64;
if (headerPieces.Length == 1)
{
encStringPieces = headerPieces[0].Split('|');
if (encStringPieces.Length == 3)
{
encType = Enums.EncryptionType.AesCbc128_HmacSha256_B64;
}
else
{
encType = Enums.EncryptionType.AesCbc256_B64;
}
}
else if (headerPieces.Length == 2)
{
encStringPieces = headerPieces[1].Split('|');
if (!Enum.TryParse(headerPieces[0], out encType))
{
return false;
}
}
switch (encType)
{
case Enums.EncryptionType.AesCbc256_B64:
case Enums.EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64:
case Enums.EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64:
if (encStringPieces.Length != 2)
{
return false;
}
break;
case Enums.EncryptionType.AesCbc128_HmacSha256_B64:
case Enums.EncryptionType.AesCbc256_HmacSha256_B64:
if (encStringPieces.Length != 3)
{
return false;
}
break;
case Enums.EncryptionType.Rsa2048_OaepSha256_B64:
case Enums.EncryptionType.Rsa2048_OaepSha1_B64:
if (encStringPieces.Length != 1)
{
return false;
}
break;
default:
return false;
}
switch (encType)
{
case Enums.EncryptionType.AesCbc256_B64:
case Enums.EncryptionType.AesCbc128_HmacSha256_B64:
case Enums.EncryptionType.AesCbc256_HmacSha256_B64:
var iv = Convert.FromBase64String(encStringPieces[0]);
var ct = Convert.FromBase64String(encStringPieces[1]);
if (iv.Length < 1 || ct.Length < 1)
{
return false;
}
if (encType == Enums.EncryptionType.AesCbc128_HmacSha256_B64 ||
encType == Enums.EncryptionType.AesCbc256_HmacSha256_B64)
{
var mac = Convert.FromBase64String(encStringPieces[2]);
if (mac.Length < 1)
{
return false;
}
}
break;
case Enums.EncryptionType.Rsa2048_OaepSha256_B64:
case Enums.EncryptionType.Rsa2048_OaepSha1_B64:
case Enums.EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64:
case Enums.EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64:
var rsaCt = Convert.FromBase64String(encStringPieces[0]);
if (rsaCt.Length < 1)
{
return false;
}
if (encType == Enums.EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64 ||
encType == Enums.EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64)
{
var mac = Convert.FromBase64String(encStringPieces[1]);
if (mac.Length < 1)
{
return false;
}
}
break;
default:
return false;
}
}
catch
{
return false;
}
return true;
}
}

View File

@ -1,17 +1,16 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Bit.Core.Utilities
namespace Bit.Core.Utilities;
public class EpochDateTimeJsonConverter : JsonConverter<DateTime>
{
public class EpochDateTimeJsonConverter : JsonConverter<DateTime>
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return CoreHelpers.FromEpocMilliseconds(reader.GetInt64());
}
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
writer.WriteNumberValue(CoreHelpers.ToEpocMilliseconds(value));
}
return CoreHelpers.FromEpocMilliseconds(reader.GetInt64());
}
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
writer.WriteNumberValue(CoreHelpers.ToEpocMilliseconds(value));
}
}

View File

@ -1,18 +1,17 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Bit.Core.Utilities
namespace Bit.Core.Utilities;
public class HandlebarsObjectJsonConverter : JsonConverter<object>
{
public class HandlebarsObjectJsonConverter : JsonConverter<object>
public override bool CanConvert(Type typeToConvert) => true;
public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
public override bool CanConvert(Type typeToConvert) => true;
public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return JsonSerializer.Deserialize<Dictionary<string, object>>(ref reader, options);
}
public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
{
JsonSerializer.Serialize(writer, value, options);
}
return JsonSerializer.Deserialize<Dictionary<string, object>>(ref reader, options);
}
public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
{
JsonSerializer.Serialize(writer, value, options);
}
}

View File

@ -2,42 +2,41 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
namespace Bit.Core.Utilities
namespace Bit.Core.Utilities;
public static class HostBuilderExtensions
{
public static class HostBuilderExtensions
public static IHostBuilder ConfigureCustomAppConfiguration(this IHostBuilder hostBuilder, string[] args)
{
public static IHostBuilder ConfigureCustomAppConfiguration(this IHostBuilder hostBuilder, string[] args)
// Reload app configuration with SelfHosted overrides.
return hostBuilder.ConfigureAppConfiguration((hostingContext, config) =>
{
// Reload app configuration with SelfHosted overrides.
return hostBuilder.ConfigureAppConfiguration((hostingContext, config) =>
if (Environment.GetEnvironmentVariable("globalSettings__selfHosted")?.ToLower() != "true")
{
if (Environment.GetEnvironmentVariable("globalSettings__selfHosted")?.ToLower() != "true")
return;
}
var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true)
.AddJsonFile("appsettings.SelfHosted.json", optional: true, reloadOnChange: true);
if (env.IsDevelopment())
{
var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
if (appAssembly != null)
{
return;
config.AddUserSecrets(appAssembly, optional: true);
}
}
var env = hostingContext.HostingEnvironment;
config.AddEnvironmentVariables();
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true)
.AddJsonFile("appsettings.SelfHosted.json", optional: true, reloadOnChange: true);
if (env.IsDevelopment())
{
var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
if (appAssembly != null)
{
config.AddUserSecrets(appAssembly, optional: true);
}
}
config.AddEnvironmentVariables();
if (args != null)
{
config.AddCommandLine(args);
}
});
}
if (args != null)
{
config.AddCommandLine(args);
}
});
}
}

View File

@ -3,205 +3,204 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using NS = Newtonsoft.Json;
namespace Bit.Core.Utilities
namespace Bit.Core.Utilities;
public static class JsonHelpers
{
public static class JsonHelpers
public static JsonSerializerOptions Default { get; }
public static JsonSerializerOptions Indented { get; }
public static JsonSerializerOptions IgnoreCase { get; }
public static JsonSerializerOptions IgnoreWritingNull { get; }
public static JsonSerializerOptions CamelCase { get; }
public static JsonSerializerOptions IgnoreWritingNullAndCamelCase { get; }
static JsonHelpers()
{
public static JsonSerializerOptions Default { get; }
public static JsonSerializerOptions Indented { get; }
public static JsonSerializerOptions IgnoreCase { get; }
public static JsonSerializerOptions IgnoreWritingNull { get; }
public static JsonSerializerOptions CamelCase { get; }
public static JsonSerializerOptions IgnoreWritingNullAndCamelCase { get; }
Default = new JsonSerializerOptions();
static JsonHelpers()
Indented = new JsonSerializerOptions
{
Default = new JsonSerializerOptions();
Indented = new JsonSerializerOptions
{
WriteIndented = true,
};
IgnoreCase = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
};
IgnoreWritingNull = new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};
CamelCase = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
IgnoreWritingNullAndCamelCase = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};
}
[Obsolete("This is built into .NET 6, it SHOULD be removed when we upgrade")]
public static T ToObject<T>(this JsonElement element, JsonSerializerOptions options = null)
{
return JsonSerializer.Deserialize<T>(element.GetRawText(), options ?? Default);
}
[Obsolete("This is built into .NET 6, it SHOULD be removed when we upgrade")]
public static T ToObject<T>(this JsonDocument document, JsonSerializerOptions options = null)
{
return JsonSerializer.Deserialize<T>(document.RootElement.GetRawText(), options ?? default);
}
public static T DeserializeOrNew<T>(string json, JsonSerializerOptions options = null)
where T : new()
{
if (string.IsNullOrWhiteSpace(json))
{
return new T();
}
return JsonSerializer.Deserialize<T>(json, options);
}
#region Legacy Newtonsoft.Json usage
private const string LegacyMessage = "Usage of Newtonsoft.Json should be kept to a minimum and will further be removed when we move to .NET 6";
[Obsolete(LegacyMessage)]
public static NS.JsonSerializerSettings LegacyEnumKeyResolver { get; } = new NS.JsonSerializerSettings
{
ContractResolver = new EnumKeyResolver<byte>(),
WriteIndented = true,
};
[Obsolete(LegacyMessage)]
public static string LegacySerialize(object value, NS.JsonSerializerSettings settings = null)
IgnoreCase = new JsonSerializerOptions
{
return NS.JsonConvert.SerializeObject(value, settings);
}
PropertyNameCaseInsensitive = true,
};
[Obsolete(LegacyMessage)]
public static T LegacyDeserialize<T>(string value, NS.JsonSerializerSettings settings = null)
IgnoreWritingNull = new JsonSerializerOptions
{
return NS.JsonConvert.DeserializeObject<T>(value, settings);
}
#endregion
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};
CamelCase = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
IgnoreWritingNullAndCamelCase = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};
}
public class EnumKeyResolver<T> : NS.Serialization.DefaultContractResolver
where T : struct
[Obsolete("This is built into .NET 6, it SHOULD be removed when we upgrade")]
public static T ToObject<T>(this JsonElement element, JsonSerializerOptions options = null)
{
protected override NS.Serialization.JsonDictionaryContract CreateDictionaryContract(Type objectType)
{
var contract = base.CreateDictionaryContract(objectType);
var keyType = contract.DictionaryKeyType;
if (keyType.BaseType == typeof(Enum))
{
contract.DictionaryKeyResolver = propName => ((T)Enum.Parse(keyType, propName)).ToString();
}
return contract;
}
return JsonSerializer.Deserialize<T>(element.GetRawText(), options ?? Default);
}
public class MsEpochConverter : JsonConverter<DateTime?>
[Obsolete("This is built into .NET 6, it SHOULD be removed when we upgrade")]
public static T ToObject<T>(this JsonDocument document, JsonSerializerOptions options = null)
{
public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null)
{
return null;
}
if (!long.TryParse(reader.GetString(), out var milliseconds))
{
return null;
}
return CoreHelpers.FromEpocMilliseconds(milliseconds);
}
public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options)
{
if (!value.HasValue)
{
writer.WriteNullValue();
}
writer.WriteStringValue(CoreHelpers.ToEpocMilliseconds(value.Value).ToString());
}
return JsonSerializer.Deserialize<T>(document.RootElement.GetRawText(), options ?? default);
}
/// <summary>
/// Allows reading a string from a JSON number or string, should only be used on <see cref="string" /> properties
/// </summary>
public class PermissiveStringConverter : JsonConverter<string>
public static T DeserializeOrNew<T>(string json, JsonSerializerOptions options = null)
where T : new()
{
internal static readonly PermissiveStringConverter Instance = new();
private static readonly CultureInfo _cultureInfo = new("en-US");
public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
if (string.IsNullOrWhiteSpace(json))
{
return reader.TokenType switch
{
JsonTokenType.String => reader.GetString(),
JsonTokenType.Number => reader.GetDecimal().ToString(_cultureInfo),
JsonTokenType.True => bool.TrueString,
JsonTokenType.False => bool.FalseString,
_ => throw new JsonException($"Unsupported TokenType: {reader.TokenType}"),
};
return new T();
}
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
{
writer.WriteStringValue(value);
}
return JsonSerializer.Deserialize<T>(json, options);
}
/// <summary>
/// Allows reading a JSON array of number or string, should only be used on <see cref="IEnumerable{T}" /> whose generic type is <see cref="string" />
/// </summary>
public class PermissiveStringEnumerableConverter : JsonConverter<IEnumerable<string>>
#region Legacy Newtonsoft.Json usage
private const string LegacyMessage = "Usage of Newtonsoft.Json should be kept to a minimum and will further be removed when we move to .NET 6";
[Obsolete(LegacyMessage)]
public static NS.JsonSerializerSettings LegacyEnumKeyResolver { get; } = new NS.JsonSerializerSettings
{
public override IEnumerable<string> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
ContractResolver = new EnumKeyResolver<byte>(),
};
[Obsolete(LegacyMessage)]
public static string LegacySerialize(object value, NS.JsonSerializerSettings settings = null)
{
return NS.JsonConvert.SerializeObject(value, settings);
}
[Obsolete(LegacyMessage)]
public static T LegacyDeserialize<T>(string value, NS.JsonSerializerSettings settings = null)
{
return NS.JsonConvert.DeserializeObject<T>(value, settings);
}
#endregion
}
public class EnumKeyResolver<T> : NS.Serialization.DefaultContractResolver
where T : struct
{
protected override NS.Serialization.JsonDictionaryContract CreateDictionaryContract(Type objectType)
{
var contract = base.CreateDictionaryContract(objectType);
var keyType = contract.DictionaryKeyType;
if (keyType.BaseType == typeof(Enum))
{
var stringList = new List<string>();
contract.DictionaryKeyResolver = propName => ((T)Enum.Parse(keyType, propName)).ToString();
}
// Handle special cases or throw
if (reader.TokenType != JsonTokenType.StartArray)
return contract;
}
}
public class MsEpochConverter : JsonConverter<DateTime?>
{
public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null)
{
return null;
}
if (!long.TryParse(reader.GetString(), out var milliseconds))
{
return null;
}
return CoreHelpers.FromEpocMilliseconds(milliseconds);
}
public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options)
{
if (!value.HasValue)
{
writer.WriteNullValue();
}
writer.WriteStringValue(CoreHelpers.ToEpocMilliseconds(value.Value).ToString());
}
}
/// <summary>
/// Allows reading a string from a JSON number or string, should only be used on <see cref="string" /> properties
/// </summary>
public class PermissiveStringConverter : JsonConverter<string>
{
internal static readonly PermissiveStringConverter Instance = new();
private static readonly CultureInfo _cultureInfo = new("en-US");
public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return reader.TokenType switch
{
JsonTokenType.String => reader.GetString(),
JsonTokenType.Number => reader.GetDecimal().ToString(_cultureInfo),
JsonTokenType.True => bool.TrueString,
JsonTokenType.False => bool.FalseString,
_ => throw new JsonException($"Unsupported TokenType: {reader.TokenType}"),
};
}
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
{
writer.WriteStringValue(value);
}
}
/// <summary>
/// Allows reading a JSON array of number or string, should only be used on <see cref="IEnumerable{T}" /> whose generic type is <see cref="string" />
/// </summary>
public class PermissiveStringEnumerableConverter : JsonConverter<IEnumerable<string>>
{
public override IEnumerable<string> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var stringList = new List<string>();
// Handle special cases or throw
if (reader.TokenType != JsonTokenType.StartArray)
{
// An array was expected but to be extra permissive allow reading from anything other than an object
if (reader.TokenType == JsonTokenType.StartObject)
{
// An array was expected but to be extra permissive allow reading from anything other than an object
if (reader.TokenType == JsonTokenType.StartObject)
{
throw new JsonException("Cannot read JSON Object to an IEnumerable<string>.");
}
stringList.Add(PermissiveStringConverter.Instance.Read(ref reader, typeof(string), options));
return stringList;
}
while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)
{
stringList.Add(PermissiveStringConverter.Instance.Read(ref reader, typeof(string), options));
throw new JsonException("Cannot read JSON Object to an IEnumerable<string>.");
}
stringList.Add(PermissiveStringConverter.Instance.Read(ref reader, typeof(string), options));
return stringList;
}
public override void Write(Utf8JsonWriter writer, IEnumerable<string> value, JsonSerializerOptions options)
while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)
{
writer.WriteStartArray();
foreach (var str in value)
{
PermissiveStringConverter.Instance.Write(writer, str, options);
}
writer.WriteEndArray();
stringList.Add(PermissiveStringConverter.Instance.Read(ref reader, typeof(string), options));
}
return stringList;
}
public override void Write(Utf8JsonWriter writer, IEnumerable<string> value, JsonSerializerOptions options)
{
writer.WriteStartArray();
foreach (var str in value)
{
PermissiveStringConverter.Instance.Write(writer, str, options);
}
writer.WriteEndArray();
}
}

View File

@ -10,137 +10,136 @@ using Serilog;
using Serilog.Events;
using Serilog.Sinks.Syslog;
namespace Bit.Core.Utilities
{
public static class LoggerFactoryExtensions
{
public static void UseSerilog(
this IApplicationBuilder appBuilder,
IWebHostEnvironment env,
IHostApplicationLifetime applicationLifetime,
GlobalSettings globalSettings)
{
if (env.IsDevelopment())
{
return;
}
namespace Bit.Core.Utilities;
applicationLifetime.ApplicationStopped.Register(Log.CloseAndFlush);
public static class LoggerFactoryExtensions
{
public static void UseSerilog(
this IApplicationBuilder appBuilder,
IWebHostEnvironment env,
IHostApplicationLifetime applicationLifetime,
GlobalSettings globalSettings)
{
if (env.IsDevelopment())
{
return;
}
public static ILoggingBuilder AddSerilog(
this ILoggingBuilder builder,
WebHostBuilderContext context,
Func<LogEvent, bool> filter = null)
applicationLifetime.ApplicationStopped.Register(Log.CloseAndFlush);
}
public static ILoggingBuilder AddSerilog(
this ILoggingBuilder builder,
WebHostBuilderContext context,
Func<LogEvent, bool> filter = null)
{
if (context.HostingEnvironment.IsDevelopment())
{
if (context.HostingEnvironment.IsDevelopment())
{
return builder;
}
bool inclusionPredicate(LogEvent e)
{
if (filter == null)
{
return true;
}
var eventId = e.Properties.ContainsKey("EventId") ? e.Properties["EventId"].ToString() : null;
if (eventId?.Contains(Constants.BypassFiltersEventId.ToString()) ?? false)
{
return true;
}
return filter(e);
}
var globalSettings = new GlobalSettings();
ConfigurationBinder.Bind(context.Configuration.GetSection("GlobalSettings"), globalSettings);
var config = new LoggerConfiguration()
.Enrich.FromLogContext()
.Filter.ByIncludingOnly(inclusionPredicate);
if (CoreHelpers.SettingHasValue(globalSettings?.DocumentDb.Uri) &&
CoreHelpers.SettingHasValue(globalSettings?.DocumentDb.Key))
{
config.WriteTo.AzureCosmosDB(new Uri(globalSettings.DocumentDb.Uri),
globalSettings.DocumentDb.Key, timeToLive: TimeSpan.FromDays(7),
partitionKey: "_partitionKey")
.Enrich.FromLogContext()
.Enrich.WithProperty("Project", globalSettings.ProjectName);
}
else if (CoreHelpers.SettingHasValue(globalSettings?.Sentry.Dsn))
{
config.WriteTo.Sentry(globalSettings.Sentry.Dsn)
.Enrich.FromLogContext()
.Enrich.WithProperty("Project", globalSettings.ProjectName);
}
else if (CoreHelpers.SettingHasValue(globalSettings?.Syslog.Destination))
{
// appending sitename to project name to allow eaiser identification in syslog.
var appName = $"{globalSettings.SiteName}-{globalSettings.ProjectName}";
if (globalSettings.Syslog.Destination.Equals("local", StringComparison.OrdinalIgnoreCase))
{
config.WriteTo.LocalSyslog(appName);
}
else if (Uri.TryCreate(globalSettings.Syslog.Destination, UriKind.Absolute, out var syslogAddress))
{
// Syslog's standard port is 514 (both UDP and TCP). TLS does not have a standard port, so assume 514.
int port = syslogAddress.Port >= 0
? syslogAddress.Port
: 514;
if (syslogAddress.Scheme.Equals("udp"))
{
config.WriteTo.UdpSyslog(syslogAddress.Host, port, appName);
}
else if (syslogAddress.Scheme.Equals("tcp"))
{
config.WriteTo.TcpSyslog(syslogAddress.Host, port, appName);
}
else if (syslogAddress.Scheme.Equals("tls"))
{
// TLS v1.1, v1.2 and v1.3 are explicitly selected (leaving out TLS v1.0)
const SslProtocols protocols = SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Tls13;
if (CoreHelpers.SettingHasValue(globalSettings.Syslog.CertificateThumbprint))
{
config.WriteTo.TcpSyslog(syslogAddress.Host, port, appName,
secureProtocols: protocols,
certProvider: new CertificateStoreProvider(StoreName.My, StoreLocation.CurrentUser,
globalSettings.Syslog.CertificateThumbprint));
}
else
{
config.WriteTo.TcpSyslog(syslogAddress.Host, port, appName,
secureProtocols: protocols,
certProvider: new CertificateFileProvider(globalSettings.Syslog.CertificatePath,
globalSettings.Syslog?.CertificatePassword ?? string.Empty));
}
}
}
}
else if (CoreHelpers.SettingHasValue(globalSettings.LogDirectory))
{
if (globalSettings.LogRollBySizeLimit.HasValue)
{
config.WriteTo.File($"{globalSettings.LogDirectory}/{globalSettings.ProjectName}/log.txt",
rollOnFileSizeLimit: true, fileSizeLimitBytes: globalSettings.LogRollBySizeLimit);
}
else
{
config.WriteTo
.RollingFile($"{globalSettings.LogDirectory}/{globalSettings.ProjectName}/{{Date}}.txt");
}
config
.Enrich.FromLogContext()
.Enrich.WithProperty("Project", globalSettings.ProjectName);
}
var serilog = config.CreateLogger();
builder.AddSerilog(serilog);
return builder;
}
bool inclusionPredicate(LogEvent e)
{
if (filter == null)
{
return true;
}
var eventId = e.Properties.ContainsKey("EventId") ? e.Properties["EventId"].ToString() : null;
if (eventId?.Contains(Constants.BypassFiltersEventId.ToString()) ?? false)
{
return true;
}
return filter(e);
}
var globalSettings = new GlobalSettings();
ConfigurationBinder.Bind(context.Configuration.GetSection("GlobalSettings"), globalSettings);
var config = new LoggerConfiguration()
.Enrich.FromLogContext()
.Filter.ByIncludingOnly(inclusionPredicate);
if (CoreHelpers.SettingHasValue(globalSettings?.DocumentDb.Uri) &&
CoreHelpers.SettingHasValue(globalSettings?.DocumentDb.Key))
{
config.WriteTo.AzureCosmosDB(new Uri(globalSettings.DocumentDb.Uri),
globalSettings.DocumentDb.Key, timeToLive: TimeSpan.FromDays(7),
partitionKey: "_partitionKey")
.Enrich.FromLogContext()
.Enrich.WithProperty("Project", globalSettings.ProjectName);
}
else if (CoreHelpers.SettingHasValue(globalSettings?.Sentry.Dsn))
{
config.WriteTo.Sentry(globalSettings.Sentry.Dsn)
.Enrich.FromLogContext()
.Enrich.WithProperty("Project", globalSettings.ProjectName);
}
else if (CoreHelpers.SettingHasValue(globalSettings?.Syslog.Destination))
{
// appending sitename to project name to allow eaiser identification in syslog.
var appName = $"{globalSettings.SiteName}-{globalSettings.ProjectName}";
if (globalSettings.Syslog.Destination.Equals("local", StringComparison.OrdinalIgnoreCase))
{
config.WriteTo.LocalSyslog(appName);
}
else if (Uri.TryCreate(globalSettings.Syslog.Destination, UriKind.Absolute, out var syslogAddress))
{
// Syslog's standard port is 514 (both UDP and TCP). TLS does not have a standard port, so assume 514.
int port = syslogAddress.Port >= 0
? syslogAddress.Port
: 514;
if (syslogAddress.Scheme.Equals("udp"))
{
config.WriteTo.UdpSyslog(syslogAddress.Host, port, appName);
}
else if (syslogAddress.Scheme.Equals("tcp"))
{
config.WriteTo.TcpSyslog(syslogAddress.Host, port, appName);
}
else if (syslogAddress.Scheme.Equals("tls"))
{
// TLS v1.1, v1.2 and v1.3 are explicitly selected (leaving out TLS v1.0)
const SslProtocols protocols = SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Tls13;
if (CoreHelpers.SettingHasValue(globalSettings.Syslog.CertificateThumbprint))
{
config.WriteTo.TcpSyslog(syslogAddress.Host, port, appName,
secureProtocols: protocols,
certProvider: new CertificateStoreProvider(StoreName.My, StoreLocation.CurrentUser,
globalSettings.Syslog.CertificateThumbprint));
}
else
{
config.WriteTo.TcpSyslog(syslogAddress.Host, port, appName,
secureProtocols: protocols,
certProvider: new CertificateFileProvider(globalSettings.Syslog.CertificatePath,
globalSettings.Syslog?.CertificatePassword ?? string.Empty));
}
}
}
}
else if (CoreHelpers.SettingHasValue(globalSettings.LogDirectory))
{
if (globalSettings.LogRollBySizeLimit.HasValue)
{
config.WriteTo.File($"{globalSettings.LogDirectory}/{globalSettings.ProjectName}/log.txt",
rollOnFileSizeLimit: true, fileSizeLimitBytes: globalSettings.LogRollBySizeLimit);
}
else
{
config.WriteTo
.RollingFile($"{globalSettings.LogDirectory}/{globalSettings.ProjectName}/{{Date}}.txt");
}
config
.Enrich.FromLogContext()
.Enrich.WithProperty("Project", globalSettings.ProjectName);
}
var serilog = config.CreateLogger();
builder.AddSerilog(serilog);
return builder;
}
}

View File

@ -2,22 +2,21 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Bit.Core.Utilities
{
public class LoggingExceptionHandlerFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
var exception = context.Exception;
if (exception == null)
{
// Should never happen.
return;
}
namespace Bit.Core.Utilities;
var logger = context.HttpContext.RequestServices
.GetRequiredService<ILogger<LoggingExceptionHandlerFilterAttribute>>();
logger.LogError(0, exception, exception.Message);
public class LoggingExceptionHandlerFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
var exception = context.Exception;
if (exception == null)
{
// Should never happen.
return;
}
var logger = context.HttpContext.RequestServices
.GetRequiredService<ILogger<LoggingExceptionHandlerFilterAttribute>>();
logger.LogError(0, exception, exception.Message);
}
}

View File

@ -1,29 +1,28 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
namespace Bit.Core.Utilities
namespace Bit.Core.Utilities;
public sealed class SecurityHeadersMiddleware
{
public sealed class SecurityHeadersMiddleware
private readonly RequestDelegate _next;
public SecurityHeadersMiddleware(RequestDelegate next)
{
private readonly RequestDelegate _next;
_next = next;
}
public SecurityHeadersMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext context)
{
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
context.Response.Headers.Add("x-frame-options", new StringValues("SAMEORIGIN"));
public Task Invoke(HttpContext context)
{
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
context.Response.Headers.Add("x-frame-options", new StringValues("SAMEORIGIN"));
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
context.Response.Headers.Add("x-xss-protection", new StringValues("1; mode=block"));
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
context.Response.Headers.Add("x-xss-protection", new StringValues("1; mode=block"));
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
context.Response.Headers.Add("x-content-type-options", new StringValues("nosniff"));
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
context.Response.Headers.Add("x-content-type-options", new StringValues("nosniff"));
return _next(context);
}
return _next(context);
}
}

View File

@ -3,24 +3,23 @@ using Bit.Core.Settings;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
namespace Bit.Core.Utilities
{
public class SelfHostedAttribute : ActionFilterAttribute
{
public bool SelfHostedOnly { get; set; }
public bool NotSelfHostedOnly { get; set; }
namespace Bit.Core.Utilities;
public override void OnActionExecuting(ActionExecutingContext context)
public class SelfHostedAttribute : ActionFilterAttribute
{
public bool SelfHostedOnly { get; set; }
public bool NotSelfHostedOnly { get; set; }
public override void OnActionExecuting(ActionExecutingContext context)
{
var globalSettings = context.HttpContext.RequestServices.GetRequiredService<GlobalSettings>();
if (SelfHostedOnly && !globalSettings.SelfHosted)
{
var globalSettings = context.HttpContext.RequestServices.GetRequiredService<GlobalSettings>();
if (SelfHostedOnly && !globalSettings.SelfHosted)
{
throw new BadRequestException("Only allowed when self hosted.");
}
else if (NotSelfHostedOnly && globalSettings.SelfHosted)
{
throw new BadRequestException("Only allowed when not self hosted.");
}
throw new BadRequestException("Only allowed when self hosted.");
}
else if (NotSelfHostedOnly && globalSettings.SelfHosted)
{
throw new BadRequestException("Only allowed when not self hosted.");
}
}
}

View File

@ -2,501 +2,500 @@
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Models.StaticStore;
namespace Bit.Core.Utilities
namespace Bit.Core.Utilities;
public class StaticStore
{
public class StaticStore
static StaticStore()
{
static StaticStore()
#region Global Domains
GlobalDomains = new Dictionary<GlobalEquivalentDomainsType, IEnumerable<string>>();
GlobalDomains.Add(GlobalEquivalentDomainsType.Ameritrade, new List<string> { "ameritrade.com", "tdameritrade.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.BoA, new List<string> { "bankofamerica.com", "bofa.com", "mbna.com", "usecfo.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Sprint, new List<string> { "sprint.com", "sprintpcs.com", "nextel.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Google, new List<string> { "youtube.com", "google.com", "gmail.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Apple, new List<string> { "apple.com", "icloud.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.WellsFargo, new List<string> { "wellsfargo.com", "wf.com", "wellsfargoadvisors.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Merrill, new List<string> { "mymerrill.com", "ml.com", "merrilledge.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Citi, new List<string> { "accountonline.com", "citi.com", "citibank.com", "citicards.com", "citibankonline.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Cnet, new List<string> { "cnet.com", "cnettv.com", "com.com", "download.com", "news.com", "search.com", "upload.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Gap, new List<string> { "bananarepublic.com", "gap.com", "oldnavy.com", "piperlime.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Microsoft, new List<string> { "bing.com", "hotmail.com", "live.com", "microsoft.com", "msn.com", "passport.net", "windows.com", "microsoftonline.com", "office.com", "office365.com", "microsoftstore.com", "xbox.com", "azure.com", "windowsazure.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.United, new List<string> { "ua2go.com", "ual.com", "united.com", "unitedwifi.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Yahoo, new List<string> { "overture.com", "yahoo.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Zonelabs, new List<string> { "zonealarm.com", "zonelabs.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.PayPal, new List<string> { "paypal.com", "paypal-search.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Avon, new List<string> { "avon.com", "youravon.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Diapers, new List<string> { "diapers.com", "soap.com", "wag.com", "yoyo.com", "beautybar.com", "casa.com", "afterschool.com", "vine.com", "bookworm.com", "look.com", "vinemarket.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Contacts, new List<string> { "1800contacts.com", "800contacts.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Amazon, new List<string> { "amazon.com", "amazon.ae", "amazon.ca", "amazon.co.uk", "amazon.com.au", "amazon.com.br", "amazon.com.mx", "amazon.com.tr", "amazon.de", "amazon.es", "amazon.fr", "amazon.in", "amazon.it", "amazon.nl", "amazon.pl", "amazon.sa", "amazon.se", "amazon.sg" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Cox, new List<string> { "cox.com", "cox.net", "coxbusiness.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Norton, new List<string> { "mynortonaccount.com", "norton.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Verizon, new List<string> { "verizon.com", "verizon.net" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Buy, new List<string> { "rakuten.com", "buy.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Sirius, new List<string> { "siriusxm.com", "sirius.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Ea, new List<string> { "ea.com", "origin.com", "play4free.com", "tiberiumalliance.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Basecamp, new List<string> { "37signals.com", "basecamp.com", "basecamphq.com", "highrisehq.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Steam, new List<string> { "steampowered.com", "steamcommunity.com", "steamgames.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Chart, new List<string> { "chart.io", "chartio.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Gotomeeting, new List<string> { "gotomeeting.com", "citrixonline.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Gogo, new List<string> { "gogoair.com", "gogoinflight.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Oracle, new List<string> { "mysql.com", "oracle.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Discover, new List<string> { "discover.com", "discovercard.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Dcu, new List<string> { "dcu.org", "dcu-online.org" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Healthcare, new List<string> { "healthcare.gov", "cuidadodesalud.gov", "cms.gov" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Pepco, new List<string> { "pepco.com", "pepcoholdings.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Century21, new List<string> { "century21.com", "21online.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Comcast, new List<string> { "comcast.com", "comcast.net", "xfinity.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Cricket, new List<string> { "cricketwireless.com", "aiowireless.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Mtb, new List<string> { "mandtbank.com", "mtb.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Dropbox, new List<string> { "dropbox.com", "getdropbox.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Snapfish, new List<string> { "snapfish.com", "snapfish.ca" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Alibaba, new List<string> { "alibaba.com", "aliexpress.com", "aliyun.com", "net.cn" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Playstation, new List<string> { "playstation.com", "sonyentertainmentnetwork.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Mercado, new List<string> { "mercadolivre.com", "mercadolivre.com.br", "mercadolibre.com", "mercadolibre.com.ar", "mercadolibre.com.mx" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Zendesk, new List<string> { "zendesk.com", "zopim.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Autodesk, new List<string> { "autodesk.com", "tinkercad.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.RailNation, new List<string> { "railnation.ru", "railnation.de", "rail-nation.com", "railnation.gr", "railnation.us", "trucknation.de", "traviangames.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Wpcu, new List<string> { "wpcu.coop", "wpcuonline.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Mathletics, new List<string> { "mathletics.com", "mathletics.com.au", "mathletics.co.uk" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Discountbank, new List<string> { "discountbank.co.il", "telebank.co.il" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Mi, new List<string> { "mi.com", "xiaomi.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Postepay, new List<string> { "postepay.it", "poste.it" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Facebook, new List<string> { "facebook.com", "messenger.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Skysports, new List<string> { "skysports.com", "skybet.com", "skyvegas.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Disney, new List<string> { "disneymoviesanywhere.com", "go.com", "disney.com", "dadt.com", "disneyplus.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Pokemon, new List<string> { "pokemon-gl.com", "pokemon.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Uv, new List<string> { "myuv.com", "uvvu.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Mdsol, new List<string> { "mdsol.com", "imedidata.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Yahavo, new List<string> { "bank-yahav.co.il", "bankhapoalim.co.il" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Sears, new List<string> { "sears.com", "shld.net" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Xiami, new List<string> { "xiami.com", "alipay.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Belkin, new List<string> { "belkin.com", "seedonk.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Turbotax, new List<string> { "turbotax.com", "intuit.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Shopify, new List<string> { "shopify.com", "myshopify.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Ebay, new List<string> { "ebay.com", "ebay.at", "ebay.be", "ebay.ca", "ebay.ch", "ebay.cn", "ebay.co.jp", "ebay.co.th", "ebay.co.uk", "ebay.com.au", "ebay.com.hk", "ebay.com.my", "ebay.com.sg", "ebay.com.tw", "ebay.de", "ebay.es", "ebay.fr", "ebay.ie", "ebay.in", "ebay.it", "ebay.nl", "ebay.ph", "ebay.pl" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Techdata, new List<string> { "techdata.com", "techdata.ch" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Schwab, new List<string> { "schwab.com", "schwabplan.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Tesla, new List<string> { "tesla.com", "teslamotors.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.MorganStanley, new List<string> { "morganstanley.com", "morganstanleyclientserv.com", "stockplanconnect.com", "ms.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.TaxAct, new List<string> { "taxact.com", "taxactonline.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Wikimedia, new List<string> { "mediawiki.org", "wikibooks.org", "wikidata.org", "wikimedia.org", "wikinews.org", "wikipedia.org", "wikiquote.org", "wikisource.org", "wikiversity.org", "wikivoyage.org", "wiktionary.org" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Airbnb, new List<string> { "airbnb.at", "airbnb.be", "airbnb.ca", "airbnb.ch", "airbnb.cl", "airbnb.co.cr", "airbnb.co.id", "airbnb.co.in", "airbnb.co.kr", "airbnb.co.nz", "airbnb.co.uk", "airbnb.co.ve", "airbnb.com", "airbnb.com.ar", "airbnb.com.au", "airbnb.com.bo", "airbnb.com.br", "airbnb.com.bz", "airbnb.com.co", "airbnb.com.ec", "airbnb.com.gt", "airbnb.com.hk", "airbnb.com.hn", "airbnb.com.mt", "airbnb.com.my", "airbnb.com.ni", "airbnb.com.pa", "airbnb.com.pe", "airbnb.com.py", "airbnb.com.sg", "airbnb.com.sv", "airbnb.com.tr", "airbnb.com.tw", "airbnb.cz", "airbnb.de", "airbnb.dk", "airbnb.es", "airbnb.fi", "airbnb.fr", "airbnb.gr", "airbnb.gy", "airbnb.hu", "airbnb.ie", "airbnb.is", "airbnb.it", "airbnb.jp", "airbnb.mx", "airbnb.nl", "airbnb.no", "airbnb.pl", "airbnb.pt", "airbnb.ru", "airbnb.se" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Eventbrite, new List<string> { "eventbrite.at", "eventbrite.be", "eventbrite.ca", "eventbrite.ch", "eventbrite.cl", "eventbrite.co", "eventbrite.co.nz", "eventbrite.co.uk", "eventbrite.com", "eventbrite.com.ar", "eventbrite.com.au", "eventbrite.com.br", "eventbrite.com.mx", "eventbrite.com.pe", "eventbrite.de", "eventbrite.dk", "eventbrite.es", "eventbrite.fi", "eventbrite.fr", "eventbrite.hk", "eventbrite.ie", "eventbrite.it", "eventbrite.nl", "eventbrite.pt", "eventbrite.se", "eventbrite.sg" });
GlobalDomains.Add(GlobalEquivalentDomainsType.StackExchange, new List<string> { "stackexchange.com", "superuser.com", "stackoverflow.com", "serverfault.com", "mathoverflow.net", "askubuntu.com", "stackapps.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Docusign, new List<string> { "docusign.com", "docusign.net" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Envato, new List<string> { "envato.com", "themeforest.net", "codecanyon.net", "videohive.net", "audiojungle.net", "graphicriver.net", "photodune.net", "3docean.net" });
GlobalDomains.Add(GlobalEquivalentDomainsType.X10Hosting, new List<string> { "x10hosting.com", "x10premium.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Cisco, new List<string> { "dnsomatic.com", "opendns.com", "umbrella.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.CedarFair, new List<string> { "cagreatamerica.com", "canadaswonderland.com", "carowinds.com", "cedarfair.com", "cedarpoint.com", "dorneypark.com", "kingsdominion.com", "knotts.com", "miadventure.com", "schlitterbahn.com", "valleyfair.com", "visitkingsisland.com", "worldsoffun.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Ubiquiti, new List<string> { "ubnt.com", "ui.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Discord, new List<string> { "discordapp.com", "discord.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Netcup, new List<string> { "netcup.de", "netcup.eu", "customercontrolpanel.de" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Yandex, new List<string> { "yandex.com", "ya.ru", "yandex.az", "yandex.by", "yandex.co.il", "yandex.com.am", "yandex.com.ge", "yandex.com.tr", "yandex.ee", "yandex.fi", "yandex.fr", "yandex.kg", "yandex.kz", "yandex.lt", "yandex.lv", "yandex.md", "yandex.pl", "yandex.ru", "yandex.tj", "yandex.tm", "yandex.ua", "yandex.uz" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Sony, new List<string> { "sonyentertainmentnetwork.com", "sony.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Proton, new List<string> { "proton.me", "protonmail.com", "protonvpn.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Ubisoft, new List<string> { "ubisoft.com", "ubi.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.TransferWise, new List<string> { "transferwise.com", "wise.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.TakeawayEU, new List<string> { "takeaway.com", "just-eat.dk", "just-eat.no", "just-eat.fr", "just-eat.ch", "lieferando.de", "lieferando.at", "thuisbezorgd.nl", "pyszne.pl" });
#endregion
#region Plans
Plans = new List<Plan>
{
#region Global Domains
GlobalDomains = new Dictionary<GlobalEquivalentDomainsType, IEnumerable<string>>();
GlobalDomains.Add(GlobalEquivalentDomainsType.Ameritrade, new List<string> { "ameritrade.com", "tdameritrade.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.BoA, new List<string> { "bankofamerica.com", "bofa.com", "mbna.com", "usecfo.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Sprint, new List<string> { "sprint.com", "sprintpcs.com", "nextel.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Google, new List<string> { "youtube.com", "google.com", "gmail.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Apple, new List<string> { "apple.com", "icloud.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.WellsFargo, new List<string> { "wellsfargo.com", "wf.com", "wellsfargoadvisors.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Merrill, new List<string> { "mymerrill.com", "ml.com", "merrilledge.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Citi, new List<string> { "accountonline.com", "citi.com", "citibank.com", "citicards.com", "citibankonline.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Cnet, new List<string> { "cnet.com", "cnettv.com", "com.com", "download.com", "news.com", "search.com", "upload.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Gap, new List<string> { "bananarepublic.com", "gap.com", "oldnavy.com", "piperlime.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Microsoft, new List<string> { "bing.com", "hotmail.com", "live.com", "microsoft.com", "msn.com", "passport.net", "windows.com", "microsoftonline.com", "office.com", "office365.com", "microsoftstore.com", "xbox.com", "azure.com", "windowsazure.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.United, new List<string> { "ua2go.com", "ual.com", "united.com", "unitedwifi.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Yahoo, new List<string> { "overture.com", "yahoo.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Zonelabs, new List<string> { "zonealarm.com", "zonelabs.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.PayPal, new List<string> { "paypal.com", "paypal-search.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Avon, new List<string> { "avon.com", "youravon.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Diapers, new List<string> { "diapers.com", "soap.com", "wag.com", "yoyo.com", "beautybar.com", "casa.com", "afterschool.com", "vine.com", "bookworm.com", "look.com", "vinemarket.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Contacts, new List<string> { "1800contacts.com", "800contacts.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Amazon, new List<string> { "amazon.com", "amazon.ae", "amazon.ca", "amazon.co.uk", "amazon.com.au", "amazon.com.br", "amazon.com.mx", "amazon.com.tr", "amazon.de", "amazon.es", "amazon.fr", "amazon.in", "amazon.it", "amazon.nl", "amazon.pl", "amazon.sa", "amazon.se", "amazon.sg" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Cox, new List<string> { "cox.com", "cox.net", "coxbusiness.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Norton, new List<string> { "mynortonaccount.com", "norton.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Verizon, new List<string> { "verizon.com", "verizon.net" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Buy, new List<string> { "rakuten.com", "buy.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Sirius, new List<string> { "siriusxm.com", "sirius.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Ea, new List<string> { "ea.com", "origin.com", "play4free.com", "tiberiumalliance.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Basecamp, new List<string> { "37signals.com", "basecamp.com", "basecamphq.com", "highrisehq.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Steam, new List<string> { "steampowered.com", "steamcommunity.com", "steamgames.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Chart, new List<string> { "chart.io", "chartio.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Gotomeeting, new List<string> { "gotomeeting.com", "citrixonline.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Gogo, new List<string> { "gogoair.com", "gogoinflight.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Oracle, new List<string> { "mysql.com", "oracle.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Discover, new List<string> { "discover.com", "discovercard.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Dcu, new List<string> { "dcu.org", "dcu-online.org" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Healthcare, new List<string> { "healthcare.gov", "cuidadodesalud.gov", "cms.gov" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Pepco, new List<string> { "pepco.com", "pepcoholdings.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Century21, new List<string> { "century21.com", "21online.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Comcast, new List<string> { "comcast.com", "comcast.net", "xfinity.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Cricket, new List<string> { "cricketwireless.com", "aiowireless.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Mtb, new List<string> { "mandtbank.com", "mtb.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Dropbox, new List<string> { "dropbox.com", "getdropbox.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Snapfish, new List<string> { "snapfish.com", "snapfish.ca" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Alibaba, new List<string> { "alibaba.com", "aliexpress.com", "aliyun.com", "net.cn" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Playstation, new List<string> { "playstation.com", "sonyentertainmentnetwork.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Mercado, new List<string> { "mercadolivre.com", "mercadolivre.com.br", "mercadolibre.com", "mercadolibre.com.ar", "mercadolibre.com.mx" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Zendesk, new List<string> { "zendesk.com", "zopim.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Autodesk, new List<string> { "autodesk.com", "tinkercad.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.RailNation, new List<string> { "railnation.ru", "railnation.de", "rail-nation.com", "railnation.gr", "railnation.us", "trucknation.de", "traviangames.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Wpcu, new List<string> { "wpcu.coop", "wpcuonline.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Mathletics, new List<string> { "mathletics.com", "mathletics.com.au", "mathletics.co.uk" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Discountbank, new List<string> { "discountbank.co.il", "telebank.co.il" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Mi, new List<string> { "mi.com", "xiaomi.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Postepay, new List<string> { "postepay.it", "poste.it" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Facebook, new List<string> { "facebook.com", "messenger.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Skysports, new List<string> { "skysports.com", "skybet.com", "skyvegas.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Disney, new List<string> { "disneymoviesanywhere.com", "go.com", "disney.com", "dadt.com", "disneyplus.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Pokemon, new List<string> { "pokemon-gl.com", "pokemon.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Uv, new List<string> { "myuv.com", "uvvu.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Mdsol, new List<string> { "mdsol.com", "imedidata.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Yahavo, new List<string> { "bank-yahav.co.il", "bankhapoalim.co.il" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Sears, new List<string> { "sears.com", "shld.net" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Xiami, new List<string> { "xiami.com", "alipay.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Belkin, new List<string> { "belkin.com", "seedonk.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Turbotax, new List<string> { "turbotax.com", "intuit.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Shopify, new List<string> { "shopify.com", "myshopify.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Ebay, new List<string> { "ebay.com", "ebay.at", "ebay.be", "ebay.ca", "ebay.ch", "ebay.cn", "ebay.co.jp", "ebay.co.th", "ebay.co.uk", "ebay.com.au", "ebay.com.hk", "ebay.com.my", "ebay.com.sg", "ebay.com.tw", "ebay.de", "ebay.es", "ebay.fr", "ebay.ie", "ebay.in", "ebay.it", "ebay.nl", "ebay.ph", "ebay.pl" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Techdata, new List<string> { "techdata.com", "techdata.ch" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Schwab, new List<string> { "schwab.com", "schwabplan.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Tesla, new List<string> { "tesla.com", "teslamotors.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.MorganStanley, new List<string> { "morganstanley.com", "morganstanleyclientserv.com", "stockplanconnect.com", "ms.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.TaxAct, new List<string> { "taxact.com", "taxactonline.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Wikimedia, new List<string> { "mediawiki.org", "wikibooks.org", "wikidata.org", "wikimedia.org", "wikinews.org", "wikipedia.org", "wikiquote.org", "wikisource.org", "wikiversity.org", "wikivoyage.org", "wiktionary.org" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Airbnb, new List<string> { "airbnb.at", "airbnb.be", "airbnb.ca", "airbnb.ch", "airbnb.cl", "airbnb.co.cr", "airbnb.co.id", "airbnb.co.in", "airbnb.co.kr", "airbnb.co.nz", "airbnb.co.uk", "airbnb.co.ve", "airbnb.com", "airbnb.com.ar", "airbnb.com.au", "airbnb.com.bo", "airbnb.com.br", "airbnb.com.bz", "airbnb.com.co", "airbnb.com.ec", "airbnb.com.gt", "airbnb.com.hk", "airbnb.com.hn", "airbnb.com.mt", "airbnb.com.my", "airbnb.com.ni", "airbnb.com.pa", "airbnb.com.pe", "airbnb.com.py", "airbnb.com.sg", "airbnb.com.sv", "airbnb.com.tr", "airbnb.com.tw", "airbnb.cz", "airbnb.de", "airbnb.dk", "airbnb.es", "airbnb.fi", "airbnb.fr", "airbnb.gr", "airbnb.gy", "airbnb.hu", "airbnb.ie", "airbnb.is", "airbnb.it", "airbnb.jp", "airbnb.mx", "airbnb.nl", "airbnb.no", "airbnb.pl", "airbnb.pt", "airbnb.ru", "airbnb.se" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Eventbrite, new List<string> { "eventbrite.at", "eventbrite.be", "eventbrite.ca", "eventbrite.ch", "eventbrite.cl", "eventbrite.co", "eventbrite.co.nz", "eventbrite.co.uk", "eventbrite.com", "eventbrite.com.ar", "eventbrite.com.au", "eventbrite.com.br", "eventbrite.com.mx", "eventbrite.com.pe", "eventbrite.de", "eventbrite.dk", "eventbrite.es", "eventbrite.fi", "eventbrite.fr", "eventbrite.hk", "eventbrite.ie", "eventbrite.it", "eventbrite.nl", "eventbrite.pt", "eventbrite.se", "eventbrite.sg" });
GlobalDomains.Add(GlobalEquivalentDomainsType.StackExchange, new List<string> { "stackexchange.com", "superuser.com", "stackoverflow.com", "serverfault.com", "mathoverflow.net", "askubuntu.com", "stackapps.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Docusign, new List<string> { "docusign.com", "docusign.net" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Envato, new List<string> { "envato.com", "themeforest.net", "codecanyon.net", "videohive.net", "audiojungle.net", "graphicriver.net", "photodune.net", "3docean.net" });
GlobalDomains.Add(GlobalEquivalentDomainsType.X10Hosting, new List<string> { "x10hosting.com", "x10premium.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Cisco, new List<string> { "dnsomatic.com", "opendns.com", "umbrella.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.CedarFair, new List<string> { "cagreatamerica.com", "canadaswonderland.com", "carowinds.com", "cedarfair.com", "cedarpoint.com", "dorneypark.com", "kingsdominion.com", "knotts.com", "miadventure.com", "schlitterbahn.com", "valleyfair.com", "visitkingsisland.com", "worldsoffun.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Ubiquiti, new List<string> { "ubnt.com", "ui.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Discord, new List<string> { "discordapp.com", "discord.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Netcup, new List<string> { "netcup.de", "netcup.eu", "customercontrolpanel.de" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Yandex, new List<string> { "yandex.com", "ya.ru", "yandex.az", "yandex.by", "yandex.co.il", "yandex.com.am", "yandex.com.ge", "yandex.com.tr", "yandex.ee", "yandex.fi", "yandex.fr", "yandex.kg", "yandex.kz", "yandex.lt", "yandex.lv", "yandex.md", "yandex.pl", "yandex.ru", "yandex.tj", "yandex.tm", "yandex.ua", "yandex.uz" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Sony, new List<string> { "sonyentertainmentnetwork.com", "sony.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Proton, new List<string> { "proton.me", "protonmail.com", "protonvpn.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.Ubisoft, new List<string> { "ubisoft.com", "ubi.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.TransferWise, new List<string> { "transferwise.com", "wise.com" });
GlobalDomains.Add(GlobalEquivalentDomainsType.TakeawayEU, new List<string> { "takeaway.com", "just-eat.dk", "just-eat.no", "just-eat.fr", "just-eat.ch", "lieferando.de", "lieferando.at", "thuisbezorgd.nl", "pyszne.pl" });
#endregion
#region Plans
Plans = new List<Plan>
new Plan
{
new Plan
{
Type = PlanType.Free,
Product = ProductType.Free,
Name = "Free",
NameLocalizationKey = "planNameFree",
DescriptionLocalizationKey = "planDescFree",
BaseSeats = 2,
MaxCollections = 2,
MaxUsers = 2,
Type = PlanType.Free,
Product = ProductType.Free,
Name = "Free",
NameLocalizationKey = "planNameFree",
DescriptionLocalizationKey = "planDescFree",
BaseSeats = 2,
MaxCollections = 2,
MaxUsers = 2,
UpgradeSortOrder = -1, // Always the lowest plan, cannot be upgraded to
DisplaySortOrder = -1,
UpgradeSortOrder = -1, // Always the lowest plan, cannot be upgraded to
DisplaySortOrder = -1,
AllowSeatAutoscale = false,
},
new Plan
{
Type = PlanType.FamiliesAnnually2019,
Product = ProductType.Families,
Name = "Families 2019",
IsAnnual = true,
NameLocalizationKey = "planNameFamilies",
DescriptionLocalizationKey = "planDescFamilies",
BaseSeats = 5,
BaseStorageGb = 1,
MaxUsers = 5,
HasAdditionalStorageOption = true,
HasPremiumAccessOption = true,
TrialPeriodDays = 7,
HasSelfHost = true,
HasTotp = true,
UpgradeSortOrder = 1,
DisplaySortOrder = 1,
LegacyYear = 2020,
StripePlanId = "personal-org-annually",
StripeStoragePlanId = "storage-gb-annually",
StripePremiumAccessPlanId = "personal-org-premium-access-annually",
BasePrice = 12,
AdditionalStoragePricePerGb = 4,
PremiumAccessOptionPrice = 40,
AllowSeatAutoscale = false,
},
new Plan
{
Type = PlanType.TeamsAnnually2019,
Product = ProductType.Teams,
Name = "Teams (Annually) 2019",
IsAnnual = true,
NameLocalizationKey = "planNameTeams",
DescriptionLocalizationKey = "planDescTeams",
CanBeUsedByBusiness = true,
BaseSeats = 5,
BaseStorageGb = 1,
HasAdditionalSeatsOption = true,
HasAdditionalStorageOption = true,
TrialPeriodDays = 7,
HasTotp = true,
UpgradeSortOrder = 2,
DisplaySortOrder = 2,
LegacyYear = 2020,
StripePlanId = "teams-org-annually",
StripeSeatPlanId = "teams-org-seat-annually",
StripeStoragePlanId = "storage-gb-annually",
BasePrice = 60,
SeatPrice = 24,
AdditionalStoragePricePerGb = 4,
AllowSeatAutoscale = true,
},
new Plan
{
Type = PlanType.TeamsMonthly2019,
Product = ProductType.Teams,
Name = "Teams (Monthly) 2019",
NameLocalizationKey = "planNameTeams",
DescriptionLocalizationKey = "planDescTeams",
CanBeUsedByBusiness = true,
BaseSeats = 5,
BaseStorageGb = 1,
HasAdditionalSeatsOption = true,
HasAdditionalStorageOption = true,
TrialPeriodDays = 7,
HasTotp = true,
UpgradeSortOrder = 2,
DisplaySortOrder = 2,
LegacyYear = 2020,
StripePlanId = "teams-org-monthly",
StripeSeatPlanId = "teams-org-seat-monthly",
StripeStoragePlanId = "storage-gb-monthly",
BasePrice = 8,
SeatPrice = 2.5M,
AdditionalStoragePricePerGb = 0.5M,
AllowSeatAutoscale = true,
},
new Plan
{
Type = PlanType.EnterpriseAnnually2019,
Name = "Enterprise (Annually) 2019",
IsAnnual = true,
Product = ProductType.Enterprise,
NameLocalizationKey = "planNameEnterprise",
DescriptionLocalizationKey = "planDescEnterprise",
CanBeUsedByBusiness = true,
BaseSeats = 0,
BaseStorageGb = 1,
HasAdditionalSeatsOption = true,
HasAdditionalStorageOption = true,
TrialPeriodDays = 7,
HasPolicies = true,
HasSelfHost = true,
HasGroups = true,
HasDirectory = true,
HasEvents = true,
HasTotp = true,
Has2fa = true,
HasApi = true,
UsersGetPremium = true,
UpgradeSortOrder = 3,
DisplaySortOrder = 3,
LegacyYear = 2020,
StripePlanId = null,
StripeSeatPlanId = "enterprise-org-seat-annually",
StripeStoragePlanId = "storage-gb-annually",
BasePrice = 0,
SeatPrice = 36,
AdditionalStoragePricePerGb = 4,
AllowSeatAutoscale = true,
},
new Plan
{
Type = PlanType.EnterpriseMonthly2019,
Product = ProductType.Enterprise,
Name = "Enterprise (Monthly) 2019",
NameLocalizationKey = "planNameEnterprise",
DescriptionLocalizationKey = "planDescEnterprise",
CanBeUsedByBusiness = true,
BaseSeats = 0,
BaseStorageGb = 1,
HasAdditionalSeatsOption = true,
HasAdditionalStorageOption = true,
TrialPeriodDays = 7,
HasPolicies = true,
HasGroups = true,
HasDirectory = true,
HasEvents = true,
HasTotp = true,
Has2fa = true,
HasApi = true,
HasSelfHost = true,
UsersGetPremium = true,
UpgradeSortOrder = 3,
DisplaySortOrder = 3,
LegacyYear = 2020,
StripePlanId = null,
StripeSeatPlanId = "enterprise-org-seat-monthly",
StripeStoragePlanId = "storage-gb-monthly",
BasePrice = 0,
SeatPrice = 4M,
AdditionalStoragePricePerGb = 0.5M,
AllowSeatAutoscale = true,
},
new Plan
{
Type = PlanType.FamiliesAnnually,
Product = ProductType.Families,
Name = "Families",
IsAnnual = true,
NameLocalizationKey = "planNameFamilies",
DescriptionLocalizationKey = "planDescFamilies",
BaseSeats = 6,
BaseStorageGb = 1,
MaxUsers = 6,
HasAdditionalStorageOption = true,
TrialPeriodDays = 7,
HasSelfHost = true,
HasTotp = true,
UsersGetPremium = true,
UpgradeSortOrder = 1,
DisplaySortOrder = 1,
StripePlanId = "2020-families-org-annually",
StripeStoragePlanId = "storage-gb-annually",
BasePrice = 40,
AdditionalStoragePricePerGb = 4,
AllowSeatAutoscale = false,
},
new Plan
{
Type = PlanType.TeamsAnnually,
Product = ProductType.Teams,
Name = "Teams (Annually)",
IsAnnual = true,
NameLocalizationKey = "planNameTeams",
DescriptionLocalizationKey = "planDescTeams",
CanBeUsedByBusiness = true,
BaseStorageGb = 1,
BaseSeats = 0,
HasAdditionalSeatsOption = true,
HasAdditionalStorageOption = true,
TrialPeriodDays = 7,
Has2fa = true,
HasApi = true,
HasDirectory = true,
HasEvents = true,
HasGroups = true,
HasTotp = true,
UsersGetPremium = true,
UpgradeSortOrder = 2,
DisplaySortOrder = 2,
StripeSeatPlanId = "2020-teams-org-seat-annually",
StripeStoragePlanId = "storage-gb-annually",
SeatPrice = 36,
AdditionalStoragePricePerGb = 4,
AllowSeatAutoscale = true,
},
new Plan
{
Type = PlanType.TeamsMonthly,
Product = ProductType.Teams,
Name = "Teams (Monthly)",
NameLocalizationKey = "planNameTeams",
DescriptionLocalizationKey = "planDescTeams",
CanBeUsedByBusiness = true,
BaseStorageGb = 1,
BaseSeats = 0,
HasAdditionalSeatsOption = true,
HasAdditionalStorageOption = true,
TrialPeriodDays = 7,
Has2fa = true,
HasApi = true,
HasDirectory = true,
HasEvents = true,
HasGroups = true,
HasTotp = true,
UsersGetPremium = true,
UpgradeSortOrder = 2,
DisplaySortOrder = 2,
StripeSeatPlanId = "2020-teams-org-seat-monthly",
StripeStoragePlanId = "storage-gb-monthly",
SeatPrice = 4,
AdditionalStoragePricePerGb = 0.5M,
AllowSeatAutoscale = true,
},
new Plan
{
Type = PlanType.EnterpriseAnnually,
Name = "Enterprise (Annually)",
Product = ProductType.Enterprise,
IsAnnual = true,
NameLocalizationKey = "planNameEnterprise",
DescriptionLocalizationKey = "planDescEnterprise",
CanBeUsedByBusiness = true,
BaseSeats = 0,
BaseStorageGb = 1,
HasAdditionalSeatsOption = true,
HasAdditionalStorageOption = true,
TrialPeriodDays = 7,
HasPolicies = true,
HasSelfHost = true,
HasGroups = true,
HasDirectory = true,
HasEvents = true,
HasTotp = true,
Has2fa = true,
HasApi = true,
HasSso = true,
HasKeyConnector = true,
HasScim = true,
HasResetPassword = true,
UsersGetPremium = true,
UpgradeSortOrder = 3,
DisplaySortOrder = 3,
StripeSeatPlanId = "2020-enterprise-org-seat-annually",
StripeStoragePlanId = "storage-gb-annually",
BasePrice = 0,
SeatPrice = 60,
AdditionalStoragePricePerGb = 4,
AllowSeatAutoscale = true,
},
new Plan
{
Type = PlanType.EnterpriseMonthly,
Product = ProductType.Enterprise,
Name = "Enterprise (Monthly)",
NameLocalizationKey = "planNameEnterprise",
DescriptionLocalizationKey = "planDescEnterprise",
CanBeUsedByBusiness = true,
BaseSeats = 0,
BaseStorageGb = 1,
HasAdditionalSeatsOption = true,
HasAdditionalStorageOption = true,
TrialPeriodDays = 7,
HasPolicies = true,
HasGroups = true,
HasDirectory = true,
HasEvents = true,
HasTotp = true,
Has2fa = true,
HasApi = true,
HasSelfHost = true,
HasSso = true,
HasKeyConnector = true,
HasScim = true,
HasResetPassword = true,
UsersGetPremium = true,
UpgradeSortOrder = 3,
DisplaySortOrder = 3,
StripeSeatPlanId = "2020-enterprise-org-seat-monthly",
StripeStoragePlanId = "storage-gb-monthly",
BasePrice = 0,
SeatPrice = 6,
AdditionalStoragePricePerGb = 0.5M,
AllowSeatAutoscale = true,
},
new Plan
{
Type = PlanType.Custom,
AllowSeatAutoscale = true,
},
};
#endregion
}
public static IDictionary<GlobalEquivalentDomainsType, IEnumerable<string>> GlobalDomains { get; set; }
public static IEnumerable<Plan> Plans { get; set; }
public static IEnumerable<SponsoredPlan> SponsoredPlans { get; set; } = new[]
AllowSeatAutoscale = false,
},
new Plan
{
new SponsoredPlan
{
PlanSponsorshipType = PlanSponsorshipType.FamiliesForEnterprise,
SponsoredProductType = ProductType.Families,
SponsoringProductType = ProductType.Enterprise,
StripePlanId = "2021-family-for-enterprise-annually",
UsersCanSponsor = (OrganizationUserOrganizationDetails org) =>
GetPlan(org.PlanType).Product == ProductType.Enterprise,
}
};
public static Plan GetPlan(PlanType planType) =>
Plans.FirstOrDefault(p => p.Type == planType);
public static SponsoredPlan GetSponsoredPlan(PlanSponsorshipType planSponsorshipType) =>
SponsoredPlans.FirstOrDefault(p => p.PlanSponsorshipType == planSponsorshipType);
Type = PlanType.FamiliesAnnually2019,
Product = ProductType.Families,
Name = "Families 2019",
IsAnnual = true,
NameLocalizationKey = "planNameFamilies",
DescriptionLocalizationKey = "planDescFamilies",
BaseSeats = 5,
BaseStorageGb = 1,
MaxUsers = 5,
HasAdditionalStorageOption = true,
HasPremiumAccessOption = true,
TrialPeriodDays = 7,
HasSelfHost = true,
HasTotp = true,
UpgradeSortOrder = 1,
DisplaySortOrder = 1,
LegacyYear = 2020,
StripePlanId = "personal-org-annually",
StripeStoragePlanId = "storage-gb-annually",
StripePremiumAccessPlanId = "personal-org-premium-access-annually",
BasePrice = 12,
AdditionalStoragePricePerGb = 4,
PremiumAccessOptionPrice = 40,
AllowSeatAutoscale = false,
},
new Plan
{
Type = PlanType.TeamsAnnually2019,
Product = ProductType.Teams,
Name = "Teams (Annually) 2019",
IsAnnual = true,
NameLocalizationKey = "planNameTeams",
DescriptionLocalizationKey = "planDescTeams",
CanBeUsedByBusiness = true,
BaseSeats = 5,
BaseStorageGb = 1,
HasAdditionalSeatsOption = true,
HasAdditionalStorageOption = true,
TrialPeriodDays = 7,
HasTotp = true,
UpgradeSortOrder = 2,
DisplaySortOrder = 2,
LegacyYear = 2020,
StripePlanId = "teams-org-annually",
StripeSeatPlanId = "teams-org-seat-annually",
StripeStoragePlanId = "storage-gb-annually",
BasePrice = 60,
SeatPrice = 24,
AdditionalStoragePricePerGb = 4,
AllowSeatAutoscale = true,
},
new Plan
{
Type = PlanType.TeamsMonthly2019,
Product = ProductType.Teams,
Name = "Teams (Monthly) 2019",
NameLocalizationKey = "planNameTeams",
DescriptionLocalizationKey = "planDescTeams",
CanBeUsedByBusiness = true,
BaseSeats = 5,
BaseStorageGb = 1,
HasAdditionalSeatsOption = true,
HasAdditionalStorageOption = true,
TrialPeriodDays = 7,
HasTotp = true,
UpgradeSortOrder = 2,
DisplaySortOrder = 2,
LegacyYear = 2020,
StripePlanId = "teams-org-monthly",
StripeSeatPlanId = "teams-org-seat-monthly",
StripeStoragePlanId = "storage-gb-monthly",
BasePrice = 8,
SeatPrice = 2.5M,
AdditionalStoragePricePerGb = 0.5M,
AllowSeatAutoscale = true,
},
new Plan
{
Type = PlanType.EnterpriseAnnually2019,
Name = "Enterprise (Annually) 2019",
IsAnnual = true,
Product = ProductType.Enterprise,
NameLocalizationKey = "planNameEnterprise",
DescriptionLocalizationKey = "planDescEnterprise",
CanBeUsedByBusiness = true,
BaseSeats = 0,
BaseStorageGb = 1,
HasAdditionalSeatsOption = true,
HasAdditionalStorageOption = true,
TrialPeriodDays = 7,
HasPolicies = true,
HasSelfHost = true,
HasGroups = true,
HasDirectory = true,
HasEvents = true,
HasTotp = true,
Has2fa = true,
HasApi = true,
UsersGetPremium = true,
UpgradeSortOrder = 3,
DisplaySortOrder = 3,
LegacyYear = 2020,
StripePlanId = null,
StripeSeatPlanId = "enterprise-org-seat-annually",
StripeStoragePlanId = "storage-gb-annually",
BasePrice = 0,
SeatPrice = 36,
AdditionalStoragePricePerGb = 4,
AllowSeatAutoscale = true,
},
new Plan
{
Type = PlanType.EnterpriseMonthly2019,
Product = ProductType.Enterprise,
Name = "Enterprise (Monthly) 2019",
NameLocalizationKey = "planNameEnterprise",
DescriptionLocalizationKey = "planDescEnterprise",
CanBeUsedByBusiness = true,
BaseSeats = 0,
BaseStorageGb = 1,
HasAdditionalSeatsOption = true,
HasAdditionalStorageOption = true,
TrialPeriodDays = 7,
HasPolicies = true,
HasGroups = true,
HasDirectory = true,
HasEvents = true,
HasTotp = true,
Has2fa = true,
HasApi = true,
HasSelfHost = true,
UsersGetPremium = true,
UpgradeSortOrder = 3,
DisplaySortOrder = 3,
LegacyYear = 2020,
StripePlanId = null,
StripeSeatPlanId = "enterprise-org-seat-monthly",
StripeStoragePlanId = "storage-gb-monthly",
BasePrice = 0,
SeatPrice = 4M,
AdditionalStoragePricePerGb = 0.5M,
AllowSeatAutoscale = true,
},
new Plan
{
Type = PlanType.FamiliesAnnually,
Product = ProductType.Families,
Name = "Families",
IsAnnual = true,
NameLocalizationKey = "planNameFamilies",
DescriptionLocalizationKey = "planDescFamilies",
BaseSeats = 6,
BaseStorageGb = 1,
MaxUsers = 6,
HasAdditionalStorageOption = true,
TrialPeriodDays = 7,
HasSelfHost = true,
HasTotp = true,
UsersGetPremium = true,
UpgradeSortOrder = 1,
DisplaySortOrder = 1,
StripePlanId = "2020-families-org-annually",
StripeStoragePlanId = "storage-gb-annually",
BasePrice = 40,
AdditionalStoragePricePerGb = 4,
AllowSeatAutoscale = false,
},
new Plan
{
Type = PlanType.TeamsAnnually,
Product = ProductType.Teams,
Name = "Teams (Annually)",
IsAnnual = true,
NameLocalizationKey = "planNameTeams",
DescriptionLocalizationKey = "planDescTeams",
CanBeUsedByBusiness = true,
BaseStorageGb = 1,
BaseSeats = 0,
HasAdditionalSeatsOption = true,
HasAdditionalStorageOption = true,
TrialPeriodDays = 7,
Has2fa = true,
HasApi = true,
HasDirectory = true,
HasEvents = true,
HasGroups = true,
HasTotp = true,
UsersGetPremium = true,
UpgradeSortOrder = 2,
DisplaySortOrder = 2,
StripeSeatPlanId = "2020-teams-org-seat-annually",
StripeStoragePlanId = "storage-gb-annually",
SeatPrice = 36,
AdditionalStoragePricePerGb = 4,
AllowSeatAutoscale = true,
},
new Plan
{
Type = PlanType.TeamsMonthly,
Product = ProductType.Teams,
Name = "Teams (Monthly)",
NameLocalizationKey = "planNameTeams",
DescriptionLocalizationKey = "planDescTeams",
CanBeUsedByBusiness = true,
BaseStorageGb = 1,
BaseSeats = 0,
HasAdditionalSeatsOption = true,
HasAdditionalStorageOption = true,
TrialPeriodDays = 7,
Has2fa = true,
HasApi = true,
HasDirectory = true,
HasEvents = true,
HasGroups = true,
HasTotp = true,
UsersGetPremium = true,
UpgradeSortOrder = 2,
DisplaySortOrder = 2,
StripeSeatPlanId = "2020-teams-org-seat-monthly",
StripeStoragePlanId = "storage-gb-monthly",
SeatPrice = 4,
AdditionalStoragePricePerGb = 0.5M,
AllowSeatAutoscale = true,
},
new Plan
{
Type = PlanType.EnterpriseAnnually,
Name = "Enterprise (Annually)",
Product = ProductType.Enterprise,
IsAnnual = true,
NameLocalizationKey = "planNameEnterprise",
DescriptionLocalizationKey = "planDescEnterprise",
CanBeUsedByBusiness = true,
BaseSeats = 0,
BaseStorageGb = 1,
HasAdditionalSeatsOption = true,
HasAdditionalStorageOption = true,
TrialPeriodDays = 7,
HasPolicies = true,
HasSelfHost = true,
HasGroups = true,
HasDirectory = true,
HasEvents = true,
HasTotp = true,
Has2fa = true,
HasApi = true,
HasSso = true,
HasKeyConnector = true,
HasScim = true,
HasResetPassword = true,
UsersGetPremium = true,
UpgradeSortOrder = 3,
DisplaySortOrder = 3,
StripeSeatPlanId = "2020-enterprise-org-seat-annually",
StripeStoragePlanId = "storage-gb-annually",
BasePrice = 0,
SeatPrice = 60,
AdditionalStoragePricePerGb = 4,
AllowSeatAutoscale = true,
},
new Plan
{
Type = PlanType.EnterpriseMonthly,
Product = ProductType.Enterprise,
Name = "Enterprise (Monthly)",
NameLocalizationKey = "planNameEnterprise",
DescriptionLocalizationKey = "planDescEnterprise",
CanBeUsedByBusiness = true,
BaseSeats = 0,
BaseStorageGb = 1,
HasAdditionalSeatsOption = true,
HasAdditionalStorageOption = true,
TrialPeriodDays = 7,
HasPolicies = true,
HasGroups = true,
HasDirectory = true,
HasEvents = true,
HasTotp = true,
Has2fa = true,
HasApi = true,
HasSelfHost = true,
HasSso = true,
HasKeyConnector = true,
HasScim = true,
HasResetPassword = true,
UsersGetPremium = true,
UpgradeSortOrder = 3,
DisplaySortOrder = 3,
StripeSeatPlanId = "2020-enterprise-org-seat-monthly",
StripeStoragePlanId = "storage-gb-monthly",
BasePrice = 0,
SeatPrice = 6,
AdditionalStoragePricePerGb = 0.5M,
AllowSeatAutoscale = true,
},
new Plan
{
Type = PlanType.Custom,
AllowSeatAutoscale = true,
},
};
#endregion
}
public static IDictionary<GlobalEquivalentDomainsType, IEnumerable<string>> GlobalDomains { get; set; }
public static IEnumerable<Plan> Plans { get; set; }
public static IEnumerable<SponsoredPlan> SponsoredPlans { get; set; } = new[]
{
new SponsoredPlan
{
PlanSponsorshipType = PlanSponsorshipType.FamiliesForEnterprise,
SponsoredProductType = ProductType.Families,
SponsoringProductType = ProductType.Enterprise,
StripePlanId = "2021-family-for-enterprise-annually",
UsersCanSponsor = (OrganizationUserOrganizationDetails org) =>
GetPlan(org.PlanType).Product == ProductType.Enterprise,
}
};
public static Plan GetPlan(PlanType planType) =>
Plans.FirstOrDefault(p => p.Type == planType);
public static SponsoredPlan GetSponsoredPlan(PlanSponsorshipType planSponsorshipType) =>
SponsoredPlans.FirstOrDefault(p => p.PlanSponsorshipType == planSponsorshipType);
}

View File

@ -2,52 +2,51 @@
using System.Text.RegularExpressions;
using MimeKit;
namespace Bit.Core.Utilities
namespace Bit.Core.Utilities;
public class StrictEmailAddressAttribute : ValidationAttribute
{
public class StrictEmailAddressAttribute : ValidationAttribute
public StrictEmailAddressAttribute()
: base("The {0} field is not a supported e-mail address format.")
{ }
public override bool IsValid(object value)
{
public StrictEmailAddressAttribute()
: base("The {0} field is not a supported e-mail address format.")
{ }
public override bool IsValid(object value)
var emailAddress = value?.ToString();
if (emailAddress == null)
{
var emailAddress = value?.ToString();
if (emailAddress == null)
{
return false;
}
try
{
var parsedEmailAddress = MailboxAddress.Parse(emailAddress).Address;
if (parsedEmailAddress != emailAddress)
{
return false;
}
}
catch (ParseException)
{
return false;
}
/**
The regex below is intended to catch edge cases that are not handled by the general parsing check above.
This enforces the following rules:
* Requires ASCII only in the local-part (code points 0-127)
* Requires an @ symbol
* Allows any char in second-level domain name, including unicode and symbols
* Requires at least one period (.) separating SLD from TLD
* Must end in a letter (including unicode)
See the unit tests for examples of what is allowed.
**/
var emailFormat = @"^[\x00-\x7F]+@.+\.\p{L}+$";
if (!Regex.IsMatch(emailAddress, emailFormat))
{
return false;
}
return new EmailAddressAttribute().IsValid(emailAddress);
return false;
}
try
{
var parsedEmailAddress = MailboxAddress.Parse(emailAddress).Address;
if (parsedEmailAddress != emailAddress)
{
return false;
}
}
catch (ParseException)
{
return false;
}
/**
The regex below is intended to catch edge cases that are not handled by the general parsing check above.
This enforces the following rules:
* Requires ASCII only in the local-part (code points 0-127)
* Requires an @ symbol
* Allows any char in second-level domain name, including unicode and symbols
* Requires at least one period (.) separating SLD from TLD
* Must end in a letter (including unicode)
See the unit tests for examples of what is allowed.
**/
var emailFormat = @"^[\x00-\x7F]+@.+\.\p{L}+$";
if (!Regex.IsMatch(emailAddress, emailFormat))
{
return false;
}
return new EmailAddressAttribute().IsValid(emailAddress);
}
}

View File

@ -1,39 +1,38 @@
using System.ComponentModel.DataAnnotations;
namespace Bit.Core.Utilities
namespace Bit.Core.Utilities;
public class StrictEmailAddressListAttribute : ValidationAttribute
{
public class StrictEmailAddressListAttribute : ValidationAttribute
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
var strictEmailAttribute = new StrictEmailAddressAttribute();
var emails = value as IList<string>;
if (!emails?.Any() ?? true)
{
var strictEmailAttribute = new StrictEmailAddressAttribute();
var emails = value as IList<string>;
if (!emails?.Any() ?? true)
{
return new ValidationResult("An email is required.");
}
if (emails.Count() > 20)
{
return new ValidationResult("You can only submit up to 20 emails at a time.");
}
for (var i = 0; i < emails.Count(); i++)
{
var email = emails.ElementAt(i);
if (!strictEmailAttribute.IsValid(email))
{
return new ValidationResult($"Email #{i + 1} is not valid.");
}
if (email.Length > 256)
{
return new ValidationResult($"Email #{i + 1} is longer than 256 characters.");
}
}
return ValidationResult.Success;
return new ValidationResult("An email is required.");
}
if (emails.Count() > 20)
{
return new ValidationResult("You can only submit up to 20 emails at a time.");
}
for (var i = 0; i < emails.Count(); i++)
{
var email = emails.ElementAt(i);
if (!strictEmailAttribute.IsValid(email))
{
return new ValidationResult($"Email #{i + 1} is not valid.");
}
if (email.Length > 256)
{
return new ValidationResult($"Email #{i + 1} is longer than 256 characters.");
}
}
return ValidationResult.Success;
}
}