mirror of
https://github.com/bitwarden/server.git
synced 2025-07-03 09:02:48 -05:00
Run formatting (#2230)
This commit is contained in:
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user