mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 15:42:48 -05:00
Revert filescoped (#2227)
* Revert "Add git blame entry (#2226)" This reverts commit239286737d
. * Revert "Turn on file scoped namespaces (#2225)" This reverts commit34fb4cca2a
.
This commit is contained in:
@ -1,15 +1,16 @@
|
||||
namespace Bit.Admin;
|
||||
|
||||
public class AdminSettings
|
||||
namespace Bit.Admin
|
||||
{
|
||||
public virtual string Admins { get; set; }
|
||||
public virtual CloudflareSettings Cloudflare { get; set; }
|
||||
public int? DeleteTrashDaysAgo { get; set; }
|
||||
|
||||
public class CloudflareSettings
|
||||
public class AdminSettings
|
||||
{
|
||||
public string ZoneId { get; set; }
|
||||
public string AuthEmail { get; set; }
|
||||
public string AuthKey { get; set; }
|
||||
public virtual string Admins { get; set; }
|
||||
public virtual CloudflareSettings Cloudflare { get; set; }
|
||||
public int? DeleteTrashDaysAgo { get; set; }
|
||||
|
||||
public class CloudflareSettings
|
||||
{
|
||||
public string ZoneId { get; set; }
|
||||
public string AuthEmail { get; set; }
|
||||
public string AuthKey { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,24 @@
|
||||
using Microsoft.AspNetCore.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bit.Admin.Controllers;
|
||||
|
||||
public class ErrorController : Controller
|
||||
namespace Bit.Admin.Controllers
|
||||
{
|
||||
[Route("/error")]
|
||||
public IActionResult Error(int? statusCode = null)
|
||||
public class ErrorController : Controller
|
||||
{
|
||||
var exceptionHandlerPathFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
|
||||
TempData["Error"] = HttpContext.Features.Get<IExceptionHandlerFeature>()?.Error.Message;
|
||||
[Route("/error")]
|
||||
public IActionResult Error(int? statusCode = null)
|
||||
{
|
||||
var exceptionHandlerPathFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
|
||||
TempData["Error"] = HttpContext.Features.Get<IExceptionHandlerFeature>()?.Error.Message;
|
||||
|
||||
if (exceptionHandlerPathFeature != null)
|
||||
{
|
||||
return Redirect(exceptionHandlerPathFeature.Path);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Redirect("/Home");
|
||||
if (exceptionHandlerPathFeature != null)
|
||||
{
|
||||
return Redirect(exceptionHandlerPathFeature.Path);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Redirect("/Home");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,108 +6,109 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Bit.Admin.Controllers;
|
||||
|
||||
public class HomeController : Controller
|
||||
namespace Bit.Admin.Controllers
|
||||
{
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly HttpClient _httpClient = new HttpClient();
|
||||
private readonly ILogger<HomeController> _logger;
|
||||
|
||||
public HomeController(GlobalSettings globalSettings, ILogger<HomeController> logger)
|
||||
public class HomeController : Controller
|
||||
{
|
||||
_globalSettings = globalSettings;
|
||||
_logger = logger;
|
||||
}
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly HttpClient _httpClient = new HttpClient();
|
||||
private readonly ILogger<HomeController> _logger;
|
||||
|
||||
[Authorize]
|
||||
public IActionResult Index()
|
||||
{
|
||||
return View(new HomeModel
|
||||
public HomeController(GlobalSettings globalSettings, ILogger<HomeController> logger)
|
||||
{
|
||||
GlobalSettings = _globalSettings,
|
||||
CurrentVersion = Core.Utilities.CoreHelpers.GetVersion()
|
||||
});
|
||||
}
|
||||
_globalSettings = globalSettings;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public IActionResult Error()
|
||||
{
|
||||
return View(new ErrorViewModel
|
||||
[Authorize]
|
||||
public IActionResult Index()
|
||||
{
|
||||
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public async Task<IActionResult> GetLatestVersion(ProjectType project, CancellationToken cancellationToken)
|
||||
{
|
||||
var requestUri = $"https://selfhost.bitwarden.com/version.json";
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.GetAsync(requestUri, cancellationToken);
|
||||
if (response.IsSuccessStatusCode)
|
||||
return View(new HomeModel
|
||||
{
|
||||
var latestVersions = JsonConvert.DeserializeObject<LatestVersions>(await response.Content.ReadAsStringAsync());
|
||||
return project switch
|
||||
GlobalSettings = _globalSettings,
|
||||
CurrentVersion = Core.Utilities.CoreHelpers.GetVersion()
|
||||
});
|
||||
}
|
||||
|
||||
public IActionResult Error()
|
||||
{
|
||||
return View(new ErrorViewModel
|
||||
{
|
||||
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public async Task<IActionResult> GetLatestVersion(ProjectType project, CancellationToken cancellationToken)
|
||||
{
|
||||
var requestUri = $"https://selfhost.bitwarden.com/version.json";
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.GetAsync(requestUri, cancellationToken);
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
ProjectType.Core => new JsonResult(latestVersions.Versions.CoreVersion),
|
||||
ProjectType.Web => new JsonResult(latestVersions.Versions.WebVersion),
|
||||
_ => throw new System.NotImplementedException(),
|
||||
};
|
||||
var latestVersions = JsonConvert.DeserializeObject<LatestVersions>(await response.Content.ReadAsStringAsync());
|
||||
return project switch
|
||||
{
|
||||
ProjectType.Core => new JsonResult(latestVersions.Versions.CoreVersion),
|
||||
ProjectType.Web => new JsonResult(latestVersions.Versions.WebVersion),
|
||||
_ => throw new System.NotImplementedException(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (HttpRequestException e)
|
||||
{
|
||||
_logger.LogError(e, $"Error encountered while sending GET request to {requestUri}");
|
||||
return new JsonResult("Unable to fetch latest version") { StatusCode = StatusCodes.Status500InternalServerError };
|
||||
}
|
||||
|
||||
return new JsonResult("-");
|
||||
}
|
||||
|
||||
public async Task<IActionResult> GetInstalledWebVersion(CancellationToken cancellationToken)
|
||||
{
|
||||
var requestUri = $"{_globalSettings.BaseServiceUri.InternalVault}/version.json";
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.GetAsync(requestUri, cancellationToken);
|
||||
if (response.IsSuccessStatusCode)
|
||||
catch (HttpRequestException e)
|
||||
{
|
||||
using var jsonDocument = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync(cancellationToken), cancellationToken: cancellationToken);
|
||||
var root = jsonDocument.RootElement;
|
||||
return new JsonResult(root.GetProperty("version").GetString());
|
||||
_logger.LogError(e, $"Error encountered while sending GET request to {requestUri}");
|
||||
return new JsonResult("Unable to fetch latest version") { StatusCode = StatusCodes.Status500InternalServerError };
|
||||
}
|
||||
|
||||
return new JsonResult("-");
|
||||
}
|
||||
catch (HttpRequestException e)
|
||||
|
||||
public async Task<IActionResult> GetInstalledWebVersion(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogError(e, $"Error encountered while sending GET request to {requestUri}");
|
||||
return new JsonResult("Unable to fetch installed version") { StatusCode = StatusCodes.Status500InternalServerError };
|
||||
var requestUri = $"{_globalSettings.BaseServiceUri.InternalVault}/version.json";
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.GetAsync(requestUri, cancellationToken);
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
using var jsonDocument = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync(cancellationToken), cancellationToken: cancellationToken);
|
||||
var root = jsonDocument.RootElement;
|
||||
return new JsonResult(root.GetProperty("version").GetString());
|
||||
}
|
||||
}
|
||||
catch (HttpRequestException e)
|
||||
{
|
||||
_logger.LogError(e, $"Error encountered while sending GET request to {requestUri}");
|
||||
return new JsonResult("Unable to fetch installed version") { StatusCode = StatusCodes.Status500InternalServerError };
|
||||
}
|
||||
|
||||
return new JsonResult("-");
|
||||
}
|
||||
|
||||
return new JsonResult("-");
|
||||
private class LatestVersions
|
||||
{
|
||||
[JsonProperty("versions")]
|
||||
public Versions Versions { get; set; }
|
||||
}
|
||||
|
||||
private class Versions
|
||||
{
|
||||
[JsonProperty("coreVersion")]
|
||||
public string CoreVersion { get; set; }
|
||||
|
||||
[JsonProperty("webVersion")]
|
||||
public string WebVersion { get; set; }
|
||||
|
||||
[JsonProperty("keyConnectorVersion")]
|
||||
public string KeyConnectorVersion { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
private class LatestVersions
|
||||
public enum ProjectType
|
||||
{
|
||||
[JsonProperty("versions")]
|
||||
public Versions Versions { get; set; }
|
||||
}
|
||||
|
||||
private class Versions
|
||||
{
|
||||
[JsonProperty("coreVersion")]
|
||||
public string CoreVersion { get; set; }
|
||||
|
||||
[JsonProperty("webVersion")]
|
||||
public string WebVersion { get; set; }
|
||||
|
||||
[JsonProperty("keyConnectorVersion")]
|
||||
public string KeyConnectorVersion { get; set; }
|
||||
Core,
|
||||
Web,
|
||||
}
|
||||
}
|
||||
|
||||
public enum ProjectType
|
||||
{
|
||||
Core,
|
||||
Web,
|
||||
}
|
||||
|
@ -1,20 +1,21 @@
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bit.Admin.Controllers;
|
||||
|
||||
public class InfoController : Controller
|
||||
namespace Bit.Admin.Controllers
|
||||
{
|
||||
[HttpGet("~/alive")]
|
||||
[HttpGet("~/now")]
|
||||
public DateTime GetAlive()
|
||||
public class InfoController : Controller
|
||||
{
|
||||
return DateTime.UtcNow;
|
||||
}
|
||||
[HttpGet("~/alive")]
|
||||
[HttpGet("~/now")]
|
||||
public DateTime GetAlive()
|
||||
{
|
||||
return DateTime.UtcNow;
|
||||
}
|
||||
|
||||
[HttpGet("~/version")]
|
||||
public JsonResult GetVersion()
|
||||
{
|
||||
return Json(CoreHelpers.GetVersion());
|
||||
[HttpGet("~/version")]
|
||||
public JsonResult GetVersion()
|
||||
{
|
||||
return Json(CoreHelpers.GetVersion());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,90 +3,91 @@ using Bit.Core.Identity;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bit.Admin.Controllers;
|
||||
|
||||
public class LoginController : Controller
|
||||
namespace Bit.Admin.Controllers
|
||||
{
|
||||
private readonly PasswordlessSignInManager<IdentityUser> _signInManager;
|
||||
|
||||
public LoginController(
|
||||
PasswordlessSignInManager<IdentityUser> signInManager)
|
||||
public class LoginController : Controller
|
||||
{
|
||||
_signInManager = signInManager;
|
||||
}
|
||||
private readonly PasswordlessSignInManager<IdentityUser> _signInManager;
|
||||
|
||||
public IActionResult Index(string returnUrl = null, int? error = null, int? success = null,
|
||||
bool accessDenied = false)
|
||||
{
|
||||
if (!error.HasValue && accessDenied)
|
||||
public LoginController(
|
||||
PasswordlessSignInManager<IdentityUser> signInManager)
|
||||
{
|
||||
error = 4;
|
||||
_signInManager = signInManager;
|
||||
}
|
||||
|
||||
return View(new LoginModel
|
||||
public IActionResult Index(string returnUrl = null, int? error = null, int? success = null,
|
||||
bool accessDenied = false)
|
||||
{
|
||||
ReturnUrl = returnUrl,
|
||||
Error = GetMessage(error),
|
||||
Success = GetMessage(success)
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Index(LoginModel model)
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
await _signInManager.PasswordlessSignInAsync(model.Email, model.ReturnUrl);
|
||||
return RedirectToAction("Index", new
|
||||
if (!error.HasValue && accessDenied)
|
||||
{
|
||||
success = 3
|
||||
error = 4;
|
||||
}
|
||||
|
||||
return View(new LoginModel
|
||||
{
|
||||
ReturnUrl = returnUrl,
|
||||
Error = GetMessage(error),
|
||||
Success = GetMessage(success)
|
||||
});
|
||||
}
|
||||
|
||||
return View(model);
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Confirm(string email, string token, string returnUrl)
|
||||
{
|
||||
var result = await _signInManager.PasswordlessSignInAsync(email, token, true);
|
||||
if (!result.Succeeded)
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Index(LoginModel model)
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
await _signInManager.PasswordlessSignInAsync(model.Email, model.ReturnUrl);
|
||||
return RedirectToAction("Index", new
|
||||
{
|
||||
success = 3
|
||||
});
|
||||
}
|
||||
|
||||
return View(model);
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Confirm(string email, string token, string returnUrl)
|
||||
{
|
||||
var result = await _signInManager.PasswordlessSignInAsync(email, token, true);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
return RedirectToAction("Index", new
|
||||
{
|
||||
error = 2
|
||||
});
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(returnUrl) && Url.IsLocalUrl(returnUrl))
|
||||
{
|
||||
return Redirect(returnUrl);
|
||||
}
|
||||
|
||||
return RedirectToAction("Index", "Home");
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Logout()
|
||||
{
|
||||
await _signInManager.SignOutAsync();
|
||||
return RedirectToAction("Index", new
|
||||
{
|
||||
error = 2
|
||||
success = 1
|
||||
});
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(returnUrl) && Url.IsLocalUrl(returnUrl))
|
||||
private string GetMessage(int? messageCode)
|
||||
{
|
||||
return Redirect(returnUrl);
|
||||
return messageCode switch
|
||||
{
|
||||
1 => "You have been logged out.",
|
||||
2 => "This login confirmation link is invalid. Try logging in again.",
|
||||
3 => "If a valid admin user with this email address exists, " +
|
||||
"we've sent you an email with a secure link to log in.",
|
||||
4 => "Access denied. Please log in.",
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
return RedirectToAction("Index", "Home");
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Logout()
|
||||
{
|
||||
await _signInManager.SignOutAsync();
|
||||
return RedirectToAction("Index", new
|
||||
{
|
||||
success = 1
|
||||
});
|
||||
}
|
||||
|
||||
private string GetMessage(int? messageCode)
|
||||
{
|
||||
return messageCode switch
|
||||
{
|
||||
1 => "You have been logged out.",
|
||||
2 => "This login confirmation link is invalid. Try logging in again.",
|
||||
3 => "If a valid admin user with this email address exists, " +
|
||||
"we've sent you an email with a secure link to log in.",
|
||||
4 => "Access denied. Please log in.",
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -7,86 +7,87 @@ using Microsoft.Azure.Cosmos;
|
||||
using Microsoft.Azure.Cosmos.Linq;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Bit.Admin.Controllers;
|
||||
|
||||
[Authorize]
|
||||
[SelfHosted(NotSelfHostedOnly = true)]
|
||||
public class LogsController : Controller
|
||||
namespace Bit.Admin.Controllers
|
||||
{
|
||||
private const string Database = "Diagnostics";
|
||||
private const string Container = "Logs";
|
||||
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
|
||||
public LogsController(GlobalSettings globalSettings)
|
||||
[Authorize]
|
||||
[SelfHosted(NotSelfHostedOnly = true)]
|
||||
public class LogsController : Controller
|
||||
{
|
||||
_globalSettings = globalSettings;
|
||||
}
|
||||
private const string Database = "Diagnostics";
|
||||
private const string Container = "Logs";
|
||||
|
||||
public async Task<IActionResult> Index(string cursor = null, int count = 50,
|
||||
LogEventLevel? level = null, string project = null, DateTime? start = null, DateTime? end = null)
|
||||
{
|
||||
using (var client = new CosmosClient(_globalSettings.DocumentDb.Uri,
|
||||
_globalSettings.DocumentDb.Key))
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
|
||||
public LogsController(GlobalSettings globalSettings)
|
||||
{
|
||||
var cosmosContainer = client.GetContainer(Database, Container);
|
||||
var query = cosmosContainer.GetItemLinqQueryable<LogModel>(
|
||||
requestOptions: new QueryRequestOptions()
|
||||
{
|
||||
MaxItemCount = count
|
||||
},
|
||||
continuationToken: cursor
|
||||
).AsQueryable();
|
||||
|
||||
if (level.HasValue)
|
||||
{
|
||||
query = query.Where(l => l.Level == level.Value.ToString());
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(project))
|
||||
{
|
||||
query = query.Where(l => l.Properties != null && l.Properties["Project"] == (object)project);
|
||||
}
|
||||
if (start.HasValue)
|
||||
{
|
||||
query = query.Where(l => l.Timestamp >= start.Value);
|
||||
}
|
||||
if (end.HasValue)
|
||||
{
|
||||
query = query.Where(l => l.Timestamp <= end.Value);
|
||||
}
|
||||
var feedIterator = query.OrderByDescending(l => l.Timestamp).ToFeedIterator();
|
||||
var response = await feedIterator.ReadNextAsync();
|
||||
|
||||
return View(new LogsModel
|
||||
{
|
||||
Level = level,
|
||||
Project = project,
|
||||
Start = start,
|
||||
End = end,
|
||||
Items = response.ToList(),
|
||||
Count = count,
|
||||
Cursor = cursor,
|
||||
NextCursor = response.ContinuationToken
|
||||
});
|
||||
_globalSettings = globalSettings;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IActionResult> View(Guid id)
|
||||
{
|
||||
using (var client = new CosmosClient(_globalSettings.DocumentDb.Uri,
|
||||
_globalSettings.DocumentDb.Key))
|
||||
public async Task<IActionResult> Index(string cursor = null, int count = 50,
|
||||
LogEventLevel? level = null, string project = null, DateTime? start = null, DateTime? end = null)
|
||||
{
|
||||
var cosmosContainer = client.GetContainer(Database, Container);
|
||||
var query = cosmosContainer.GetItemLinqQueryable<LogDetailsModel>()
|
||||
.AsQueryable()
|
||||
.Where(l => l.Id == id.ToString());
|
||||
|
||||
var response = await query.ToFeedIterator().ReadNextAsync();
|
||||
if (response == null || response.Count == 0)
|
||||
using (var client = new CosmosClient(_globalSettings.DocumentDb.Uri,
|
||||
_globalSettings.DocumentDb.Key))
|
||||
{
|
||||
return RedirectToAction("Index");
|
||||
var cosmosContainer = client.GetContainer(Database, Container);
|
||||
var query = cosmosContainer.GetItemLinqQueryable<LogModel>(
|
||||
requestOptions: new QueryRequestOptions()
|
||||
{
|
||||
MaxItemCount = count
|
||||
},
|
||||
continuationToken: cursor
|
||||
).AsQueryable();
|
||||
|
||||
if (level.HasValue)
|
||||
{
|
||||
query = query.Where(l => l.Level == level.Value.ToString());
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(project))
|
||||
{
|
||||
query = query.Where(l => l.Properties != null && l.Properties["Project"] == (object)project);
|
||||
}
|
||||
if (start.HasValue)
|
||||
{
|
||||
query = query.Where(l => l.Timestamp >= start.Value);
|
||||
}
|
||||
if (end.HasValue)
|
||||
{
|
||||
query = query.Where(l => l.Timestamp <= end.Value);
|
||||
}
|
||||
var feedIterator = query.OrderByDescending(l => l.Timestamp).ToFeedIterator();
|
||||
var response = await feedIterator.ReadNextAsync();
|
||||
|
||||
return View(new LogsModel
|
||||
{
|
||||
Level = level,
|
||||
Project = project,
|
||||
Start = start,
|
||||
End = end,
|
||||
Items = response.ToList(),
|
||||
Count = count,
|
||||
Cursor = cursor,
|
||||
NextCursor = response.ContinuationToken
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IActionResult> View(Guid id)
|
||||
{
|
||||
using (var client = new CosmosClient(_globalSettings.DocumentDb.Uri,
|
||||
_globalSettings.DocumentDb.Key))
|
||||
{
|
||||
var cosmosContainer = client.GetContainer(Database, Container);
|
||||
var query = cosmosContainer.GetItemLinqQueryable<LogDetailsModel>()
|
||||
.AsQueryable()
|
||||
.Where(l => l.Id == id.ToString());
|
||||
|
||||
var response = await query.ToFeedIterator().ReadNextAsync();
|
||||
if (response == null || response.Count == 0)
|
||||
{
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
return View(response.First());
|
||||
}
|
||||
return View(response.First());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,206 +11,207 @@ using Bit.Core.Utilities;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bit.Admin.Controllers;
|
||||
|
||||
[Authorize]
|
||||
public class OrganizationsController : Controller
|
||||
namespace Bit.Admin.Controllers
|
||||
{
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly IOrganizationConnectionRepository _organizationConnectionRepository;
|
||||
private readonly ISelfHostedSyncSponsorshipsCommand _syncSponsorshipsCommand;
|
||||
private readonly ICipherRepository _cipherRepository;
|
||||
private readonly ICollectionRepository _collectionRepository;
|
||||
private readonly IGroupRepository _groupRepository;
|
||||
private readonly IPolicyRepository _policyRepository;
|
||||
private readonly IPaymentService _paymentService;
|
||||
private readonly ILicensingService _licensingService;
|
||||
private readonly IApplicationCacheService _applicationCacheService;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IReferenceEventService _referenceEventService;
|
||||
private readonly IUserService _userService;
|
||||
private readonly ILogger<OrganizationsController> _logger;
|
||||
|
||||
public OrganizationsController(
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IOrganizationConnectionRepository organizationConnectionRepository,
|
||||
ISelfHostedSyncSponsorshipsCommand syncSponsorshipsCommand,
|
||||
ICipherRepository cipherRepository,
|
||||
ICollectionRepository collectionRepository,
|
||||
IGroupRepository groupRepository,
|
||||
IPolicyRepository policyRepository,
|
||||
IPaymentService paymentService,
|
||||
ILicensingService licensingService,
|
||||
IApplicationCacheService applicationCacheService,
|
||||
GlobalSettings globalSettings,
|
||||
IReferenceEventService referenceEventService,
|
||||
IUserService userService,
|
||||
ILogger<OrganizationsController> logger)
|
||||
[Authorize]
|
||||
public class OrganizationsController : Controller
|
||||
{
|
||||
_organizationRepository = organizationRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
_organizationConnectionRepository = organizationConnectionRepository;
|
||||
_syncSponsorshipsCommand = syncSponsorshipsCommand;
|
||||
_cipherRepository = cipherRepository;
|
||||
_collectionRepository = collectionRepository;
|
||||
_groupRepository = groupRepository;
|
||||
_policyRepository = policyRepository;
|
||||
_paymentService = paymentService;
|
||||
_licensingService = licensingService;
|
||||
_applicationCacheService = applicationCacheService;
|
||||
_globalSettings = globalSettings;
|
||||
_referenceEventService = referenceEventService;
|
||||
_userService = userService;
|
||||
_logger = logger;
|
||||
}
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly IOrganizationConnectionRepository _organizationConnectionRepository;
|
||||
private readonly ISelfHostedSyncSponsorshipsCommand _syncSponsorshipsCommand;
|
||||
private readonly ICipherRepository _cipherRepository;
|
||||
private readonly ICollectionRepository _collectionRepository;
|
||||
private readonly IGroupRepository _groupRepository;
|
||||
private readonly IPolicyRepository _policyRepository;
|
||||
private readonly IPaymentService _paymentService;
|
||||
private readonly ILicensingService _licensingService;
|
||||
private readonly IApplicationCacheService _applicationCacheService;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IReferenceEventService _referenceEventService;
|
||||
private readonly IUserService _userService;
|
||||
private readonly ILogger<OrganizationsController> _logger;
|
||||
|
||||
public async Task<IActionResult> Index(string name = null, string userEmail = null, bool? paid = null,
|
||||
int page = 1, int count = 25)
|
||||
{
|
||||
if (page < 1)
|
||||
public OrganizationsController(
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IOrganizationConnectionRepository organizationConnectionRepository,
|
||||
ISelfHostedSyncSponsorshipsCommand syncSponsorshipsCommand,
|
||||
ICipherRepository cipherRepository,
|
||||
ICollectionRepository collectionRepository,
|
||||
IGroupRepository groupRepository,
|
||||
IPolicyRepository policyRepository,
|
||||
IPaymentService paymentService,
|
||||
ILicensingService licensingService,
|
||||
IApplicationCacheService applicationCacheService,
|
||||
GlobalSettings globalSettings,
|
||||
IReferenceEventService referenceEventService,
|
||||
IUserService userService,
|
||||
ILogger<OrganizationsController> logger)
|
||||
{
|
||||
page = 1;
|
||||
_organizationRepository = organizationRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
_organizationConnectionRepository = organizationConnectionRepository;
|
||||
_syncSponsorshipsCommand = syncSponsorshipsCommand;
|
||||
_cipherRepository = cipherRepository;
|
||||
_collectionRepository = collectionRepository;
|
||||
_groupRepository = groupRepository;
|
||||
_policyRepository = policyRepository;
|
||||
_paymentService = paymentService;
|
||||
_licensingService = licensingService;
|
||||
_applicationCacheService = applicationCacheService;
|
||||
_globalSettings = globalSettings;
|
||||
_referenceEventService = referenceEventService;
|
||||
_userService = userService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
if (count < 1)
|
||||
public async Task<IActionResult> Index(string name = null, string userEmail = null, bool? paid = null,
|
||||
int page = 1, int count = 25)
|
||||
{
|
||||
count = 1;
|
||||
if (page < 1)
|
||||
{
|
||||
page = 1;
|
||||
}
|
||||
|
||||
if (count < 1)
|
||||
{
|
||||
count = 1;
|
||||
}
|
||||
|
||||
var skip = (page - 1) * count;
|
||||
var organizations = await _organizationRepository.SearchAsync(name, userEmail, paid, skip, count);
|
||||
return View(new OrganizationsModel
|
||||
{
|
||||
Items = organizations as List<Organization>,
|
||||
Name = string.IsNullOrWhiteSpace(name) ? null : name,
|
||||
UserEmail = string.IsNullOrWhiteSpace(userEmail) ? null : userEmail,
|
||||
Paid = paid,
|
||||
Page = page,
|
||||
Count = count,
|
||||
Action = _globalSettings.SelfHosted ? "View" : "Edit",
|
||||
SelfHosted = _globalSettings.SelfHosted
|
||||
});
|
||||
}
|
||||
|
||||
var skip = (page - 1) * count;
|
||||
var organizations = await _organizationRepository.SearchAsync(name, userEmail, paid, skip, count);
|
||||
return View(new OrganizationsModel
|
||||
public async Task<IActionResult> View(Guid id)
|
||||
{
|
||||
Items = organizations as List<Organization>,
|
||||
Name = string.IsNullOrWhiteSpace(name) ? null : name,
|
||||
UserEmail = string.IsNullOrWhiteSpace(userEmail) ? null : userEmail,
|
||||
Paid = paid,
|
||||
Page = page,
|
||||
Count = count,
|
||||
Action = _globalSettings.SelfHosted ? "View" : "Edit",
|
||||
SelfHosted = _globalSettings.SelfHosted
|
||||
});
|
||||
}
|
||||
var organization = await _organizationRepository.GetByIdAsync(id);
|
||||
if (organization == null)
|
||||
{
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
public async Task<IActionResult> View(Guid id)
|
||||
{
|
||||
var organization = await _organizationRepository.GetByIdAsync(id);
|
||||
if (organization == null)
|
||||
var ciphers = await _cipherRepository.GetManyByOrganizationIdAsync(id);
|
||||
var collections = await _collectionRepository.GetManyByOrganizationIdAsync(id);
|
||||
IEnumerable<Group> groups = null;
|
||||
if (organization.UseGroups)
|
||||
{
|
||||
groups = await _groupRepository.GetManyByOrganizationIdAsync(id);
|
||||
}
|
||||
IEnumerable<Policy> policies = null;
|
||||
if (organization.UsePolicies)
|
||||
{
|
||||
policies = await _policyRepository.GetManyByOrganizationIdAsync(id);
|
||||
}
|
||||
var users = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(id);
|
||||
var billingSyncConnection = _globalSettings.EnableCloudCommunication ? await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(id, OrganizationConnectionType.CloudBillingSync) : null;
|
||||
return View(new OrganizationViewModel(organization, billingSyncConnection, users, ciphers, collections, groups, policies));
|
||||
}
|
||||
|
||||
[SelfHosted(NotSelfHostedOnly = true)]
|
||||
public async Task<IActionResult> Edit(Guid id)
|
||||
{
|
||||
var organization = await _organizationRepository.GetByIdAsync(id);
|
||||
if (organization == null)
|
||||
{
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
var ciphers = await _cipherRepository.GetManyByOrganizationIdAsync(id);
|
||||
var collections = await _collectionRepository.GetManyByOrganizationIdAsync(id);
|
||||
IEnumerable<Group> groups = null;
|
||||
if (organization.UseGroups)
|
||||
{
|
||||
groups = await _groupRepository.GetManyByOrganizationIdAsync(id);
|
||||
}
|
||||
IEnumerable<Policy> policies = null;
|
||||
if (organization.UsePolicies)
|
||||
{
|
||||
policies = await _policyRepository.GetManyByOrganizationIdAsync(id);
|
||||
}
|
||||
var users = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(id);
|
||||
var billingInfo = await _paymentService.GetBillingAsync(organization);
|
||||
var billingSyncConnection = _globalSettings.EnableCloudCommunication ? await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(id, OrganizationConnectionType.CloudBillingSync) : null;
|
||||
return View(new OrganizationEditModel(organization, users, ciphers, collections, groups, policies,
|
||||
billingInfo, billingSyncConnection, _globalSettings));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[SelfHosted(NotSelfHostedOnly = true)]
|
||||
public async Task<IActionResult> Edit(Guid id, OrganizationEditModel model)
|
||||
{
|
||||
var organization = await _organizationRepository.GetByIdAsync(id);
|
||||
model.ToOrganization(organization);
|
||||
await _organizationRepository.ReplaceAsync(organization);
|
||||
await _applicationCacheService.UpsertOrganizationAbilityAsync(organization);
|
||||
await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.OrganizationEditedByAdmin, organization)
|
||||
{
|
||||
EventRaisedByUser = _userService.GetUserName(User),
|
||||
SalesAssistedTrialStarted = model.SalesAssistedTrialStarted,
|
||||
});
|
||||
return RedirectToAction("Edit", new { id });
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Delete(Guid id)
|
||||
{
|
||||
var organization = await _organizationRepository.GetByIdAsync(id);
|
||||
if (organization != null)
|
||||
{
|
||||
await _organizationRepository.DeleteAsync(organization);
|
||||
await _applicationCacheService.DeleteOrganizationAbilityAsync(organization.Id);
|
||||
}
|
||||
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
var ciphers = await _cipherRepository.GetManyByOrganizationIdAsync(id);
|
||||
var collections = await _collectionRepository.GetManyByOrganizationIdAsync(id);
|
||||
IEnumerable<Group> groups = null;
|
||||
if (organization.UseGroups)
|
||||
public async Task<IActionResult> TriggerBillingSync(Guid id)
|
||||
{
|
||||
groups = await _groupRepository.GetManyByOrganizationIdAsync(id);
|
||||
}
|
||||
IEnumerable<Policy> policies = null;
|
||||
if (organization.UsePolicies)
|
||||
{
|
||||
policies = await _policyRepository.GetManyByOrganizationIdAsync(id);
|
||||
}
|
||||
var users = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(id);
|
||||
var billingSyncConnection = _globalSettings.EnableCloudCommunication ? await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(id, OrganizationConnectionType.CloudBillingSync) : null;
|
||||
return View(new OrganizationViewModel(organization, billingSyncConnection, users, ciphers, collections, groups, policies));
|
||||
}
|
||||
var organization = await _organizationRepository.GetByIdAsync(id);
|
||||
if (organization == null)
|
||||
{
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
var connection = (await _organizationConnectionRepository.GetEnabledByOrganizationIdTypeAsync(id, OrganizationConnectionType.CloudBillingSync)).FirstOrDefault();
|
||||
if (connection != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var config = connection.GetConfig<BillingSyncConfig>();
|
||||
await _syncSponsorshipsCommand.SyncOrganization(id, config.CloudOrganizationId, connection);
|
||||
TempData["ConnectionActivated"] = id;
|
||||
TempData["ConnectionError"] = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
TempData["ConnectionError"] = ex.Message;
|
||||
_logger.LogWarning(ex, "Error while attempting to do billing sync for organization with id '{OrganizationId}'", id);
|
||||
}
|
||||
|
||||
[SelfHosted(NotSelfHostedOnly = true)]
|
||||
public async Task<IActionResult> Edit(Guid id)
|
||||
{
|
||||
var organization = await _organizationRepository.GetByIdAsync(id);
|
||||
if (organization == null)
|
||||
{
|
||||
if (_globalSettings.SelfHosted)
|
||||
{
|
||||
return RedirectToAction("View", new { id });
|
||||
}
|
||||
else
|
||||
{
|
||||
return RedirectToAction("Edit", new { id });
|
||||
}
|
||||
}
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
var ciphers = await _cipherRepository.GetManyByOrganizationIdAsync(id);
|
||||
var collections = await _collectionRepository.GetManyByOrganizationIdAsync(id);
|
||||
IEnumerable<Group> groups = null;
|
||||
if (organization.UseGroups)
|
||||
{
|
||||
groups = await _groupRepository.GetManyByOrganizationIdAsync(id);
|
||||
}
|
||||
IEnumerable<Policy> policies = null;
|
||||
if (organization.UsePolicies)
|
||||
{
|
||||
policies = await _policyRepository.GetManyByOrganizationIdAsync(id);
|
||||
}
|
||||
var users = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(id);
|
||||
var billingInfo = await _paymentService.GetBillingAsync(organization);
|
||||
var billingSyncConnection = _globalSettings.EnableCloudCommunication ? await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(id, OrganizationConnectionType.CloudBillingSync) : null;
|
||||
return View(new OrganizationEditModel(organization, users, ciphers, collections, groups, policies,
|
||||
billingInfo, billingSyncConnection, _globalSettings));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[SelfHosted(NotSelfHostedOnly = true)]
|
||||
public async Task<IActionResult> Edit(Guid id, OrganizationEditModel model)
|
||||
{
|
||||
var organization = await _organizationRepository.GetByIdAsync(id);
|
||||
model.ToOrganization(organization);
|
||||
await _organizationRepository.ReplaceAsync(organization);
|
||||
await _applicationCacheService.UpsertOrganizationAbilityAsync(organization);
|
||||
await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.OrganizationEditedByAdmin, organization)
|
||||
{
|
||||
EventRaisedByUser = _userService.GetUserName(User),
|
||||
SalesAssistedTrialStarted = model.SalesAssistedTrialStarted,
|
||||
});
|
||||
return RedirectToAction("Edit", new { id });
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Delete(Guid id)
|
||||
{
|
||||
var organization = await _organizationRepository.GetByIdAsync(id);
|
||||
if (organization != null)
|
||||
{
|
||||
await _organizationRepository.DeleteAsync(organization);
|
||||
await _applicationCacheService.DeleteOrganizationAbilityAsync(organization.Id);
|
||||
}
|
||||
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
public async Task<IActionResult> TriggerBillingSync(Guid id)
|
||||
{
|
||||
var organization = await _organizationRepository.GetByIdAsync(id);
|
||||
if (organization == null)
|
||||
{
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
var connection = (await _organizationConnectionRepository.GetEnabledByOrganizationIdTypeAsync(id, OrganizationConnectionType.CloudBillingSync)).FirstOrDefault();
|
||||
if (connection != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var config = connection.GetConfig<BillingSyncConfig>();
|
||||
await _syncSponsorshipsCommand.SyncOrganization(id, config.CloudOrganizationId, connection);
|
||||
TempData["ConnectionActivated"] = id;
|
||||
TempData["ConnectionError"] = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
TempData["ConnectionError"] = ex.Message;
|
||||
_logger.LogWarning(ex, "Error while attempting to do billing sync for organization with id '{OrganizationId}'", id);
|
||||
}
|
||||
|
||||
if (_globalSettings.SelfHosted)
|
||||
{
|
||||
return RedirectToAction("View", new { id });
|
||||
}
|
||||
else
|
||||
{
|
||||
return RedirectToAction("Edit", new { id });
|
||||
}
|
||||
}
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -7,127 +7,128 @@ using Bit.Core.Utilities;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bit.Admin.Controllers;
|
||||
|
||||
[Authorize]
|
||||
[SelfHosted(NotSelfHostedOnly = true)]
|
||||
public class ProvidersController : Controller
|
||||
namespace Bit.Admin.Controllers
|
||||
{
|
||||
private readonly IProviderRepository _providerRepository;
|
||||
private readonly IProviderUserRepository _providerUserRepository;
|
||||
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IApplicationCacheService _applicationCacheService;
|
||||
private readonly IProviderService _providerService;
|
||||
|
||||
public ProvidersController(IProviderRepository providerRepository, IProviderUserRepository providerUserRepository,
|
||||
IProviderOrganizationRepository providerOrganizationRepository, IProviderService providerService,
|
||||
GlobalSettings globalSettings, IApplicationCacheService applicationCacheService)
|
||||
{
|
||||
_providerRepository = providerRepository;
|
||||
_providerUserRepository = providerUserRepository;
|
||||
_providerOrganizationRepository = providerOrganizationRepository;
|
||||
_providerService = providerService;
|
||||
_globalSettings = globalSettings;
|
||||
_applicationCacheService = applicationCacheService;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Index(string name = null, string userEmail = null, int page = 1, int count = 25)
|
||||
{
|
||||
if (page < 1)
|
||||
{
|
||||
page = 1;
|
||||
}
|
||||
|
||||
if (count < 1)
|
||||
{
|
||||
count = 1;
|
||||
}
|
||||
|
||||
var skip = (page - 1) * count;
|
||||
var providers = await _providerRepository.SearchAsync(name, userEmail, skip, count);
|
||||
return View(new ProvidersModel
|
||||
{
|
||||
Items = providers as List<Provider>,
|
||||
Name = string.IsNullOrWhiteSpace(name) ? null : name,
|
||||
UserEmail = string.IsNullOrWhiteSpace(userEmail) ? null : userEmail,
|
||||
Page = page,
|
||||
Count = count,
|
||||
Action = _globalSettings.SelfHosted ? "View" : "Edit",
|
||||
SelfHosted = _globalSettings.SelfHosted
|
||||
});
|
||||
}
|
||||
|
||||
public IActionResult Create(string ownerEmail = null)
|
||||
{
|
||||
return View(new CreateProviderModel
|
||||
{
|
||||
OwnerEmail = ownerEmail
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Create(CreateProviderModel model)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
|
||||
await _providerService.CreateAsync(model.OwnerEmail);
|
||||
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
public async Task<IActionResult> View(Guid id)
|
||||
{
|
||||
var provider = await _providerRepository.GetByIdAsync(id);
|
||||
if (provider == null)
|
||||
{
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
var users = await _providerUserRepository.GetManyDetailsByProviderAsync(id);
|
||||
var providerOrganizations = await _providerOrganizationRepository.GetManyDetailsByProviderAsync(id);
|
||||
return View(new ProviderViewModel(provider, users, providerOrganizations));
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[SelfHosted(NotSelfHostedOnly = true)]
|
||||
public async Task<IActionResult> Edit(Guid id)
|
||||
public class ProvidersController : Controller
|
||||
{
|
||||
var provider = await _providerRepository.GetByIdAsync(id);
|
||||
if (provider == null)
|
||||
private readonly IProviderRepository _providerRepository;
|
||||
private readonly IProviderUserRepository _providerUserRepository;
|
||||
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IApplicationCacheService _applicationCacheService;
|
||||
private readonly IProviderService _providerService;
|
||||
|
||||
public ProvidersController(IProviderRepository providerRepository, IProviderUserRepository providerUserRepository,
|
||||
IProviderOrganizationRepository providerOrganizationRepository, IProviderService providerService,
|
||||
GlobalSettings globalSettings, IApplicationCacheService applicationCacheService)
|
||||
{
|
||||
_providerRepository = providerRepository;
|
||||
_providerUserRepository = providerUserRepository;
|
||||
_providerOrganizationRepository = providerOrganizationRepository;
|
||||
_providerService = providerService;
|
||||
_globalSettings = globalSettings;
|
||||
_applicationCacheService = applicationCacheService;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Index(string name = null, string userEmail = null, int page = 1, int count = 25)
|
||||
{
|
||||
if (page < 1)
|
||||
{
|
||||
page = 1;
|
||||
}
|
||||
|
||||
if (count < 1)
|
||||
{
|
||||
count = 1;
|
||||
}
|
||||
|
||||
var skip = (page - 1) * count;
|
||||
var providers = await _providerRepository.SearchAsync(name, userEmail, skip, count);
|
||||
return View(new ProvidersModel
|
||||
{
|
||||
Items = providers as List<Provider>,
|
||||
Name = string.IsNullOrWhiteSpace(name) ? null : name,
|
||||
UserEmail = string.IsNullOrWhiteSpace(userEmail) ? null : userEmail,
|
||||
Page = page,
|
||||
Count = count,
|
||||
Action = _globalSettings.SelfHosted ? "View" : "Edit",
|
||||
SelfHosted = _globalSettings.SelfHosted
|
||||
});
|
||||
}
|
||||
|
||||
public IActionResult Create(string ownerEmail = null)
|
||||
{
|
||||
return View(new CreateProviderModel
|
||||
{
|
||||
OwnerEmail = ownerEmail
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Create(CreateProviderModel model)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
|
||||
await _providerService.CreateAsync(model.OwnerEmail);
|
||||
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
var users = await _providerUserRepository.GetManyDetailsByProviderAsync(id);
|
||||
var providerOrganizations = await _providerOrganizationRepository.GetManyDetailsByProviderAsync(id);
|
||||
return View(new ProviderEditModel(provider, users, providerOrganizations));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[SelfHosted(NotSelfHostedOnly = true)]
|
||||
public async Task<IActionResult> Edit(Guid id, ProviderEditModel model)
|
||||
{
|
||||
var provider = await _providerRepository.GetByIdAsync(id);
|
||||
if (provider == null)
|
||||
public async Task<IActionResult> View(Guid id)
|
||||
{
|
||||
return RedirectToAction("Index");
|
||||
var provider = await _providerRepository.GetByIdAsync(id);
|
||||
if (provider == null)
|
||||
{
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
var users = await _providerUserRepository.GetManyDetailsByProviderAsync(id);
|
||||
var providerOrganizations = await _providerOrganizationRepository.GetManyDetailsByProviderAsync(id);
|
||||
return View(new ProviderViewModel(provider, users, providerOrganizations));
|
||||
}
|
||||
|
||||
model.ToProvider(provider);
|
||||
await _providerRepository.ReplaceAsync(provider);
|
||||
await _applicationCacheService.UpsertProviderAbilityAsync(provider);
|
||||
return RedirectToAction("Edit", new { id });
|
||||
}
|
||||
[SelfHosted(NotSelfHostedOnly = true)]
|
||||
public async Task<IActionResult> Edit(Guid id)
|
||||
{
|
||||
var provider = await _providerRepository.GetByIdAsync(id);
|
||||
if (provider == null)
|
||||
{
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
public async Task<IActionResult> ResendInvite(Guid ownerId, Guid providerId)
|
||||
{
|
||||
await _providerService.ResendProviderSetupInviteEmailAsync(providerId, ownerId);
|
||||
TempData["InviteResentTo"] = ownerId;
|
||||
return RedirectToAction("Edit", new { id = providerId });
|
||||
var users = await _providerUserRepository.GetManyDetailsByProviderAsync(id);
|
||||
var providerOrganizations = await _providerOrganizationRepository.GetManyDetailsByProviderAsync(id);
|
||||
return View(new ProviderEditModel(provider, users, providerOrganizations));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[SelfHosted(NotSelfHostedOnly = true)]
|
||||
public async Task<IActionResult> Edit(Guid id, ProviderEditModel model)
|
||||
{
|
||||
var provider = await _providerRepository.GetByIdAsync(id);
|
||||
if (provider == null)
|
||||
{
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
model.ToProvider(provider);
|
||||
await _providerRepository.ReplaceAsync(provider);
|
||||
await _applicationCacheService.UpsertProviderAbilityAsync(provider);
|
||||
return RedirectToAction("Edit", new { id });
|
||||
}
|
||||
|
||||
public async Task<IActionResult> ResendInvite(Guid ownerId, Guid providerId)
|
||||
{
|
||||
await _providerService.ResendProviderSetupInviteEmailAsync(providerId, ownerId);
|
||||
TempData["InviteResentTo"] = ownerId;
|
||||
return RedirectToAction("Edit", new { id = providerId });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -7,104 +7,105 @@ using Bit.Core.Utilities;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bit.Admin.Controllers;
|
||||
|
||||
[Authorize]
|
||||
public class UsersController : Controller
|
||||
namespace Bit.Admin.Controllers
|
||||
{
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly ICipherRepository _cipherRepository;
|
||||
private readonly IPaymentService _paymentService;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
|
||||
public UsersController(
|
||||
IUserRepository userRepository,
|
||||
ICipherRepository cipherRepository,
|
||||
IPaymentService paymentService,
|
||||
GlobalSettings globalSettings)
|
||||
[Authorize]
|
||||
public class UsersController : Controller
|
||||
{
|
||||
_userRepository = userRepository;
|
||||
_cipherRepository = cipherRepository;
|
||||
_paymentService = paymentService;
|
||||
_globalSettings = globalSettings;
|
||||
}
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly ICipherRepository _cipherRepository;
|
||||
private readonly IPaymentService _paymentService;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
|
||||
public async Task<IActionResult> Index(string email, int page = 1, int count = 25)
|
||||
{
|
||||
if (page < 1)
|
||||
public UsersController(
|
||||
IUserRepository userRepository,
|
||||
ICipherRepository cipherRepository,
|
||||
IPaymentService paymentService,
|
||||
GlobalSettings globalSettings)
|
||||
{
|
||||
page = 1;
|
||||
_userRepository = userRepository;
|
||||
_cipherRepository = cipherRepository;
|
||||
_paymentService = paymentService;
|
||||
_globalSettings = globalSettings;
|
||||
}
|
||||
|
||||
if (count < 1)
|
||||
public async Task<IActionResult> Index(string email, int page = 1, int count = 25)
|
||||
{
|
||||
count = 1;
|
||||
if (page < 1)
|
||||
{
|
||||
page = 1;
|
||||
}
|
||||
|
||||
if (count < 1)
|
||||
{
|
||||
count = 1;
|
||||
}
|
||||
|
||||
var skip = (page - 1) * count;
|
||||
var users = await _userRepository.SearchAsync(email, skip, count);
|
||||
return View(new UsersModel
|
||||
{
|
||||
Items = users as List<User>,
|
||||
Email = string.IsNullOrWhiteSpace(email) ? null : email,
|
||||
Page = page,
|
||||
Count = count,
|
||||
Action = _globalSettings.SelfHosted ? "View" : "Edit"
|
||||
});
|
||||
}
|
||||
|
||||
var skip = (page - 1) * count;
|
||||
var users = await _userRepository.SearchAsync(email, skip, count);
|
||||
return View(new UsersModel
|
||||
public async Task<IActionResult> View(Guid id)
|
||||
{
|
||||
Items = users as List<User>,
|
||||
Email = string.IsNullOrWhiteSpace(email) ? null : email,
|
||||
Page = page,
|
||||
Count = count,
|
||||
Action = _globalSettings.SelfHosted ? "View" : "Edit"
|
||||
});
|
||||
}
|
||||
var user = await _userRepository.GetByIdAsync(id);
|
||||
if (user == null)
|
||||
{
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
public async Task<IActionResult> View(Guid id)
|
||||
{
|
||||
var user = await _userRepository.GetByIdAsync(id);
|
||||
if (user == null)
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(id);
|
||||
return View(new UserViewModel(user, ciphers));
|
||||
}
|
||||
|
||||
[SelfHosted(NotSelfHostedOnly = true)]
|
||||
public async Task<IActionResult> Edit(Guid id)
|
||||
{
|
||||
var user = await _userRepository.GetByIdAsync(id);
|
||||
if (user == null)
|
||||
{
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(id);
|
||||
var billingInfo = await _paymentService.GetBillingAsync(user);
|
||||
return View(new UserEditModel(user, ciphers, billingInfo, _globalSettings));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[SelfHosted(NotSelfHostedOnly = true)]
|
||||
public async Task<IActionResult> Edit(Guid id, UserEditModel model)
|
||||
{
|
||||
var user = await _userRepository.GetByIdAsync(id);
|
||||
if (user == null)
|
||||
{
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
model.ToUser(user);
|
||||
await _userRepository.ReplaceAsync(user);
|
||||
return RedirectToAction("Edit", new { id });
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Delete(Guid id)
|
||||
{
|
||||
var user = await _userRepository.GetByIdAsync(id);
|
||||
if (user != null)
|
||||
{
|
||||
await _userRepository.DeleteAsync(user);
|
||||
}
|
||||
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(id);
|
||||
return View(new UserViewModel(user, ciphers));
|
||||
}
|
||||
|
||||
[SelfHosted(NotSelfHostedOnly = true)]
|
||||
public async Task<IActionResult> Edit(Guid id)
|
||||
{
|
||||
var user = await _userRepository.GetByIdAsync(id);
|
||||
if (user == null)
|
||||
{
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(id);
|
||||
var billingInfo = await _paymentService.GetBillingAsync(user);
|
||||
return View(new UserEditModel(user, ciphers, billingInfo, _globalSettings));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[SelfHosted(NotSelfHostedOnly = true)]
|
||||
public async Task<IActionResult> Edit(Guid id, UserEditModel model)
|
||||
{
|
||||
var user = await _userRepository.GetByIdAsync(id);
|
||||
if (user == null)
|
||||
{
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
model.ToUser(user);
|
||||
await _userRepository.ReplaceAsync(user);
|
||||
return RedirectToAction("Edit", new { id });
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Delete(Guid id)
|
||||
{
|
||||
var user = await _userRepository.GetByIdAsync(id);
|
||||
if (user != null)
|
||||
{
|
||||
await _userRepository.DeleteAsync(user);
|
||||
}
|
||||
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
}
|
||||
|
@ -4,80 +4,81 @@ using Amazon.SQS.Model;
|
||||
using Bit.Core.Settings;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Bit.Admin.HostedServices;
|
||||
|
||||
public class AmazonSqsBlockIpHostedService : BlockIpHostedService
|
||||
namespace Bit.Admin.HostedServices
|
||||
{
|
||||
private AmazonSQSClient _client;
|
||||
|
||||
public AmazonSqsBlockIpHostedService(
|
||||
ILogger<AmazonSqsBlockIpHostedService> logger,
|
||||
IOptions<AdminSettings> adminSettings,
|
||||
GlobalSettings globalSettings)
|
||||
: base(logger, adminSettings, globalSettings)
|
||||
{ }
|
||||
|
||||
public override void Dispose()
|
||||
public class AmazonSqsBlockIpHostedService : BlockIpHostedService
|
||||
{
|
||||
_client?.Dispose();
|
||||
}
|
||||
private AmazonSQSClient _client;
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_client = new AmazonSQSClient(_globalSettings.Amazon.AccessKeyId,
|
||||
_globalSettings.Amazon.AccessKeySecret, RegionEndpoint.GetBySystemName(_globalSettings.Amazon.Region));
|
||||
var blockIpQueue = await _client.GetQueueUrlAsync("block-ip", cancellationToken);
|
||||
var blockIpQueueUrl = blockIpQueue.QueueUrl;
|
||||
var unblockIpQueue = await _client.GetQueueUrlAsync("unblock-ip", cancellationToken);
|
||||
var unblockIpQueueUrl = unblockIpQueue.QueueUrl;
|
||||
public AmazonSqsBlockIpHostedService(
|
||||
ILogger<AmazonSqsBlockIpHostedService> logger,
|
||||
IOptions<AdminSettings> adminSettings,
|
||||
GlobalSettings globalSettings)
|
||||
: base(logger, adminSettings, globalSettings)
|
||||
{ }
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
public override void Dispose()
|
||||
{
|
||||
var blockMessageResponse = await _client.ReceiveMessageAsync(new ReceiveMessageRequest
|
||||
{
|
||||
QueueUrl = blockIpQueueUrl,
|
||||
MaxNumberOfMessages = 10,
|
||||
WaitTimeSeconds = 15
|
||||
}, cancellationToken);
|
||||
if (blockMessageResponse.Messages.Any())
|
||||
{
|
||||
foreach (var message in blockMessageResponse.Messages)
|
||||
{
|
||||
try
|
||||
{
|
||||
await BlockIpAsync(message.Body, cancellationToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Failed to block IP.");
|
||||
}
|
||||
await _client.DeleteMessageAsync(blockIpQueueUrl, message.ReceiptHandle, cancellationToken);
|
||||
}
|
||||
}
|
||||
_client?.Dispose();
|
||||
}
|
||||
|
||||
var unblockMessageResponse = await _client.ReceiveMessageAsync(new ReceiveMessageRequest
|
||||
{
|
||||
QueueUrl = unblockIpQueueUrl,
|
||||
MaxNumberOfMessages = 10,
|
||||
WaitTimeSeconds = 15
|
||||
}, cancellationToken);
|
||||
if (unblockMessageResponse.Messages.Any())
|
||||
{
|
||||
foreach (var message in unblockMessageResponse.Messages)
|
||||
{
|
||||
try
|
||||
{
|
||||
await UnblockIpAsync(message.Body, cancellationToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Failed to unblock IP.");
|
||||
}
|
||||
await _client.DeleteMessageAsync(unblockIpQueueUrl, message.ReceiptHandle, cancellationToken);
|
||||
}
|
||||
}
|
||||
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_client = new AmazonSQSClient(_globalSettings.Amazon.AccessKeyId,
|
||||
_globalSettings.Amazon.AccessKeySecret, RegionEndpoint.GetBySystemName(_globalSettings.Amazon.Region));
|
||||
var blockIpQueue = await _client.GetQueueUrlAsync("block-ip", cancellationToken);
|
||||
var blockIpQueueUrl = blockIpQueue.QueueUrl;
|
||||
var unblockIpQueue = await _client.GetQueueUrlAsync("unblock-ip", cancellationToken);
|
||||
var unblockIpQueueUrl = unblockIpQueue.QueueUrl;
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(15));
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var blockMessageResponse = await _client.ReceiveMessageAsync(new ReceiveMessageRequest
|
||||
{
|
||||
QueueUrl = blockIpQueueUrl,
|
||||
MaxNumberOfMessages = 10,
|
||||
WaitTimeSeconds = 15
|
||||
}, cancellationToken);
|
||||
if (blockMessageResponse.Messages.Any())
|
||||
{
|
||||
foreach (var message in blockMessageResponse.Messages)
|
||||
{
|
||||
try
|
||||
{
|
||||
await BlockIpAsync(message.Body, cancellationToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Failed to block IP.");
|
||||
}
|
||||
await _client.DeleteMessageAsync(blockIpQueueUrl, message.ReceiptHandle, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
var unblockMessageResponse = await _client.ReceiveMessageAsync(new ReceiveMessageRequest
|
||||
{
|
||||
QueueUrl = unblockIpQueueUrl,
|
||||
MaxNumberOfMessages = 10,
|
||||
WaitTimeSeconds = 15
|
||||
}, cancellationToken);
|
||||
if (unblockMessageResponse.Messages.Any())
|
||||
{
|
||||
foreach (var message in unblockMessageResponse.Messages)
|
||||
{
|
||||
try
|
||||
{
|
||||
await UnblockIpAsync(message.Body, cancellationToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Failed to unblock IP.");
|
||||
}
|
||||
await _client.DeleteMessageAsync(unblockIpQueueUrl, message.ReceiptHandle, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(15));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,62 +2,63 @@
|
||||
using Bit.Core.Settings;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Bit.Admin.HostedServices;
|
||||
|
||||
public class AzureQueueBlockIpHostedService : BlockIpHostedService
|
||||
namespace Bit.Admin.HostedServices
|
||||
{
|
||||
private QueueClient _blockIpQueueClient;
|
||||
private QueueClient _unblockIpQueueClient;
|
||||
|
||||
public AzureQueueBlockIpHostedService(
|
||||
ILogger<AzureQueueBlockIpHostedService> logger,
|
||||
IOptions<AdminSettings> adminSettings,
|
||||
GlobalSettings globalSettings)
|
||||
: base(logger, adminSettings, globalSettings)
|
||||
{ }
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
|
||||
public class AzureQueueBlockIpHostedService : BlockIpHostedService
|
||||
{
|
||||
_blockIpQueueClient = new QueueClient(_globalSettings.Storage.ConnectionString, "blockip");
|
||||
_unblockIpQueueClient = new QueueClient(_globalSettings.Storage.ConnectionString, "unblockip");
|
||||
private QueueClient _blockIpQueueClient;
|
||||
private QueueClient _unblockIpQueueClient;
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
public AzureQueueBlockIpHostedService(
|
||||
ILogger<AzureQueueBlockIpHostedService> logger,
|
||||
IOptions<AdminSettings> adminSettings,
|
||||
GlobalSettings globalSettings)
|
||||
: base(logger, adminSettings, globalSettings)
|
||||
{ }
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var blockMessages = await _blockIpQueueClient.ReceiveMessagesAsync(maxMessages: 32);
|
||||
if (blockMessages.Value?.Any() ?? false)
|
||||
{
|
||||
foreach (var message in blockMessages.Value)
|
||||
{
|
||||
try
|
||||
{
|
||||
await BlockIpAsync(message.MessageText, cancellationToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Failed to block IP.");
|
||||
}
|
||||
await _blockIpQueueClient.DeleteMessageAsync(message.MessageId, message.PopReceipt);
|
||||
}
|
||||
}
|
||||
_blockIpQueueClient = new QueueClient(_globalSettings.Storage.ConnectionString, "blockip");
|
||||
_unblockIpQueueClient = new QueueClient(_globalSettings.Storage.ConnectionString, "unblockip");
|
||||
|
||||
var unblockMessages = await _unblockIpQueueClient.ReceiveMessagesAsync(maxMessages: 32);
|
||||
if (unblockMessages.Value?.Any() ?? false)
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
foreach (var message in unblockMessages.Value)
|
||||
var blockMessages = await _blockIpQueueClient.ReceiveMessagesAsync(maxMessages: 32);
|
||||
if (blockMessages.Value?.Any() ?? false)
|
||||
{
|
||||
try
|
||||
foreach (var message in blockMessages.Value)
|
||||
{
|
||||
await UnblockIpAsync(message.MessageText, cancellationToken);
|
||||
try
|
||||
{
|
||||
await BlockIpAsync(message.MessageText, cancellationToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Failed to block IP.");
|
||||
}
|
||||
await _blockIpQueueClient.DeleteMessageAsync(message.MessageId, message.PopReceipt);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Failed to unblock IP.");
|
||||
}
|
||||
await _unblockIpQueueClient.DeleteMessageAsync(message.MessageId, message.PopReceipt);
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(15));
|
||||
var unblockMessages = await _unblockIpQueueClient.ReceiveMessagesAsync(maxMessages: 32);
|
||||
if (unblockMessages.Value?.Any() ?? false)
|
||||
{
|
||||
foreach (var message in unblockMessages.Value)
|
||||
{
|
||||
try
|
||||
{
|
||||
await UnblockIpAsync(message.MessageText, cancellationToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Failed to unblock IP.");
|
||||
}
|
||||
await _unblockIpQueueClient.DeleteMessageAsync(message.MessageId, message.PopReceipt);
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(15));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,96 +6,97 @@ using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Admin.HostedServices;
|
||||
|
||||
public class AzureQueueMailHostedService : IHostedService
|
||||
namespace Bit.Admin.HostedServices
|
||||
{
|
||||
private readonly ILogger<AzureQueueMailHostedService> _logger;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IMailService _mailService;
|
||||
private CancellationTokenSource _cts;
|
||||
private Task _executingTask;
|
||||
|
||||
private QueueClient _mailQueueClient;
|
||||
|
||||
public AzureQueueMailHostedService(
|
||||
ILogger<AzureQueueMailHostedService> logger,
|
||||
IMailService mailService,
|
||||
GlobalSettings globalSettings)
|
||||
public class AzureQueueMailHostedService : IHostedService
|
||||
{
|
||||
_logger = logger;
|
||||
_mailService = mailService;
|
||||
_globalSettings = globalSettings;
|
||||
}
|
||||
private readonly ILogger<AzureQueueMailHostedService> _logger;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IMailService _mailService;
|
||||
private CancellationTokenSource _cts;
|
||||
private Task _executingTask;
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
_executingTask = ExecuteAsync(_cts.Token);
|
||||
return _executingTask.IsCompleted ? _executingTask : Task.CompletedTask;
|
||||
}
|
||||
private QueueClient _mailQueueClient;
|
||||
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_executingTask == null)
|
||||
public AzureQueueMailHostedService(
|
||||
ILogger<AzureQueueMailHostedService> logger,
|
||||
IMailService mailService,
|
||||
GlobalSettings globalSettings)
|
||||
{
|
||||
return;
|
||||
_logger = logger;
|
||||
_mailService = mailService;
|
||||
_globalSettings = globalSettings;
|
||||
}
|
||||
_cts.Cancel();
|
||||
await Task.WhenAny(_executingTask, Task.Delay(-1, cancellationToken));
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
private async Task ExecuteAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_mailQueueClient = new QueueClient(_globalSettings.Mail.ConnectionString, "mail");
|
||||
|
||||
QueueMessage[] mailMessages;
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (!(mailMessages = await RetrieveMessagesAsync()).Any())
|
||||
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
_executingTask = ExecuteAsync(_cts.Token);
|
||||
return _executingTask.IsCompleted ? _executingTask : Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_executingTask == null)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(15));
|
||||
return;
|
||||
}
|
||||
_cts.Cancel();
|
||||
await Task.WhenAny(_executingTask, Task.Delay(-1, cancellationToken));
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
foreach (var message in mailMessages)
|
||||
private async Task ExecuteAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_mailQueueClient = new QueueClient(_globalSettings.Mail.ConnectionString, "mail");
|
||||
|
||||
QueueMessage[] mailMessages;
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
if (!(mailMessages = await RetrieveMessagesAsync()).Any())
|
||||
{
|
||||
using var document = JsonDocument.Parse(message.DecodeMessageText());
|
||||
var root = document.RootElement;
|
||||
await Task.Delay(TimeSpan.FromSeconds(15));
|
||||
}
|
||||
|
||||
if (root.ValueKind == JsonValueKind.Array)
|
||||
foreach (var message in mailMessages)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var mailQueueMessage in root.ToObject<List<MailQueueMessage>>())
|
||||
using var document = JsonDocument.Parse(message.DecodeMessageText());
|
||||
var root = document.RootElement;
|
||||
|
||||
if (root.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
foreach (var mailQueueMessage in root.ToObject<List<MailQueueMessage>>())
|
||||
{
|
||||
await _mailService.SendEnqueuedMailMessageAsync(mailQueueMessage);
|
||||
}
|
||||
}
|
||||
else if (root.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
var mailQueueMessage = root.ToObject<MailQueueMessage>();
|
||||
await _mailService.SendEnqueuedMailMessageAsync(mailQueueMessage);
|
||||
}
|
||||
}
|
||||
else if (root.ValueKind == JsonValueKind.Object)
|
||||
catch (Exception e)
|
||||
{
|
||||
var mailQueueMessage = root.ToObject<MailQueueMessage>();
|
||||
await _mailService.SendEnqueuedMailMessageAsync(mailQueueMessage);
|
||||
_logger.LogError(e, "Failed to send email");
|
||||
// TODO: retries?
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Failed to send email");
|
||||
// TODO: retries?
|
||||
}
|
||||
|
||||
await _mailQueueClient.DeleteMessageAsync(message.MessageId, message.PopReceipt);
|
||||
await _mailQueueClient.DeleteMessageAsync(message.MessageId, message.PopReceipt);
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
break;
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<QueueMessage[]> RetrieveMessagesAsync()
|
||||
{
|
||||
return (await _mailQueueClient.ReceiveMessagesAsync(maxMessages: 32))?.Value ?? new QueueMessage[] { };
|
||||
private async Task<QueueMessage[]> RetrieveMessagesAsync()
|
||||
{
|
||||
return (await _mailQueueClient.ReceiveMessagesAsync(maxMessages: 32))?.Value ?? new QueueMessage[] { };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,105 +1,71 @@
|
||||
using Bit.Core.Settings;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Bit.Admin.HostedServices;
|
||||
|
||||
public abstract class BlockIpHostedService : IHostedService, IDisposable
|
||||
namespace Bit.Admin.HostedServices
|
||||
{
|
||||
protected readonly ILogger<BlockIpHostedService> _logger;
|
||||
protected readonly GlobalSettings _globalSettings;
|
||||
private readonly AdminSettings _adminSettings;
|
||||
|
||||
private Task _executingTask;
|
||||
private CancellationTokenSource _cts;
|
||||
private HttpClient _httpClient = new HttpClient();
|
||||
|
||||
public BlockIpHostedService(
|
||||
ILogger<BlockIpHostedService> logger,
|
||||
IOptions<AdminSettings> adminSettings,
|
||||
GlobalSettings globalSettings)
|
||||
public abstract class BlockIpHostedService : IHostedService, IDisposable
|
||||
{
|
||||
_logger = logger;
|
||||
_globalSettings = globalSettings;
|
||||
_adminSettings = adminSettings?.Value;
|
||||
}
|
||||
protected readonly ILogger<BlockIpHostedService> _logger;
|
||||
protected readonly GlobalSettings _globalSettings;
|
||||
private readonly AdminSettings _adminSettings;
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
_executingTask = ExecuteAsync(_cts.Token);
|
||||
return _executingTask.IsCompleted ? _executingTask : Task.CompletedTask;
|
||||
}
|
||||
private Task _executingTask;
|
||||
private CancellationTokenSource _cts;
|
||||
private HttpClient _httpClient = new HttpClient();
|
||||
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_executingTask == null)
|
||||
public BlockIpHostedService(
|
||||
ILogger<BlockIpHostedService> logger,
|
||||
IOptions<AdminSettings> adminSettings,
|
||||
GlobalSettings globalSettings)
|
||||
{
|
||||
return;
|
||||
_logger = logger;
|
||||
_globalSettings = globalSettings;
|
||||
_adminSettings = adminSettings?.Value;
|
||||
}
|
||||
_cts.Cancel();
|
||||
await Task.WhenAny(_executingTask, Task.Delay(-1, cancellationToken));
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{ }
|
||||
|
||||
protected abstract Task ExecuteAsync(CancellationToken cancellationToken);
|
||||
|
||||
protected async Task BlockIpAsync(string message, CancellationToken cancellationToken)
|
||||
{
|
||||
var request = new HttpRequestMessage();
|
||||
request.Headers.Accept.Clear();
|
||||
request.Headers.Add("X-Auth-Email", _adminSettings.Cloudflare.AuthEmail);
|
||||
request.Headers.Add("X-Auth-Key", _adminSettings.Cloudflare.AuthKey);
|
||||
request.Method = HttpMethod.Post;
|
||||
request.RequestUri = new Uri("https://api.cloudflare.com/" +
|
||||
$"client/v4/zones/{_adminSettings.Cloudflare.ZoneId}/firewall/access_rules/rules");
|
||||
|
||||
request.Content = JsonContent.Create(new
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
mode = "block",
|
||||
configuration = new
|
||||
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
_executingTask = ExecuteAsync(_cts.Token);
|
||||
return _executingTask.IsCompleted ? _executingTask : Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_executingTask == null)
|
||||
{
|
||||
target = "ip",
|
||||
value = message
|
||||
},
|
||||
notes = $"Rate limit abuse on {DateTime.UtcNow.ToString()}."
|
||||
});
|
||||
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return;
|
||||
return;
|
||||
}
|
||||
_cts.Cancel();
|
||||
await Task.WhenAny(_executingTask, Task.Delay(-1, cancellationToken));
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
var accessRuleResponse = await response.Content.ReadFromJsonAsync<AccessRuleResponse>(cancellationToken: cancellationToken);
|
||||
if (!accessRuleResponse.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
public virtual void Dispose()
|
||||
{ }
|
||||
|
||||
// TODO: Send `accessRuleResponse.Result?.Id` message to unblock queue
|
||||
}
|
||||
protected abstract Task ExecuteAsync(CancellationToken cancellationToken);
|
||||
|
||||
protected async Task UnblockIpAsync(string message, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
protected async Task BlockIpAsync(string message, CancellationToken cancellationToken)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.Contains(".") || message.Contains(":"))
|
||||
{
|
||||
// IP address messages
|
||||
var request = new HttpRequestMessage();
|
||||
request.Headers.Accept.Clear();
|
||||
request.Headers.Add("X-Auth-Email", _adminSettings.Cloudflare.AuthEmail);
|
||||
request.Headers.Add("X-Auth-Key", _adminSettings.Cloudflare.AuthKey);
|
||||
request.Method = HttpMethod.Get;
|
||||
request.Method = HttpMethod.Post;
|
||||
request.RequestUri = new Uri("https://api.cloudflare.com/" +
|
||||
$"client/v4/zones/{_adminSettings.Cloudflare.ZoneId}/firewall/access_rules/rules?" +
|
||||
$"configuration_target=ip&configuration_value={message}");
|
||||
$"client/v4/zones/{_adminSettings.Cloudflare.ZoneId}/firewall/access_rules/rules");
|
||||
|
||||
request.Content = JsonContent.Create(new
|
||||
{
|
||||
mode = "block",
|
||||
configuration = new
|
||||
{
|
||||
target = "ip",
|
||||
value = message
|
||||
},
|
||||
notes = $"Rate limit abuse on {DateTime.UtcNow.ToString()}."
|
||||
});
|
||||
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
@ -107,58 +73,93 @@ public abstract class BlockIpHostedService : IHostedService, IDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
var listResponse = await response.Content.ReadFromJsonAsync<ListResponse>(cancellationToken: cancellationToken);
|
||||
if (!listResponse.Success)
|
||||
var accessRuleResponse = await response.Content.ReadFromJsonAsync<AccessRuleResponse>(cancellationToken: cancellationToken);
|
||||
if (!accessRuleResponse.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var rule in listResponse.Result)
|
||||
// TODO: Send `accessRuleResponse.Result?.Id` message to unblock queue
|
||||
}
|
||||
|
||||
protected async Task UnblockIpAsync(string message, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
{
|
||||
await DeleteAccessRuleAsync(rule.Id, cancellationToken);
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.Contains(".") || message.Contains(":"))
|
||||
{
|
||||
// IP address messages
|
||||
var request = new HttpRequestMessage();
|
||||
request.Headers.Accept.Clear();
|
||||
request.Headers.Add("X-Auth-Email", _adminSettings.Cloudflare.AuthEmail);
|
||||
request.Headers.Add("X-Auth-Key", _adminSettings.Cloudflare.AuthKey);
|
||||
request.Method = HttpMethod.Get;
|
||||
request.RequestUri = new Uri("https://api.cloudflare.com/" +
|
||||
$"client/v4/zones/{_adminSettings.Cloudflare.ZoneId}/firewall/access_rules/rules?" +
|
||||
$"configuration_target=ip&configuration_value={message}");
|
||||
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var listResponse = await response.Content.ReadFromJsonAsync<ListResponse>(cancellationToken: cancellationToken);
|
||||
if (!listResponse.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var rule in listResponse.Result)
|
||||
{
|
||||
await DeleteAccessRuleAsync(rule.Id, cancellationToken);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Rule Id messages
|
||||
await DeleteAccessRuleAsync(message, cancellationToken);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
protected async Task DeleteAccessRuleAsync(string ruleId, CancellationToken cancellationToken)
|
||||
{
|
||||
// Rule Id messages
|
||||
await DeleteAccessRuleAsync(message, cancellationToken);
|
||||
var request = new HttpRequestMessage();
|
||||
request.Headers.Accept.Clear();
|
||||
request.Headers.Add("X-Auth-Email", _adminSettings.Cloudflare.AuthEmail);
|
||||
request.Headers.Add("X-Auth-Key", _adminSettings.Cloudflare.AuthKey);
|
||||
request.Method = HttpMethod.Delete;
|
||||
request.RequestUri = new Uri("https://api.cloudflare.com/" +
|
||||
$"client/v4/zones/{_adminSettings.Cloudflare.ZoneId}/firewall/access_rules/rules/{ruleId}");
|
||||
await _httpClient.SendAsync(request, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task DeleteAccessRuleAsync(string ruleId, CancellationToken cancellationToken)
|
||||
{
|
||||
var request = new HttpRequestMessage();
|
||||
request.Headers.Accept.Clear();
|
||||
request.Headers.Add("X-Auth-Email", _adminSettings.Cloudflare.AuthEmail);
|
||||
request.Headers.Add("X-Auth-Key", _adminSettings.Cloudflare.AuthKey);
|
||||
request.Method = HttpMethod.Delete;
|
||||
request.RequestUri = new Uri("https://api.cloudflare.com/" +
|
||||
$"client/v4/zones/{_adminSettings.Cloudflare.ZoneId}/firewall/access_rules/rules/{ruleId}");
|
||||
await _httpClient.SendAsync(request, cancellationToken);
|
||||
}
|
||||
|
||||
public class ListResponse
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public List<AccessRuleResultResponse> Result { get; set; }
|
||||
}
|
||||
|
||||
public class AccessRuleResponse
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public AccessRuleResultResponse Result { get; set; }
|
||||
}
|
||||
|
||||
public class AccessRuleResultResponse
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public ConfigurationResponse Configuration { get; set; }
|
||||
|
||||
public class ConfigurationResponse
|
||||
public class ListResponse
|
||||
{
|
||||
public string Target { get; set; }
|
||||
public string Value { get; set; }
|
||||
public bool Success { get; set; }
|
||||
public List<AccessRuleResultResponse> Result { get; set; }
|
||||
}
|
||||
|
||||
public class AccessRuleResponse
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public AccessRuleResultResponse Result { get; set; }
|
||||
}
|
||||
|
||||
public class AccessRuleResultResponse
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public ConfigurationResponse Configuration { get; set; }
|
||||
|
||||
public class ConfigurationResponse
|
||||
{
|
||||
public string Target { get; set; }
|
||||
public string Value { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,61 +3,62 @@ using Bit.Core.Jobs;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Migrator;
|
||||
|
||||
namespace Bit.Admin.HostedServices;
|
||||
|
||||
public class DatabaseMigrationHostedService : IHostedService, IDisposable
|
||||
namespace Bit.Admin.HostedServices
|
||||
{
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly ILogger<DatabaseMigrationHostedService> _logger;
|
||||
private readonly DbMigrator _dbMigrator;
|
||||
|
||||
public DatabaseMigrationHostedService(
|
||||
GlobalSettings globalSettings,
|
||||
ILogger<DatabaseMigrationHostedService> logger,
|
||||
ILogger<DbMigrator> migratorLogger,
|
||||
ILogger<JobListener> listenerLogger)
|
||||
public class DatabaseMigrationHostedService : IHostedService, IDisposable
|
||||
{
|
||||
_globalSettings = globalSettings;
|
||||
_logger = logger;
|
||||
_dbMigrator = new DbMigrator(globalSettings.SqlServer.ConnectionString, migratorLogger);
|
||||
}
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly ILogger<DatabaseMigrationHostedService> _logger;
|
||||
private readonly DbMigrator _dbMigrator;
|
||||
|
||||
public virtual async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// Wait 20 seconds to allow database to come online
|
||||
await Task.Delay(20000);
|
||||
|
||||
var maxMigrationAttempts = 10;
|
||||
for (var i = 1; i <= maxMigrationAttempts; i++)
|
||||
public DatabaseMigrationHostedService(
|
||||
GlobalSettings globalSettings,
|
||||
ILogger<DatabaseMigrationHostedService> logger,
|
||||
ILogger<DbMigrator> migratorLogger,
|
||||
ILogger<JobListener> listenerLogger)
|
||||
{
|
||||
try
|
||||
_globalSettings = globalSettings;
|
||||
_logger = logger;
|
||||
_dbMigrator = new DbMigrator(globalSettings.SqlServer.ConnectionString, migratorLogger);
|
||||
}
|
||||
|
||||
public virtual async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// Wait 20 seconds to allow database to come online
|
||||
await Task.Delay(20000);
|
||||
|
||||
var maxMigrationAttempts = 10;
|
||||
for (var i = 1; i <= maxMigrationAttempts; i++)
|
||||
{
|
||||
_dbMigrator.MigrateMsSqlDatabase(true, cancellationToken);
|
||||
// TODO: Maybe flip a flag somewhere to indicate migration is complete??
|
||||
break;
|
||||
}
|
||||
catch (SqlException e)
|
||||
{
|
||||
if (i >= maxMigrationAttempts)
|
||||
try
|
||||
{
|
||||
_logger.LogError(e, "Database failed to migrate.");
|
||||
throw;
|
||||
_dbMigrator.MigrateMsSqlDatabase(true, cancellationToken);
|
||||
// TODO: Maybe flip a flag somewhere to indicate migration is complete??
|
||||
break;
|
||||
}
|
||||
else
|
||||
catch (SqlException e)
|
||||
{
|
||||
_logger.LogError(e,
|
||||
"Database unavailable for migration. Trying again (attempt #{0})...", i + 1);
|
||||
await Task.Delay(20000);
|
||||
if (i >= maxMigrationAttempts)
|
||||
{
|
||||
_logger.LogError(e, "Database failed to migrate.");
|
||||
throw;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogError(e,
|
||||
"Database unavailable for migration. Trying again (attempt #{0})...", i + 1);
|
||||
await Task.Delay(20000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
public virtual Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{ }
|
||||
public virtual void Dispose()
|
||||
{ }
|
||||
}
|
||||
}
|
||||
|
@ -3,26 +3,27 @@ using Bit.Core.Jobs;
|
||||
using Bit.Core.Settings;
|
||||
using Quartz;
|
||||
|
||||
namespace Bit.Admin.Jobs;
|
||||
|
||||
public class AliveJob : BaseJob
|
||||
namespace Bit.Admin.Jobs
|
||||
{
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private HttpClient _httpClient = new HttpClient();
|
||||
|
||||
public AliveJob(
|
||||
GlobalSettings globalSettings,
|
||||
ILogger<AliveJob> logger)
|
||||
: base(logger)
|
||||
public class AliveJob : BaseJob
|
||||
{
|
||||
_globalSettings = globalSettings;
|
||||
}
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private HttpClient _httpClient = new HttpClient();
|
||||
|
||||
protected async override Task ExecuteJobAsync(IJobExecutionContext context)
|
||||
{
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: Keep alive");
|
||||
var response = await _httpClient.GetAsync(_globalSettings.BaseServiceUri.Admin);
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Finished job task: Keep alive, " +
|
||||
response.StatusCode);
|
||||
public AliveJob(
|
||||
GlobalSettings globalSettings,
|
||||
ILogger<AliveJob> logger)
|
||||
: base(logger)
|
||||
{
|
||||
_globalSettings = globalSettings;
|
||||
}
|
||||
|
||||
protected async override Task ExecuteJobAsync(IJobExecutionContext context)
|
||||
{
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: Keep alive");
|
||||
var response = await _httpClient.GetAsync(_globalSettings.BaseServiceUri.Admin);
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Finished job task: Keep alive, " +
|
||||
response.StatusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,24 +3,25 @@ using Bit.Core.Jobs;
|
||||
using Bit.Core.Repositories;
|
||||
using Quartz;
|
||||
|
||||
namespace Bit.Admin.Jobs;
|
||||
|
||||
public class DatabaseExpiredGrantsJob : BaseJob
|
||||
namespace Bit.Admin.Jobs
|
||||
{
|
||||
private readonly IMaintenanceRepository _maintenanceRepository;
|
||||
|
||||
public DatabaseExpiredGrantsJob(
|
||||
IMaintenanceRepository maintenanceRepository,
|
||||
ILogger<DatabaseExpiredGrantsJob> logger)
|
||||
: base(logger)
|
||||
public class DatabaseExpiredGrantsJob : BaseJob
|
||||
{
|
||||
_maintenanceRepository = maintenanceRepository;
|
||||
}
|
||||
private readonly IMaintenanceRepository _maintenanceRepository;
|
||||
|
||||
protected async override Task ExecuteJobAsync(IJobExecutionContext context)
|
||||
{
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: DeleteExpiredGrantsAsync");
|
||||
await _maintenanceRepository.DeleteExpiredGrantsAsync();
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Finished job task: DeleteExpiredGrantsAsync");
|
||||
public DatabaseExpiredGrantsJob(
|
||||
IMaintenanceRepository maintenanceRepository,
|
||||
ILogger<DatabaseExpiredGrantsJob> logger)
|
||||
: base(logger)
|
||||
{
|
||||
_maintenanceRepository = maintenanceRepository;
|
||||
}
|
||||
|
||||
protected async override Task ExecuteJobAsync(IJobExecutionContext context)
|
||||
{
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: DeleteExpiredGrantsAsync");
|
||||
await _maintenanceRepository.DeleteExpiredGrantsAsync();
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Finished job task: DeleteExpiredGrantsAsync");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,35 +4,36 @@ using Bit.Core.Repositories;
|
||||
using Bit.Core.Settings;
|
||||
using Quartz;
|
||||
|
||||
namespace Bit.Admin.Jobs;
|
||||
|
||||
public class DatabaseExpiredSponsorshipsJob : BaseJob
|
||||
namespace Bit.Admin.Jobs
|
||||
{
|
||||
private GlobalSettings _globalSettings;
|
||||
private readonly IMaintenanceRepository _maintenanceRepository;
|
||||
|
||||
public DatabaseExpiredSponsorshipsJob(
|
||||
IMaintenanceRepository maintenanceRepository,
|
||||
ILogger<DatabaseExpiredSponsorshipsJob> logger,
|
||||
GlobalSettings globalSettings)
|
||||
: base(logger)
|
||||
public class DatabaseExpiredSponsorshipsJob : BaseJob
|
||||
{
|
||||
_maintenanceRepository = maintenanceRepository;
|
||||
_globalSettings = globalSettings;
|
||||
}
|
||||
private GlobalSettings _globalSettings;
|
||||
private readonly IMaintenanceRepository _maintenanceRepository;
|
||||
|
||||
protected override async Task ExecuteJobAsync(IJobExecutionContext context)
|
||||
{
|
||||
if (_globalSettings.SelfHosted && !_globalSettings.EnableCloudCommunication)
|
||||
public DatabaseExpiredSponsorshipsJob(
|
||||
IMaintenanceRepository maintenanceRepository,
|
||||
ILogger<DatabaseExpiredSponsorshipsJob> logger,
|
||||
GlobalSettings globalSettings)
|
||||
: base(logger)
|
||||
{
|
||||
return;
|
||||
_maintenanceRepository = maintenanceRepository;
|
||||
_globalSettings = globalSettings;
|
||||
}
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: DeleteExpiredSponsorshipsAsync");
|
||||
|
||||
// allow a 90 day grace period before deleting
|
||||
var deleteDate = DateTime.UtcNow.AddDays(-90);
|
||||
protected override async Task ExecuteJobAsync(IJobExecutionContext context)
|
||||
{
|
||||
if (_globalSettings.SelfHosted && !_globalSettings.EnableCloudCommunication)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: DeleteExpiredSponsorshipsAsync");
|
||||
|
||||
await _maintenanceRepository.DeleteExpiredSponsorshipsAsync(deleteDate);
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Finished job task: DeleteExpiredSponsorshipsAsync");
|
||||
// allow a 90 day grace period before deleting
|
||||
var deleteDate = DateTime.UtcNow.AddDays(-90);
|
||||
|
||||
await _maintenanceRepository.DeleteExpiredSponsorshipsAsync(deleteDate);
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Finished job task: DeleteExpiredSponsorshipsAsync");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,24 +3,25 @@ using Bit.Core.Jobs;
|
||||
using Bit.Core.Repositories;
|
||||
using Quartz;
|
||||
|
||||
namespace Bit.Admin.Jobs;
|
||||
|
||||
public class DatabaseRebuildlIndexesJob : BaseJob
|
||||
namespace Bit.Admin.Jobs
|
||||
{
|
||||
private readonly IMaintenanceRepository _maintenanceRepository;
|
||||
|
||||
public DatabaseRebuildlIndexesJob(
|
||||
IMaintenanceRepository maintenanceRepository,
|
||||
ILogger<DatabaseRebuildlIndexesJob> logger)
|
||||
: base(logger)
|
||||
public class DatabaseRebuildlIndexesJob : BaseJob
|
||||
{
|
||||
_maintenanceRepository = maintenanceRepository;
|
||||
}
|
||||
private readonly IMaintenanceRepository _maintenanceRepository;
|
||||
|
||||
protected async override Task ExecuteJobAsync(IJobExecutionContext context)
|
||||
{
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: RebuildIndexesAsync");
|
||||
await _maintenanceRepository.RebuildIndexesAsync();
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Finished job task: RebuildIndexesAsync");
|
||||
public DatabaseRebuildlIndexesJob(
|
||||
IMaintenanceRepository maintenanceRepository,
|
||||
ILogger<DatabaseRebuildlIndexesJob> logger)
|
||||
: base(logger)
|
||||
{
|
||||
_maintenanceRepository = maintenanceRepository;
|
||||
}
|
||||
|
||||
protected async override Task ExecuteJobAsync(IJobExecutionContext context)
|
||||
{
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: RebuildIndexesAsync");
|
||||
await _maintenanceRepository.RebuildIndexesAsync();
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Finished job task: RebuildIndexesAsync");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,27 +3,28 @@ using Bit.Core.Jobs;
|
||||
using Bit.Core.Repositories;
|
||||
using Quartz;
|
||||
|
||||
namespace Bit.Admin.Jobs;
|
||||
|
||||
public class DatabaseUpdateStatisticsJob : BaseJob
|
||||
namespace Bit.Admin.Jobs
|
||||
{
|
||||
private readonly IMaintenanceRepository _maintenanceRepository;
|
||||
|
||||
public DatabaseUpdateStatisticsJob(
|
||||
IMaintenanceRepository maintenanceRepository,
|
||||
ILogger<DatabaseUpdateStatisticsJob> logger)
|
||||
: base(logger)
|
||||
public class DatabaseUpdateStatisticsJob : BaseJob
|
||||
{
|
||||
_maintenanceRepository = maintenanceRepository;
|
||||
}
|
||||
private readonly IMaintenanceRepository _maintenanceRepository;
|
||||
|
||||
protected async override Task ExecuteJobAsync(IJobExecutionContext context)
|
||||
{
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: UpdateStatisticsAsync");
|
||||
await _maintenanceRepository.UpdateStatisticsAsync();
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Finished job task: UpdateStatisticsAsync");
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: DisableCipherAutoStatsAsync");
|
||||
await _maintenanceRepository.DisableCipherAutoStatsAsync();
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Finished job task: DisableCipherAutoStatsAsync");
|
||||
public DatabaseUpdateStatisticsJob(
|
||||
IMaintenanceRepository maintenanceRepository,
|
||||
ILogger<DatabaseUpdateStatisticsJob> logger)
|
||||
: base(logger)
|
||||
{
|
||||
_maintenanceRepository = maintenanceRepository;
|
||||
}
|
||||
|
||||
protected async override Task ExecuteJobAsync(IJobExecutionContext context)
|
||||
{
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: UpdateStatisticsAsync");
|
||||
await _maintenanceRepository.UpdateStatisticsAsync();
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Finished job task: UpdateStatisticsAsync");
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: DisableCipherAutoStatsAsync");
|
||||
await _maintenanceRepository.DisableCipherAutoStatsAsync();
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Finished job task: DisableCipherAutoStatsAsync");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,33 +4,34 @@ using Bit.Core.Repositories;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Quartz;
|
||||
|
||||
namespace Bit.Admin.Jobs;
|
||||
|
||||
public class DeleteCiphersJob : BaseJob
|
||||
namespace Bit.Admin.Jobs
|
||||
{
|
||||
private readonly ICipherRepository _cipherRepository;
|
||||
private readonly AdminSettings _adminSettings;
|
||||
|
||||
public DeleteCiphersJob(
|
||||
ICipherRepository cipherRepository,
|
||||
IOptions<AdminSettings> adminSettings,
|
||||
ILogger<DeleteCiphersJob> logger)
|
||||
: base(logger)
|
||||
public class DeleteCiphersJob : BaseJob
|
||||
{
|
||||
_cipherRepository = cipherRepository;
|
||||
_adminSettings = adminSettings?.Value;
|
||||
}
|
||||
private readonly ICipherRepository _cipherRepository;
|
||||
private readonly AdminSettings _adminSettings;
|
||||
|
||||
protected async override Task ExecuteJobAsync(IJobExecutionContext context)
|
||||
{
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: DeleteDeletedAsync");
|
||||
var deleteDate = DateTime.UtcNow.AddDays(-30);
|
||||
var daysAgoSetting = (_adminSettings?.DeleteTrashDaysAgo).GetValueOrDefault();
|
||||
if (daysAgoSetting > 0)
|
||||
public DeleteCiphersJob(
|
||||
ICipherRepository cipherRepository,
|
||||
IOptions<AdminSettings> adminSettings,
|
||||
ILogger<DeleteCiphersJob> logger)
|
||||
: base(logger)
|
||||
{
|
||||
deleteDate = DateTime.UtcNow.AddDays(-1 * daysAgoSetting);
|
||||
_cipherRepository = cipherRepository;
|
||||
_adminSettings = adminSettings?.Value;
|
||||
}
|
||||
|
||||
protected async override Task ExecuteJobAsync(IJobExecutionContext context)
|
||||
{
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: DeleteDeletedAsync");
|
||||
var deleteDate = DateTime.UtcNow.AddDays(-30);
|
||||
var daysAgoSetting = (_adminSettings?.DeleteTrashDaysAgo).GetValueOrDefault();
|
||||
if (daysAgoSetting > 0)
|
||||
{
|
||||
deleteDate = DateTime.UtcNow.AddDays(-1 * daysAgoSetting);
|
||||
}
|
||||
await _cipherRepository.DeleteDeletedAsync(deleteDate);
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Finished job task: DeleteDeletedAsync");
|
||||
}
|
||||
await _cipherRepository.DeleteDeletedAsync(deleteDate);
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Finished job task: DeleteDeletedAsync");
|
||||
}
|
||||
}
|
||||
|
@ -4,37 +4,38 @@ using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Quartz;
|
||||
|
||||
namespace Bit.Admin.Jobs;
|
||||
|
||||
public class DeleteSendsJob : BaseJob
|
||||
namespace Bit.Admin.Jobs
|
||||
{
|
||||
private readonly ISendRepository _sendRepository;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public DeleteSendsJob(
|
||||
ISendRepository sendRepository,
|
||||
IServiceProvider serviceProvider,
|
||||
ILogger<DatabaseExpiredGrantsJob> logger)
|
||||
: base(logger)
|
||||
public class DeleteSendsJob : BaseJob
|
||||
{
|
||||
_sendRepository = sendRepository;
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
private readonly ISendRepository _sendRepository;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
protected async override Task ExecuteJobAsync(IJobExecutionContext context)
|
||||
{
|
||||
var sends = await _sendRepository.GetManyByDeletionDateAsync(DateTime.UtcNow);
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Deleting {0} sends.", sends.Count);
|
||||
if (!sends.Any())
|
||||
public DeleteSendsJob(
|
||||
ISendRepository sendRepository,
|
||||
IServiceProvider serviceProvider,
|
||||
ILogger<DatabaseExpiredGrantsJob> logger)
|
||||
: base(logger)
|
||||
{
|
||||
return;
|
||||
_sendRepository = sendRepository;
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
using (var scope = _serviceProvider.CreateScope())
|
||||
|
||||
protected async override Task ExecuteJobAsync(IJobExecutionContext context)
|
||||
{
|
||||
var sendService = scope.ServiceProvider.GetRequiredService<ISendService>();
|
||||
foreach (var send in sends)
|
||||
var sends = await _sendRepository.GetManyByDeletionDateAsync(DateTime.UtcNow);
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Deleting {0} sends.", sends.Count);
|
||||
if (!sends.Any())
|
||||
{
|
||||
await sendService.DeleteSendAsync(send);
|
||||
return;
|
||||
}
|
||||
using (var scope = _serviceProvider.CreateScope())
|
||||
{
|
||||
var sendService = scope.ServiceProvider.GetRequiredService<ISendService>();
|
||||
foreach (var send in sends)
|
||||
{
|
||||
await sendService.DeleteSendAsync(send);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,93 +3,94 @@ using Bit.Core.Jobs;
|
||||
using Bit.Core.Settings;
|
||||
using Quartz;
|
||||
|
||||
namespace Bit.Admin.Jobs;
|
||||
|
||||
public class JobsHostedService : BaseJobsHostedService
|
||||
namespace Bit.Admin.Jobs
|
||||
{
|
||||
public JobsHostedService(
|
||||
GlobalSettings globalSettings,
|
||||
IServiceProvider serviceProvider,
|
||||
ILogger<JobsHostedService> logger,
|
||||
ILogger<JobListener> listenerLogger)
|
||||
: base(globalSettings, serviceProvider, logger, listenerLogger) { }
|
||||
|
||||
public override async Task StartAsync(CancellationToken cancellationToken)
|
||||
public class JobsHostedService : BaseJobsHostedService
|
||||
{
|
||||
var timeZone = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
|
||||
TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time") :
|
||||
TimeZoneInfo.FindSystemTimeZoneById("America/New_York");
|
||||
if (_globalSettings.SelfHosted)
|
||||
public JobsHostedService(
|
||||
GlobalSettings globalSettings,
|
||||
IServiceProvider serviceProvider,
|
||||
ILogger<JobsHostedService> logger,
|
||||
ILogger<JobListener> listenerLogger)
|
||||
: base(globalSettings, serviceProvider, logger, listenerLogger) { }
|
||||
|
||||
public override async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
timeZone = TimeZoneInfo.Local;
|
||||
var timeZone = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
|
||||
TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time") :
|
||||
TimeZoneInfo.FindSystemTimeZoneById("America/New_York");
|
||||
if (_globalSettings.SelfHosted)
|
||||
{
|
||||
timeZone = TimeZoneInfo.Local;
|
||||
}
|
||||
|
||||
var everyTopOfTheHourTrigger = TriggerBuilder.Create()
|
||||
.WithIdentity("EveryTopOfTheHourTrigger")
|
||||
.StartNow()
|
||||
.WithCronSchedule("0 0 * * * ?")
|
||||
.Build();
|
||||
var everyFiveMinutesTrigger = TriggerBuilder.Create()
|
||||
.WithIdentity("EveryFiveMinutesTrigger")
|
||||
.StartNow()
|
||||
.WithCronSchedule("0 */5 * * * ?")
|
||||
.Build();
|
||||
var everyFridayAt10pmTrigger = TriggerBuilder.Create()
|
||||
.WithIdentity("EveryFridayAt10pmTrigger")
|
||||
.StartNow()
|
||||
.WithCronSchedule("0 0 22 ? * FRI", x => x.InTimeZone(timeZone))
|
||||
.Build();
|
||||
var everySaturdayAtMidnightTrigger = TriggerBuilder.Create()
|
||||
.WithIdentity("EverySaturdayAtMidnightTrigger")
|
||||
.StartNow()
|
||||
.WithCronSchedule("0 0 0 ? * SAT", x => x.InTimeZone(timeZone))
|
||||
.Build();
|
||||
var everySundayAtMidnightTrigger = TriggerBuilder.Create()
|
||||
.WithIdentity("EverySundayAtMidnightTrigger")
|
||||
.StartNow()
|
||||
.WithCronSchedule("0 0 0 ? * SUN", x => x.InTimeZone(timeZone))
|
||||
.Build();
|
||||
var everyMondayAtMidnightTrigger = TriggerBuilder.Create()
|
||||
.WithIdentity("EveryMondayAtMidnightTrigger")
|
||||
.StartNow()
|
||||
.WithCronSchedule("0 0 0 ? * MON", x => x.InTimeZone(timeZone))
|
||||
.Build();
|
||||
var everyDayAtMidnightUtc = TriggerBuilder.Create()
|
||||
.WithIdentity("EveryDayAtMidnightUtc")
|
||||
.StartNow()
|
||||
.WithCronSchedule("0 0 0 * * ?")
|
||||
.Build();
|
||||
|
||||
var jobs = new List<Tuple<Type, ITrigger>>
|
||||
{
|
||||
new Tuple<Type, ITrigger>(typeof(DeleteSendsJob), everyFiveMinutesTrigger),
|
||||
new Tuple<Type, ITrigger>(typeof(DatabaseExpiredGrantsJob), everyFridayAt10pmTrigger),
|
||||
new Tuple<Type, ITrigger>(typeof(DatabaseUpdateStatisticsJob), everySaturdayAtMidnightTrigger),
|
||||
new Tuple<Type, ITrigger>(typeof(DatabaseRebuildlIndexesJob), everySundayAtMidnightTrigger),
|
||||
new Tuple<Type, ITrigger>(typeof(DeleteCiphersJob), everyDayAtMidnightUtc),
|
||||
new Tuple<Type, ITrigger>(typeof(DatabaseExpiredSponsorshipsJob), everyMondayAtMidnightTrigger)
|
||||
};
|
||||
|
||||
if (!_globalSettings.SelfHosted)
|
||||
{
|
||||
jobs.Add(new Tuple<Type, ITrigger>(typeof(AliveJob), everyTopOfTheHourTrigger));
|
||||
}
|
||||
|
||||
Jobs = jobs;
|
||||
await base.StartAsync(cancellationToken);
|
||||
}
|
||||
|
||||
var everyTopOfTheHourTrigger = TriggerBuilder.Create()
|
||||
.WithIdentity("EveryTopOfTheHourTrigger")
|
||||
.StartNow()
|
||||
.WithCronSchedule("0 0 * * * ?")
|
||||
.Build();
|
||||
var everyFiveMinutesTrigger = TriggerBuilder.Create()
|
||||
.WithIdentity("EveryFiveMinutesTrigger")
|
||||
.StartNow()
|
||||
.WithCronSchedule("0 */5 * * * ?")
|
||||
.Build();
|
||||
var everyFridayAt10pmTrigger = TriggerBuilder.Create()
|
||||
.WithIdentity("EveryFridayAt10pmTrigger")
|
||||
.StartNow()
|
||||
.WithCronSchedule("0 0 22 ? * FRI", x => x.InTimeZone(timeZone))
|
||||
.Build();
|
||||
var everySaturdayAtMidnightTrigger = TriggerBuilder.Create()
|
||||
.WithIdentity("EverySaturdayAtMidnightTrigger")
|
||||
.StartNow()
|
||||
.WithCronSchedule("0 0 0 ? * SAT", x => x.InTimeZone(timeZone))
|
||||
.Build();
|
||||
var everySundayAtMidnightTrigger = TriggerBuilder.Create()
|
||||
.WithIdentity("EverySundayAtMidnightTrigger")
|
||||
.StartNow()
|
||||
.WithCronSchedule("0 0 0 ? * SUN", x => x.InTimeZone(timeZone))
|
||||
.Build();
|
||||
var everyMondayAtMidnightTrigger = TriggerBuilder.Create()
|
||||
.WithIdentity("EveryMondayAtMidnightTrigger")
|
||||
.StartNow()
|
||||
.WithCronSchedule("0 0 0 ? * MON", x => x.InTimeZone(timeZone))
|
||||
.Build();
|
||||
var everyDayAtMidnightUtc = TriggerBuilder.Create()
|
||||
.WithIdentity("EveryDayAtMidnightUtc")
|
||||
.StartNow()
|
||||
.WithCronSchedule("0 0 0 * * ?")
|
||||
.Build();
|
||||
|
||||
var jobs = new List<Tuple<Type, ITrigger>>
|
||||
public static void AddJobsServices(IServiceCollection services, bool selfHosted)
|
||||
{
|
||||
new Tuple<Type, ITrigger>(typeof(DeleteSendsJob), everyFiveMinutesTrigger),
|
||||
new Tuple<Type, ITrigger>(typeof(DatabaseExpiredGrantsJob), everyFridayAt10pmTrigger),
|
||||
new Tuple<Type, ITrigger>(typeof(DatabaseUpdateStatisticsJob), everySaturdayAtMidnightTrigger),
|
||||
new Tuple<Type, ITrigger>(typeof(DatabaseRebuildlIndexesJob), everySundayAtMidnightTrigger),
|
||||
new Tuple<Type, ITrigger>(typeof(DeleteCiphersJob), everyDayAtMidnightUtc),
|
||||
new Tuple<Type, ITrigger>(typeof(DatabaseExpiredSponsorshipsJob), everyMondayAtMidnightTrigger)
|
||||
};
|
||||
|
||||
if (!_globalSettings.SelfHosted)
|
||||
{
|
||||
jobs.Add(new Tuple<Type, ITrigger>(typeof(AliveJob), everyTopOfTheHourTrigger));
|
||||
if (!selfHosted)
|
||||
{
|
||||
services.AddTransient<AliveJob>();
|
||||
}
|
||||
services.AddTransient<DatabaseUpdateStatisticsJob>();
|
||||
services.AddTransient<DatabaseRebuildlIndexesJob>();
|
||||
services.AddTransient<DatabaseExpiredGrantsJob>();
|
||||
services.AddTransient<DatabaseExpiredSponsorshipsJob>();
|
||||
services.AddTransient<DeleteSendsJob>();
|
||||
services.AddTransient<DeleteCiphersJob>();
|
||||
}
|
||||
|
||||
Jobs = jobs;
|
||||
await base.StartAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public static void AddJobsServices(IServiceCollection services, bool selfHosted)
|
||||
{
|
||||
if (!selfHosted)
|
||||
{
|
||||
services.AddTransient<AliveJob>();
|
||||
}
|
||||
services.AddTransient<DatabaseUpdateStatisticsJob>();
|
||||
services.AddTransient<DatabaseRebuildlIndexesJob>();
|
||||
services.AddTransient<DatabaseExpiredGrantsJob>();
|
||||
services.AddTransient<DatabaseExpiredSponsorshipsJob>();
|
||||
services.AddTransient<DeleteSendsJob>();
|
||||
services.AddTransient<DeleteCiphersJob>();
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
using Bit.Core.Models.Business;
|
||||
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class BillingInformationModel
|
||||
namespace Bit.Admin.Models
|
||||
{
|
||||
public BillingInfo BillingInfo { get; set; }
|
||||
public Guid? UserId { get; set; }
|
||||
public Guid? OrganizationId { get; set; }
|
||||
public class BillingInformationModel
|
||||
{
|
||||
public BillingInfo BillingInfo { get; set; }
|
||||
public Guid? UserId { get; set; }
|
||||
public Guid? OrganizationId { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,26 +1,27 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class ChargeBraintreeModel : IValidatableObject
|
||||
namespace Bit.Admin.Models
|
||||
{
|
||||
[Required]
|
||||
[Display(Name = "Braintree Customer Id")]
|
||||
public string Id { get; set; }
|
||||
[Required]
|
||||
[Display(Name = "Amount")]
|
||||
public decimal? Amount { get; set; }
|
||||
public string TransactionId { get; set; }
|
||||
public string PayPalTransactionId { get; set; }
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
public class ChargeBraintreeModel : IValidatableObject
|
||||
{
|
||||
if (Id != null)
|
||||
[Required]
|
||||
[Display(Name = "Braintree Customer Id")]
|
||||
public string Id { get; set; }
|
||||
[Required]
|
||||
[Display(Name = "Amount")]
|
||||
public decimal? Amount { get; set; }
|
||||
public string TransactionId { get; set; }
|
||||
public string PayPalTransactionId { get; set; }
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
{
|
||||
if (Id.Length != 36 || (Id[0] != 'o' && Id[0] != 'u') ||
|
||||
!Guid.TryParse(Id.Substring(1, 32), out var guid))
|
||||
if (Id != null)
|
||||
{
|
||||
yield return new ValidationResult("Customer Id is not a valid format.");
|
||||
if (Id.Length != 36 || (Id[0] != 'o' && Id[0] != 'u') ||
|
||||
!Guid.TryParse(Id.Substring(1, 32), out var guid))
|
||||
{
|
||||
yield return new ValidationResult("Customer Id is not a valid format.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class CreateProviderModel
|
||||
namespace Bit.Admin.Models
|
||||
{
|
||||
public CreateProviderModel() { }
|
||||
public class CreateProviderModel
|
||||
{
|
||||
public CreateProviderModel() { }
|
||||
|
||||
[Display(Name = "Owner Email")]
|
||||
[Required]
|
||||
public string OwnerEmail { get; set; }
|
||||
[Display(Name = "Owner Email")]
|
||||
[Required]
|
||||
public string OwnerEmail { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -2,76 +2,77 @@
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class CreateUpdateTransactionModel : IValidatableObject
|
||||
namespace Bit.Admin.Models
|
||||
{
|
||||
public CreateUpdateTransactionModel() { }
|
||||
|
||||
public CreateUpdateTransactionModel(Transaction transaction)
|
||||
public class CreateUpdateTransactionModel : IValidatableObject
|
||||
{
|
||||
Edit = true;
|
||||
UserId = transaction.UserId;
|
||||
OrganizationId = transaction.OrganizationId;
|
||||
Amount = transaction.Amount;
|
||||
RefundedAmount = transaction.RefundedAmount;
|
||||
Refunded = transaction.Refunded.GetValueOrDefault();
|
||||
Details = transaction.Details;
|
||||
Date = transaction.CreationDate;
|
||||
PaymentMethod = transaction.PaymentMethodType;
|
||||
Gateway = transaction.Gateway;
|
||||
GatewayId = transaction.GatewayId;
|
||||
Type = transaction.Type;
|
||||
}
|
||||
public CreateUpdateTransactionModel() { }
|
||||
|
||||
public bool Edit { get; set; }
|
||||
|
||||
[Display(Name = "User Id")]
|
||||
public Guid? UserId { get; set; }
|
||||
[Display(Name = "Organization Id")]
|
||||
public Guid? OrganizationId { get; set; }
|
||||
[Required]
|
||||
public decimal? Amount { get; set; }
|
||||
[Display(Name = "Refunded Amount")]
|
||||
public decimal? RefundedAmount { get; set; }
|
||||
public bool Refunded { get; set; }
|
||||
[Required]
|
||||
public string Details { get; set; }
|
||||
[Required]
|
||||
public DateTime? Date { get; set; }
|
||||
[Display(Name = "Payment Method")]
|
||||
public PaymentMethodType? PaymentMethod { get; set; }
|
||||
public GatewayType? Gateway { get; set; }
|
||||
[Display(Name = "Gateway Id")]
|
||||
public string GatewayId { get; set; }
|
||||
[Required]
|
||||
public TransactionType? Type { get; set; }
|
||||
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
{
|
||||
if ((!UserId.HasValue && !OrganizationId.HasValue) || (UserId.HasValue && OrganizationId.HasValue))
|
||||
public CreateUpdateTransactionModel(Transaction transaction)
|
||||
{
|
||||
yield return new ValidationResult("Must provide either User Id, or Organization Id.");
|
||||
Edit = true;
|
||||
UserId = transaction.UserId;
|
||||
OrganizationId = transaction.OrganizationId;
|
||||
Amount = transaction.Amount;
|
||||
RefundedAmount = transaction.RefundedAmount;
|
||||
Refunded = transaction.Refunded.GetValueOrDefault();
|
||||
Details = transaction.Details;
|
||||
Date = transaction.CreationDate;
|
||||
PaymentMethod = transaction.PaymentMethodType;
|
||||
Gateway = transaction.Gateway;
|
||||
GatewayId = transaction.GatewayId;
|
||||
Type = transaction.Type;
|
||||
}
|
||||
|
||||
public bool Edit { get; set; }
|
||||
|
||||
[Display(Name = "User Id")]
|
||||
public Guid? UserId { get; set; }
|
||||
[Display(Name = "Organization Id")]
|
||||
public Guid? OrganizationId { get; set; }
|
||||
[Required]
|
||||
public decimal? Amount { get; set; }
|
||||
[Display(Name = "Refunded Amount")]
|
||||
public decimal? RefundedAmount { get; set; }
|
||||
public bool Refunded { get; set; }
|
||||
[Required]
|
||||
public string Details { get; set; }
|
||||
[Required]
|
||||
public DateTime? Date { get; set; }
|
||||
[Display(Name = "Payment Method")]
|
||||
public PaymentMethodType? PaymentMethod { get; set; }
|
||||
public GatewayType? Gateway { get; set; }
|
||||
[Display(Name = "Gateway Id")]
|
||||
public string GatewayId { get; set; }
|
||||
[Required]
|
||||
public TransactionType? Type { get; set; }
|
||||
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
{
|
||||
if ((!UserId.HasValue && !OrganizationId.HasValue) || (UserId.HasValue && OrganizationId.HasValue))
|
||||
{
|
||||
yield return new ValidationResult("Must provide either User Id, or Organization Id.");
|
||||
}
|
||||
}
|
||||
|
||||
public Transaction ToTransaction(Guid? id = null)
|
||||
{
|
||||
return new Transaction
|
||||
{
|
||||
Id = id.GetValueOrDefault(),
|
||||
UserId = UserId,
|
||||
OrganizationId = OrganizationId,
|
||||
Amount = Amount.Value,
|
||||
RefundedAmount = RefundedAmount,
|
||||
Refunded = Refunded ? true : (bool?)null,
|
||||
Details = Details,
|
||||
CreationDate = Date.Value,
|
||||
PaymentMethodType = PaymentMethod,
|
||||
Gateway = Gateway,
|
||||
GatewayId = GatewayId,
|
||||
Type = Type.Value
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public Transaction ToTransaction(Guid? id = null)
|
||||
{
|
||||
return new Transaction
|
||||
{
|
||||
Id = id.GetValueOrDefault(),
|
||||
UserId = UserId,
|
||||
OrganizationId = OrganizationId,
|
||||
Amount = Amount.Value,
|
||||
RefundedAmount = RefundedAmount,
|
||||
Refunded = Refunded ? true : (bool?)null,
|
||||
Details = Details,
|
||||
CreationDate = Date.Value,
|
||||
PaymentMethodType = PaymentMethod,
|
||||
Gateway = Gateway,
|
||||
GatewayId = GatewayId,
|
||||
Type = Type.Value
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class CursorPagedModel<T>
|
||||
namespace Bit.Admin.Models
|
||||
{
|
||||
public List<T> Items { get; set; }
|
||||
public int Count { get; set; }
|
||||
public string Cursor { get; set; }
|
||||
public string NextCursor { get; set; }
|
||||
public class CursorPagedModel<T>
|
||||
{
|
||||
public List<T> Items { get; set; }
|
||||
public int Count { get; set; }
|
||||
public string Cursor { get; set; }
|
||||
public string NextCursor { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class ErrorViewModel
|
||||
namespace Bit.Admin.Models
|
||||
{
|
||||
public string RequestId { get; set; }
|
||||
public class ErrorViewModel
|
||||
{
|
||||
public string RequestId { get; set; }
|
||||
|
||||
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
using Bit.Core.Settings;
|
||||
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class HomeModel
|
||||
namespace Bit.Admin.Models
|
||||
{
|
||||
public string CurrentVersion { get; set; }
|
||||
public GlobalSettings GlobalSettings { get; set; }
|
||||
public class HomeModel
|
||||
{
|
||||
public string CurrentVersion { get; set; }
|
||||
public GlobalSettings GlobalSettings { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,34 +1,35 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class LicenseModel : IValidatableObject
|
||||
namespace Bit.Admin.Models
|
||||
{
|
||||
[Display(Name = "User Id")]
|
||||
public Guid? UserId { get; set; }
|
||||
[Display(Name = "Organization Id")]
|
||||
public Guid? OrganizationId { get; set; }
|
||||
[Display(Name = "Installation Id")]
|
||||
public Guid? InstallationId { get; set; }
|
||||
[Required]
|
||||
[Display(Name = "Version")]
|
||||
public int Version { get; set; }
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
public class LicenseModel : IValidatableObject
|
||||
{
|
||||
if (UserId.HasValue && OrganizationId.HasValue)
|
||||
{
|
||||
yield return new ValidationResult("Use either User Id or Organization Id. Not both.");
|
||||
}
|
||||
[Display(Name = "User Id")]
|
||||
public Guid? UserId { get; set; }
|
||||
[Display(Name = "Organization Id")]
|
||||
public Guid? OrganizationId { get; set; }
|
||||
[Display(Name = "Installation Id")]
|
||||
public Guid? InstallationId { get; set; }
|
||||
[Required]
|
||||
[Display(Name = "Version")]
|
||||
public int Version { get; set; }
|
||||
|
||||
if (!UserId.HasValue && !OrganizationId.HasValue)
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
{
|
||||
yield return new ValidationResult("User Id or Organization Id is required.");
|
||||
}
|
||||
if (UserId.HasValue && OrganizationId.HasValue)
|
||||
{
|
||||
yield return new ValidationResult("Use either User Id or Organization Id. Not both.");
|
||||
}
|
||||
|
||||
if (OrganizationId.HasValue && !InstallationId.HasValue)
|
||||
{
|
||||
yield return new ValidationResult("Installation Id is required for organization licenses.");
|
||||
if (!UserId.HasValue && !OrganizationId.HasValue)
|
||||
{
|
||||
yield return new ValidationResult("User Id or Organization Id is required.");
|
||||
}
|
||||
|
||||
if (OrganizationId.HasValue && !InstallationId.HasValue)
|
||||
{
|
||||
yield return new ValidationResult("Installation Id is required for organization licenses.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,54 +1,55 @@
|
||||
using Microsoft.Azure.Documents;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class LogModel : Resource
|
||||
namespace Bit.Admin.Models
|
||||
{
|
||||
public long EventIdHash { get; set; }
|
||||
public string Level { get; set; }
|
||||
public string Message { get; set; }
|
||||
public string MessageTruncated => Message.Length > 200 ? $"{Message.Substring(0, 200)}..." : Message;
|
||||
public string MessageTemplate { get; set; }
|
||||
public IDictionary<string, object> Properties { get; set; }
|
||||
public string Project => Properties?.ContainsKey("Project") ?? false ? Properties["Project"].ToString() : null;
|
||||
}
|
||||
|
||||
public class LogDetailsModel : LogModel
|
||||
{
|
||||
public JObject Exception { get; set; }
|
||||
|
||||
public string ExceptionToString(JObject e)
|
||||
public class LogModel : Resource
|
||||
{
|
||||
if (e == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
public long EventIdHash { get; set; }
|
||||
public string Level { get; set; }
|
||||
public string Message { get; set; }
|
||||
public string MessageTruncated => Message.Length > 200 ? $"{Message.Substring(0, 200)}..." : Message;
|
||||
public string MessageTemplate { get; set; }
|
||||
public IDictionary<string, object> Properties { get; set; }
|
||||
public string Project => Properties?.ContainsKey("Project") ?? false ? Properties["Project"].ToString() : null;
|
||||
}
|
||||
|
||||
var val = string.Empty;
|
||||
if (e["Message"] != null && e["Message"].ToObject<string>() != null)
|
||||
{
|
||||
val += "Message:\n";
|
||||
val += e["Message"] + "\n";
|
||||
}
|
||||
public class LogDetailsModel : LogModel
|
||||
{
|
||||
public JObject Exception { get; set; }
|
||||
|
||||
if (e["StackTrace"] != null && e["StackTrace"].ToObject<string>() != null)
|
||||
public string ExceptionToString(JObject e)
|
||||
{
|
||||
val += "\nStack Trace:\n";
|
||||
val += e["StackTrace"];
|
||||
}
|
||||
else if (e["StackTraceString"] != null && e["StackTraceString"].ToObject<string>() != null)
|
||||
{
|
||||
val += "\nStack Trace String:\n";
|
||||
val += e["StackTraceString"];
|
||||
}
|
||||
if (e == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (e["InnerException"] != null && e["InnerException"].ToObject<JObject>() != null)
|
||||
{
|
||||
val += "\n\n=== Inner Exception ===\n\n";
|
||||
val += ExceptionToString(e["InnerException"].ToObject<JObject>());
|
||||
}
|
||||
var val = string.Empty;
|
||||
if (e["Message"] != null && e["Message"].ToObject<string>() != null)
|
||||
{
|
||||
val += "Message:\n";
|
||||
val += e["Message"] + "\n";
|
||||
}
|
||||
|
||||
return val;
|
||||
if (e["StackTrace"] != null && e["StackTrace"].ToObject<string>() != null)
|
||||
{
|
||||
val += "\nStack Trace:\n";
|
||||
val += e["StackTrace"];
|
||||
}
|
||||
else if (e["StackTraceString"] != null && e["StackTraceString"].ToObject<string>() != null)
|
||||
{
|
||||
val += "\nStack Trace String:\n";
|
||||
val += e["StackTraceString"];
|
||||
}
|
||||
|
||||
if (e["InnerException"] != null && e["InnerException"].ToObject<JObject>() != null)
|
||||
{
|
||||
val += "\n\n=== Inner Exception ===\n\n";
|
||||
val += ExceptionToString(e["InnerException"].ToObject<JObject>());
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,14 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class LoginModel
|
||||
namespace Bit.Admin.Models
|
||||
{
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
public string Email { get; set; }
|
||||
public string ReturnUrl { get; set; }
|
||||
public string Error { get; set; }
|
||||
public string Success { get; set; }
|
||||
public class LoginModel
|
||||
{
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
public string Email { get; set; }
|
||||
public string ReturnUrl { get; set; }
|
||||
public string Error { get; set; }
|
||||
public string Success { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class LogsModel : CursorPagedModel<LogModel>
|
||||
namespace Bit.Admin.Models
|
||||
{
|
||||
public LogEventLevel? Level { get; set; }
|
||||
public string Project { get; set; }
|
||||
public DateTime? Start { get; set; }
|
||||
public DateTime? End { get; set; }
|
||||
public class LogsModel : CursorPagedModel<LogModel>
|
||||
{
|
||||
public LogEventLevel? Level { get; set; }
|
||||
public string Project { get; set; }
|
||||
public DateTime? Start { get; set; }
|
||||
public DateTime? End { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -6,147 +6,148 @@ using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class OrganizationEditModel : OrganizationViewModel
|
||||
namespace Bit.Admin.Models
|
||||
{
|
||||
public OrganizationEditModel() { }
|
||||
|
||||
public OrganizationEditModel(Organization org, IEnumerable<OrganizationUserUserDetails> orgUsers,
|
||||
IEnumerable<Cipher> ciphers, IEnumerable<Collection> collections, IEnumerable<Group> groups,
|
||||
IEnumerable<Policy> policies, BillingInfo billingInfo, IEnumerable<OrganizationConnection> connections,
|
||||
GlobalSettings globalSettings)
|
||||
: base(org, connections, orgUsers, ciphers, collections, groups, policies)
|
||||
public class OrganizationEditModel : OrganizationViewModel
|
||||
{
|
||||
BillingInfo = billingInfo;
|
||||
BraintreeMerchantId = globalSettings.Braintree.MerchantId;
|
||||
public OrganizationEditModel() { }
|
||||
|
||||
Name = org.Name;
|
||||
BusinessName = org.BusinessName;
|
||||
BillingEmail = org.BillingEmail;
|
||||
PlanType = org.PlanType;
|
||||
Plan = org.Plan;
|
||||
Seats = org.Seats;
|
||||
MaxAutoscaleSeats = org.MaxAutoscaleSeats;
|
||||
MaxCollections = org.MaxCollections;
|
||||
UsePolicies = org.UsePolicies;
|
||||
UseSso = org.UseSso;
|
||||
UseKeyConnector = org.UseKeyConnector;
|
||||
UseScim = org.UseScim;
|
||||
UseGroups = org.UseGroups;
|
||||
UseDirectory = org.UseDirectory;
|
||||
UseEvents = org.UseEvents;
|
||||
UseTotp = org.UseTotp;
|
||||
Use2fa = org.Use2fa;
|
||||
UseApi = org.UseApi;
|
||||
UseResetPassword = org.UseResetPassword;
|
||||
SelfHost = org.SelfHost;
|
||||
UsersGetPremium = org.UsersGetPremium;
|
||||
MaxStorageGb = org.MaxStorageGb;
|
||||
Gateway = org.Gateway;
|
||||
GatewayCustomerId = org.GatewayCustomerId;
|
||||
GatewaySubscriptionId = org.GatewaySubscriptionId;
|
||||
Enabled = org.Enabled;
|
||||
LicenseKey = org.LicenseKey;
|
||||
ExpirationDate = org.ExpirationDate;
|
||||
}
|
||||
public OrganizationEditModel(Organization org, IEnumerable<OrganizationUserUserDetails> orgUsers,
|
||||
IEnumerable<Cipher> ciphers, IEnumerable<Collection> collections, IEnumerable<Group> groups,
|
||||
IEnumerable<Policy> policies, BillingInfo billingInfo, IEnumerable<OrganizationConnection> connections,
|
||||
GlobalSettings globalSettings)
|
||||
: base(org, connections, orgUsers, ciphers, collections, groups, policies)
|
||||
{
|
||||
BillingInfo = billingInfo;
|
||||
BraintreeMerchantId = globalSettings.Braintree.MerchantId;
|
||||
|
||||
public BillingInfo BillingInfo { get; set; }
|
||||
public string RandomLicenseKey => CoreHelpers.SecureRandomString(20);
|
||||
public string FourteenDayExpirationDate => DateTime.Now.AddDays(14).ToString("yyyy-MM-ddTHH:mm");
|
||||
public string BraintreeMerchantId { get; set; }
|
||||
Name = org.Name;
|
||||
BusinessName = org.BusinessName;
|
||||
BillingEmail = org.BillingEmail;
|
||||
PlanType = org.PlanType;
|
||||
Plan = org.Plan;
|
||||
Seats = org.Seats;
|
||||
MaxAutoscaleSeats = org.MaxAutoscaleSeats;
|
||||
MaxCollections = org.MaxCollections;
|
||||
UsePolicies = org.UsePolicies;
|
||||
UseSso = org.UseSso;
|
||||
UseKeyConnector = org.UseKeyConnector;
|
||||
UseScim = org.UseScim;
|
||||
UseGroups = org.UseGroups;
|
||||
UseDirectory = org.UseDirectory;
|
||||
UseEvents = org.UseEvents;
|
||||
UseTotp = org.UseTotp;
|
||||
Use2fa = org.Use2fa;
|
||||
UseApi = org.UseApi;
|
||||
UseResetPassword = org.UseResetPassword;
|
||||
SelfHost = org.SelfHost;
|
||||
UsersGetPremium = org.UsersGetPremium;
|
||||
MaxStorageGb = org.MaxStorageGb;
|
||||
Gateway = org.Gateway;
|
||||
GatewayCustomerId = org.GatewayCustomerId;
|
||||
GatewaySubscriptionId = org.GatewaySubscriptionId;
|
||||
Enabled = org.Enabled;
|
||||
LicenseKey = org.LicenseKey;
|
||||
ExpirationDate = org.ExpirationDate;
|
||||
}
|
||||
|
||||
[Required]
|
||||
[Display(Name = "Name")]
|
||||
public string Name { get; set; }
|
||||
[Display(Name = "Business Name")]
|
||||
public string BusinessName { get; set; }
|
||||
[Display(Name = "Billing Email")]
|
||||
public string BillingEmail { get; set; }
|
||||
[Required]
|
||||
[Display(Name = "Plan")]
|
||||
public PlanType? PlanType { get; set; }
|
||||
[Required]
|
||||
[Display(Name = "Plan Name")]
|
||||
public string Plan { get; set; }
|
||||
[Display(Name = "Seats")]
|
||||
public int? Seats { get; set; }
|
||||
[Display(Name = "Max. Autoscale Seats")]
|
||||
public int? MaxAutoscaleSeats { get; set; }
|
||||
[Display(Name = "Max. Collections")]
|
||||
public short? MaxCollections { get; set; }
|
||||
[Display(Name = "Policies")]
|
||||
public bool UsePolicies { get; set; }
|
||||
[Display(Name = "SSO")]
|
||||
public bool UseSso { get; set; }
|
||||
[Display(Name = "Key Connector with Customer Encryption")]
|
||||
public bool UseKeyConnector { get; set; }
|
||||
[Display(Name = "Groups")]
|
||||
public bool UseGroups { get; set; }
|
||||
[Display(Name = "Directory")]
|
||||
public bool UseDirectory { get; set; }
|
||||
[Display(Name = "Events")]
|
||||
public bool UseEvents { get; set; }
|
||||
[Display(Name = "TOTP")]
|
||||
public bool UseTotp { get; set; }
|
||||
[Display(Name = "2FA")]
|
||||
public bool Use2fa { get; set; }
|
||||
[Display(Name = "API")]
|
||||
public bool UseApi { get; set; }
|
||||
[Display(Name = "Reset Password")]
|
||||
public bool UseResetPassword { get; set; }
|
||||
[Display(Name = "SCIM")]
|
||||
public bool UseScim { get; set; }
|
||||
[Display(Name = "Self Host")]
|
||||
public bool SelfHost { get; set; }
|
||||
[Display(Name = "Users Get Premium")]
|
||||
public bool UsersGetPremium { get; set; }
|
||||
[Display(Name = "Max. Storage GB")]
|
||||
public short? MaxStorageGb { get; set; }
|
||||
[Display(Name = "Gateway")]
|
||||
public GatewayType? Gateway { get; set; }
|
||||
[Display(Name = "Gateway Customer Id")]
|
||||
public string GatewayCustomerId { get; set; }
|
||||
[Display(Name = "Gateway Subscription Id")]
|
||||
public string GatewaySubscriptionId { get; set; }
|
||||
[Display(Name = "Enabled")]
|
||||
public bool Enabled { get; set; }
|
||||
[Display(Name = "License Key")]
|
||||
public string LicenseKey { get; set; }
|
||||
[Display(Name = "Expiration Date")]
|
||||
public DateTime? ExpirationDate { get; set; }
|
||||
public bool SalesAssistedTrialStarted { get; set; }
|
||||
public BillingInfo BillingInfo { get; set; }
|
||||
public string RandomLicenseKey => CoreHelpers.SecureRandomString(20);
|
||||
public string FourteenDayExpirationDate => DateTime.Now.AddDays(14).ToString("yyyy-MM-ddTHH:mm");
|
||||
public string BraintreeMerchantId { get; set; }
|
||||
|
||||
public Organization ToOrganization(Organization existingOrganization)
|
||||
{
|
||||
existingOrganization.Name = Name;
|
||||
existingOrganization.BusinessName = BusinessName;
|
||||
existingOrganization.BillingEmail = BillingEmail?.ToLowerInvariant()?.Trim();
|
||||
existingOrganization.PlanType = PlanType.Value;
|
||||
existingOrganization.Plan = Plan;
|
||||
existingOrganization.Seats = Seats;
|
||||
existingOrganization.MaxCollections = MaxCollections;
|
||||
existingOrganization.UsePolicies = UsePolicies;
|
||||
existingOrganization.UseSso = UseSso;
|
||||
existingOrganization.UseKeyConnector = UseKeyConnector;
|
||||
existingOrganization.UseScim = UseScim;
|
||||
existingOrganization.UseGroups = UseGroups;
|
||||
existingOrganization.UseDirectory = UseDirectory;
|
||||
existingOrganization.UseEvents = UseEvents;
|
||||
existingOrganization.UseTotp = UseTotp;
|
||||
existingOrganization.Use2fa = Use2fa;
|
||||
existingOrganization.UseApi = UseApi;
|
||||
existingOrganization.UseResetPassword = UseResetPassword;
|
||||
existingOrganization.SelfHost = SelfHost;
|
||||
existingOrganization.UsersGetPremium = UsersGetPremium;
|
||||
existingOrganization.MaxStorageGb = MaxStorageGb;
|
||||
existingOrganization.Gateway = Gateway;
|
||||
existingOrganization.GatewayCustomerId = GatewayCustomerId;
|
||||
existingOrganization.GatewaySubscriptionId = GatewaySubscriptionId;
|
||||
existingOrganization.Enabled = Enabled;
|
||||
existingOrganization.LicenseKey = LicenseKey;
|
||||
existingOrganization.ExpirationDate = ExpirationDate;
|
||||
existingOrganization.MaxAutoscaleSeats = MaxAutoscaleSeats;
|
||||
return existingOrganization;
|
||||
[Required]
|
||||
[Display(Name = "Name")]
|
||||
public string Name { get; set; }
|
||||
[Display(Name = "Business Name")]
|
||||
public string BusinessName { get; set; }
|
||||
[Display(Name = "Billing Email")]
|
||||
public string BillingEmail { get; set; }
|
||||
[Required]
|
||||
[Display(Name = "Plan")]
|
||||
public PlanType? PlanType { get; set; }
|
||||
[Required]
|
||||
[Display(Name = "Plan Name")]
|
||||
public string Plan { get; set; }
|
||||
[Display(Name = "Seats")]
|
||||
public int? Seats { get; set; }
|
||||
[Display(Name = "Max. Autoscale Seats")]
|
||||
public int? MaxAutoscaleSeats { get; set; }
|
||||
[Display(Name = "Max. Collections")]
|
||||
public short? MaxCollections { get; set; }
|
||||
[Display(Name = "Policies")]
|
||||
public bool UsePolicies { get; set; }
|
||||
[Display(Name = "SSO")]
|
||||
public bool UseSso { get; set; }
|
||||
[Display(Name = "Key Connector with Customer Encryption")]
|
||||
public bool UseKeyConnector { get; set; }
|
||||
[Display(Name = "Groups")]
|
||||
public bool UseGroups { get; set; }
|
||||
[Display(Name = "Directory")]
|
||||
public bool UseDirectory { get; set; }
|
||||
[Display(Name = "Events")]
|
||||
public bool UseEvents { get; set; }
|
||||
[Display(Name = "TOTP")]
|
||||
public bool UseTotp { get; set; }
|
||||
[Display(Name = "2FA")]
|
||||
public bool Use2fa { get; set; }
|
||||
[Display(Name = "API")]
|
||||
public bool UseApi { get; set; }
|
||||
[Display(Name = "Reset Password")]
|
||||
public bool UseResetPassword { get; set; }
|
||||
[Display(Name = "SCIM")]
|
||||
public bool UseScim { get; set; }
|
||||
[Display(Name = "Self Host")]
|
||||
public bool SelfHost { get; set; }
|
||||
[Display(Name = "Users Get Premium")]
|
||||
public bool UsersGetPremium { get; set; }
|
||||
[Display(Name = "Max. Storage GB")]
|
||||
public short? MaxStorageGb { get; set; }
|
||||
[Display(Name = "Gateway")]
|
||||
public GatewayType? Gateway { get; set; }
|
||||
[Display(Name = "Gateway Customer Id")]
|
||||
public string GatewayCustomerId { get; set; }
|
||||
[Display(Name = "Gateway Subscription Id")]
|
||||
public string GatewaySubscriptionId { get; set; }
|
||||
[Display(Name = "Enabled")]
|
||||
public bool Enabled { get; set; }
|
||||
[Display(Name = "License Key")]
|
||||
public string LicenseKey { get; set; }
|
||||
[Display(Name = "Expiration Date")]
|
||||
public DateTime? ExpirationDate { get; set; }
|
||||
public bool SalesAssistedTrialStarted { get; set; }
|
||||
|
||||
public Organization ToOrganization(Organization existingOrganization)
|
||||
{
|
||||
existingOrganization.Name = Name;
|
||||
existingOrganization.BusinessName = BusinessName;
|
||||
existingOrganization.BillingEmail = BillingEmail?.ToLowerInvariant()?.Trim();
|
||||
existingOrganization.PlanType = PlanType.Value;
|
||||
existingOrganization.Plan = Plan;
|
||||
existingOrganization.Seats = Seats;
|
||||
existingOrganization.MaxCollections = MaxCollections;
|
||||
existingOrganization.UsePolicies = UsePolicies;
|
||||
existingOrganization.UseSso = UseSso;
|
||||
existingOrganization.UseKeyConnector = UseKeyConnector;
|
||||
existingOrganization.UseScim = UseScim;
|
||||
existingOrganization.UseGroups = UseGroups;
|
||||
existingOrganization.UseDirectory = UseDirectory;
|
||||
existingOrganization.UseEvents = UseEvents;
|
||||
existingOrganization.UseTotp = UseTotp;
|
||||
existingOrganization.Use2fa = Use2fa;
|
||||
existingOrganization.UseApi = UseApi;
|
||||
existingOrganization.UseResetPassword = UseResetPassword;
|
||||
existingOrganization.SelfHost = SelfHost;
|
||||
existingOrganization.UsersGetPremium = UsersGetPremium;
|
||||
existingOrganization.MaxStorageGb = MaxStorageGb;
|
||||
existingOrganization.Gateway = Gateway;
|
||||
existingOrganization.GatewayCustomerId = GatewayCustomerId;
|
||||
existingOrganization.GatewaySubscriptionId = GatewaySubscriptionId;
|
||||
existingOrganization.Enabled = Enabled;
|
||||
existingOrganization.LicenseKey = LicenseKey;
|
||||
existingOrganization.ExpirationDate = ExpirationDate;
|
||||
existingOrganization.MaxAutoscaleSeats = MaxAutoscaleSeats;
|
||||
return existingOrganization;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,48 +2,49 @@
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class OrganizationViewModel
|
||||
namespace Bit.Admin.Models
|
||||
{
|
||||
public OrganizationViewModel() { }
|
||||
|
||||
public OrganizationViewModel(Organization org, IEnumerable<OrganizationConnection> connections,
|
||||
IEnumerable<OrganizationUserUserDetails> orgUsers, IEnumerable<Cipher> ciphers, IEnumerable<Collection> collections,
|
||||
IEnumerable<Group> groups, IEnumerable<Policy> policies)
|
||||
public class OrganizationViewModel
|
||||
{
|
||||
Organization = org;
|
||||
Connections = connections ?? Enumerable.Empty<OrganizationConnection>();
|
||||
HasPublicPrivateKeys = org.PublicKey != null && org.PrivateKey != null;
|
||||
UserInvitedCount = orgUsers.Count(u => u.Status == OrganizationUserStatusType.Invited);
|
||||
UserAcceptedCount = orgUsers.Count(u => u.Status == OrganizationUserStatusType.Accepted);
|
||||
UserConfirmedCount = orgUsers.Count(u => u.Status == OrganizationUserStatusType.Confirmed);
|
||||
UserCount = orgUsers.Count();
|
||||
CipherCount = ciphers.Count();
|
||||
CollectionCount = collections.Count();
|
||||
GroupCount = groups?.Count() ?? 0;
|
||||
PolicyCount = policies?.Count() ?? 0;
|
||||
Owners = string.Join(", ",
|
||||
orgUsers
|
||||
.Where(u => u.Type == OrganizationUserType.Owner && u.Status == OrganizationUserStatusType.Confirmed)
|
||||
.Select(u => u.Email));
|
||||
Admins = string.Join(", ",
|
||||
orgUsers
|
||||
.Where(u => u.Type == OrganizationUserType.Admin && u.Status == OrganizationUserStatusType.Confirmed)
|
||||
.Select(u => u.Email));
|
||||
}
|
||||
public OrganizationViewModel() { }
|
||||
|
||||
public Organization Organization { get; set; }
|
||||
public IEnumerable<OrganizationConnection> Connections { get; set; }
|
||||
public string Owners { get; set; }
|
||||
public string Admins { get; set; }
|
||||
public int UserInvitedCount { get; set; }
|
||||
public int UserConfirmedCount { get; set; }
|
||||
public int UserAcceptedCount { get; set; }
|
||||
public int UserCount { get; set; }
|
||||
public int CipherCount { get; set; }
|
||||
public int CollectionCount { get; set; }
|
||||
public int GroupCount { get; set; }
|
||||
public int PolicyCount { get; set; }
|
||||
public bool HasPublicPrivateKeys { get; set; }
|
||||
public OrganizationViewModel(Organization org, IEnumerable<OrganizationConnection> connections,
|
||||
IEnumerable<OrganizationUserUserDetails> orgUsers, IEnumerable<Cipher> ciphers, IEnumerable<Collection> collections,
|
||||
IEnumerable<Group> groups, IEnumerable<Policy> policies)
|
||||
{
|
||||
Organization = org;
|
||||
Connections = connections ?? Enumerable.Empty<OrganizationConnection>();
|
||||
HasPublicPrivateKeys = org.PublicKey != null && org.PrivateKey != null;
|
||||
UserInvitedCount = orgUsers.Count(u => u.Status == OrganizationUserStatusType.Invited);
|
||||
UserAcceptedCount = orgUsers.Count(u => u.Status == OrganizationUserStatusType.Accepted);
|
||||
UserConfirmedCount = orgUsers.Count(u => u.Status == OrganizationUserStatusType.Confirmed);
|
||||
UserCount = orgUsers.Count();
|
||||
CipherCount = ciphers.Count();
|
||||
CollectionCount = collections.Count();
|
||||
GroupCount = groups?.Count() ?? 0;
|
||||
PolicyCount = policies?.Count() ?? 0;
|
||||
Owners = string.Join(", ",
|
||||
orgUsers
|
||||
.Where(u => u.Type == OrganizationUserType.Owner && u.Status == OrganizationUserStatusType.Confirmed)
|
||||
.Select(u => u.Email));
|
||||
Admins = string.Join(", ",
|
||||
orgUsers
|
||||
.Where(u => u.Type == OrganizationUserType.Admin && u.Status == OrganizationUserStatusType.Confirmed)
|
||||
.Select(u => u.Email));
|
||||
}
|
||||
|
||||
public Organization Organization { get; set; }
|
||||
public IEnumerable<OrganizationConnection> Connections { get; set; }
|
||||
public string Owners { get; set; }
|
||||
public string Admins { get; set; }
|
||||
public int UserInvitedCount { get; set; }
|
||||
public int UserConfirmedCount { get; set; }
|
||||
public int UserAcceptedCount { get; set; }
|
||||
public int UserCount { get; set; }
|
||||
public int CipherCount { get; set; }
|
||||
public int CollectionCount { get; set; }
|
||||
public int GroupCount { get; set; }
|
||||
public int PolicyCount { get; set; }
|
||||
public bool HasPublicPrivateKeys { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
using Bit.Core.Entities;
|
||||
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class OrganizationsModel : PagedModel<Organization>
|
||||
namespace Bit.Admin.Models
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string UserEmail { get; set; }
|
||||
public bool? Paid { get; set; }
|
||||
public string Action { get; set; }
|
||||
public bool SelfHosted { get; set; }
|
||||
public class OrganizationsModel : PagedModel<Organization>
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string UserEmail { get; set; }
|
||||
public bool? Paid { get; set; }
|
||||
public string Action { get; set; }
|
||||
public bool SelfHosted { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public abstract class PagedModel<T>
|
||||
namespace Bit.Admin.Models
|
||||
{
|
||||
public List<T> Items { get; set; }
|
||||
public int Page { get; set; }
|
||||
public int Count { get; set; }
|
||||
public int? PreviousPage => Page < 2 ? (int?)null : Page - 1;
|
||||
public int? NextPage => Items.Count < Count ? (int?)null : Page + 1;
|
||||
public abstract class PagedModel<T>
|
||||
{
|
||||
public List<T> Items { get; set; }
|
||||
public int Page { get; set; }
|
||||
public int Count { get; set; }
|
||||
public int? PreviousPage => Page < 2 ? (int?)null : Page - 1;
|
||||
public int? NextPage => Items.Count < Count ? (int?)null : Page + 1;
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,14 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class PromoteAdminModel
|
||||
namespace Bit.Admin.Models
|
||||
{
|
||||
[Required]
|
||||
[Display(Name = "Admin User Id")]
|
||||
public Guid? UserId { get; set; }
|
||||
[Required]
|
||||
[Display(Name = "Organization Id")]
|
||||
public Guid? OrganizationId { get; set; }
|
||||
public class PromoteAdminModel
|
||||
{
|
||||
[Required]
|
||||
[Display(Name = "Admin User Id")]
|
||||
public Guid? UserId { get; set; }
|
||||
[Required]
|
||||
[Display(Name = "Organization Id")]
|
||||
public Guid? OrganizationId { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -2,32 +2,33 @@
|
||||
using Bit.Core.Entities.Provider;
|
||||
using Bit.Core.Models.Data;
|
||||
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class ProviderEditModel : ProviderViewModel
|
||||
namespace Bit.Admin.Models
|
||||
{
|
||||
public ProviderEditModel() { }
|
||||
|
||||
public ProviderEditModel(Provider provider, IEnumerable<ProviderUserUserDetails> providerUsers, IEnumerable<ProviderOrganizationOrganizationDetails> organizations)
|
||||
: base(provider, providerUsers, organizations)
|
||||
public class ProviderEditModel : ProviderViewModel
|
||||
{
|
||||
Name = provider.Name;
|
||||
BusinessName = provider.BusinessName;
|
||||
BillingEmail = provider.BillingEmail;
|
||||
}
|
||||
public ProviderEditModel() { }
|
||||
|
||||
[Display(Name = "Billing Email")]
|
||||
public string BillingEmail { get; set; }
|
||||
[Display(Name = "Business Name")]
|
||||
public string BusinessName { get; set; }
|
||||
public string Name { get; set; }
|
||||
[Display(Name = "Events")]
|
||||
public ProviderEditModel(Provider provider, IEnumerable<ProviderUserUserDetails> providerUsers, IEnumerable<ProviderOrganizationOrganizationDetails> organizations)
|
||||
: base(provider, providerUsers, organizations)
|
||||
{
|
||||
Name = provider.Name;
|
||||
BusinessName = provider.BusinessName;
|
||||
BillingEmail = provider.BillingEmail;
|
||||
}
|
||||
|
||||
public Provider ToProvider(Provider existingProvider)
|
||||
{
|
||||
existingProvider.Name = Name;
|
||||
existingProvider.BusinessName = BusinessName;
|
||||
existingProvider.BillingEmail = BillingEmail?.ToLowerInvariant()?.Trim();
|
||||
return existingProvider;
|
||||
[Display(Name = "Billing Email")]
|
||||
public string BillingEmail { get; set; }
|
||||
[Display(Name = "Business Name")]
|
||||
public string BusinessName { get; set; }
|
||||
public string Name { get; set; }
|
||||
[Display(Name = "Events")]
|
||||
|
||||
public Provider ToProvider(Provider existingProvider)
|
||||
{
|
||||
existingProvider.Name = Name;
|
||||
existingProvider.BusinessName = BusinessName;
|
||||
existingProvider.BillingEmail = BillingEmail?.ToLowerInvariant()?.Trim();
|
||||
return existingProvider;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,23 +2,24 @@
|
||||
using Bit.Core.Enums.Provider;
|
||||
using Bit.Core.Models.Data;
|
||||
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class ProviderViewModel
|
||||
namespace Bit.Admin.Models
|
||||
{
|
||||
public ProviderViewModel() { }
|
||||
|
||||
public ProviderViewModel(Provider provider, IEnumerable<ProviderUserUserDetails> providerUsers, IEnumerable<ProviderOrganizationOrganizationDetails> organizations)
|
||||
public class ProviderViewModel
|
||||
{
|
||||
Provider = provider;
|
||||
UserCount = providerUsers.Count();
|
||||
ProviderAdmins = providerUsers.Where(u => u.Type == ProviderUserType.ProviderAdmin);
|
||||
public ProviderViewModel() { }
|
||||
|
||||
ProviderOrganizations = organizations.Where(o => o.ProviderId == provider.Id);
|
||||
public ProviderViewModel(Provider provider, IEnumerable<ProviderUserUserDetails> providerUsers, IEnumerable<ProviderOrganizationOrganizationDetails> organizations)
|
||||
{
|
||||
Provider = provider;
|
||||
UserCount = providerUsers.Count();
|
||||
ProviderAdmins = providerUsers.Where(u => u.Type == ProviderUserType.ProviderAdmin);
|
||||
|
||||
ProviderOrganizations = organizations.Where(o => o.ProviderId == provider.Id);
|
||||
}
|
||||
|
||||
public int UserCount { get; set; }
|
||||
public Provider Provider { get; set; }
|
||||
public IEnumerable<ProviderUserUserDetails> ProviderAdmins { get; set; }
|
||||
public IEnumerable<ProviderOrganizationOrganizationDetails> ProviderOrganizations { get; set; }
|
||||
}
|
||||
|
||||
public int UserCount { get; set; }
|
||||
public Provider Provider { get; set; }
|
||||
public IEnumerable<ProviderUserUserDetails> ProviderAdmins { get; set; }
|
||||
public IEnumerable<ProviderOrganizationOrganizationDetails> ProviderOrganizations { get; set; }
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
using Bit.Core.Entities.Provider;
|
||||
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class ProvidersModel : PagedModel<Provider>
|
||||
namespace Bit.Admin.Models
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string UserEmail { get; set; }
|
||||
public bool? Paid { get; set; }
|
||||
public string Action { get; set; }
|
||||
public bool SelfHosted { get; set; }
|
||||
public class ProvidersModel : PagedModel<Provider>
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string UserEmail { get; set; }
|
||||
public bool? Paid { get; set; }
|
||||
public string Action { get; set; }
|
||||
public bool SelfHosted { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,42 +1,43 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Bit.Core.Models.BitStripe;
|
||||
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class StripeSubscriptionRowModel
|
||||
namespace Bit.Admin.Models
|
||||
{
|
||||
public Stripe.Subscription Subscription { get; set; }
|
||||
public bool Selected { get; set; }
|
||||
|
||||
public StripeSubscriptionRowModel() { }
|
||||
public StripeSubscriptionRowModel(Stripe.Subscription subscription)
|
||||
public class StripeSubscriptionRowModel
|
||||
{
|
||||
Subscription = subscription;
|
||||
}
|
||||
}
|
||||
public Stripe.Subscription Subscription { get; set; }
|
||||
public bool Selected { get; set; }
|
||||
|
||||
public enum StripeSubscriptionsAction
|
||||
{
|
||||
Search,
|
||||
PreviousPage,
|
||||
NextPage,
|
||||
Export,
|
||||
BulkCancel
|
||||
}
|
||||
|
||||
public class StripeSubscriptionsModel : IValidatableObject
|
||||
{
|
||||
public List<StripeSubscriptionRowModel> Items { get; set; }
|
||||
public StripeSubscriptionsAction Action { get; set; } = StripeSubscriptionsAction.Search;
|
||||
public string Message { get; set; }
|
||||
public List<Stripe.Price> Prices { get; set; }
|
||||
public List<Stripe.TestHelpers.TestClock> TestClocks { get; set; }
|
||||
public StripeSubscriptionListOptions Filter { get; set; } = new StripeSubscriptionListOptions();
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
{
|
||||
if (Action == StripeSubscriptionsAction.BulkCancel && Filter.Status != "unpaid")
|
||||
public StripeSubscriptionRowModel() { }
|
||||
public StripeSubscriptionRowModel(Stripe.Subscription subscription)
|
||||
{
|
||||
yield return new ValidationResult("Bulk cancel is currently only supported for unpaid subscriptions");
|
||||
Subscription = subscription;
|
||||
}
|
||||
}
|
||||
|
||||
public enum StripeSubscriptionsAction
|
||||
{
|
||||
Search,
|
||||
PreviousPage,
|
||||
NextPage,
|
||||
Export,
|
||||
BulkCancel
|
||||
}
|
||||
|
||||
public class StripeSubscriptionsModel : IValidatableObject
|
||||
{
|
||||
public List<StripeSubscriptionRowModel> Items { get; set; }
|
||||
public StripeSubscriptionsAction Action { get; set; } = StripeSubscriptionsAction.Search;
|
||||
public string Message { get; set; }
|
||||
public List<Stripe.Price> Prices { get; set; }
|
||||
public List<Stripe.TestHelpers.TestClock> TestClocks { get; set; }
|
||||
public StripeSubscriptionListOptions Filter { get; set; } = new StripeSubscriptionListOptions();
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
{
|
||||
if (Action == StripeSubscriptionsAction.BulkCancel && Filter.Status != "unpaid")
|
||||
{
|
||||
yield return new ValidationResult("Bulk cancel is currently only supported for unpaid subscriptions");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class TaxRateAddEditModel
|
||||
namespace Bit.Admin.Models
|
||||
{
|
||||
public string StripeTaxRateId { get; set; }
|
||||
public string Country { get; set; }
|
||||
public string State { get; set; }
|
||||
public string PostalCode { get; set; }
|
||||
public decimal Rate { get; set; }
|
||||
public class TaxRateAddEditModel
|
||||
{
|
||||
public string StripeTaxRateId { get; set; }
|
||||
public string Country { get; set; }
|
||||
public string State { get; set; }
|
||||
public string PostalCode { get; set; }
|
||||
public decimal Rate { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
using Bit.Core.Entities;
|
||||
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class TaxRatesModel : PagedModel<TaxRate>
|
||||
namespace Bit.Admin.Models
|
||||
{
|
||||
public string Message { get; set; }
|
||||
public class TaxRatesModel : PagedModel<TaxRate>
|
||||
{
|
||||
public string Message { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -4,70 +4,71 @@ using Bit.Core.Models.Business;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class UserEditModel : UserViewModel
|
||||
namespace Bit.Admin.Models
|
||||
{
|
||||
public UserEditModel() { }
|
||||
|
||||
public UserEditModel(User user, IEnumerable<Cipher> ciphers, BillingInfo billingInfo,
|
||||
GlobalSettings globalSettings)
|
||||
: base(user, ciphers)
|
||||
public class UserEditModel : UserViewModel
|
||||
{
|
||||
BillingInfo = billingInfo;
|
||||
BraintreeMerchantId = globalSettings.Braintree.MerchantId;
|
||||
public UserEditModel() { }
|
||||
|
||||
Name = user.Name;
|
||||
Email = user.Email;
|
||||
EmailVerified = user.EmailVerified;
|
||||
Premium = user.Premium;
|
||||
MaxStorageGb = user.MaxStorageGb;
|
||||
Gateway = user.Gateway;
|
||||
GatewayCustomerId = user.GatewayCustomerId;
|
||||
GatewaySubscriptionId = user.GatewaySubscriptionId;
|
||||
LicenseKey = user.LicenseKey;
|
||||
PremiumExpirationDate = user.PremiumExpirationDate;
|
||||
}
|
||||
public UserEditModel(User user, IEnumerable<Cipher> ciphers, BillingInfo billingInfo,
|
||||
GlobalSettings globalSettings)
|
||||
: base(user, ciphers)
|
||||
{
|
||||
BillingInfo = billingInfo;
|
||||
BraintreeMerchantId = globalSettings.Braintree.MerchantId;
|
||||
|
||||
public BillingInfo BillingInfo { get; set; }
|
||||
public string RandomLicenseKey => CoreHelpers.SecureRandomString(20);
|
||||
public string OneYearExpirationDate => DateTime.Now.AddYears(1).ToString("yyyy-MM-ddTHH:mm");
|
||||
public string BraintreeMerchantId { get; set; }
|
||||
Name = user.Name;
|
||||
Email = user.Email;
|
||||
EmailVerified = user.EmailVerified;
|
||||
Premium = user.Premium;
|
||||
MaxStorageGb = user.MaxStorageGb;
|
||||
Gateway = user.Gateway;
|
||||
GatewayCustomerId = user.GatewayCustomerId;
|
||||
GatewaySubscriptionId = user.GatewaySubscriptionId;
|
||||
LicenseKey = user.LicenseKey;
|
||||
PremiumExpirationDate = user.PremiumExpirationDate;
|
||||
}
|
||||
|
||||
[Display(Name = "Name")]
|
||||
public string Name { get; set; }
|
||||
[Required]
|
||||
[Display(Name = "Email")]
|
||||
public string Email { get; set; }
|
||||
[Display(Name = "Email Verified")]
|
||||
public bool EmailVerified { get; set; }
|
||||
[Display(Name = "Premium")]
|
||||
public bool Premium { get; set; }
|
||||
[Display(Name = "Max. Storage GB")]
|
||||
public short? MaxStorageGb { get; set; }
|
||||
[Display(Name = "Gateway")]
|
||||
public Core.Enums.GatewayType? Gateway { get; set; }
|
||||
[Display(Name = "Gateway Customer Id")]
|
||||
public string GatewayCustomerId { get; set; }
|
||||
[Display(Name = "Gateway Subscription Id")]
|
||||
public string GatewaySubscriptionId { get; set; }
|
||||
[Display(Name = "License Key")]
|
||||
public string LicenseKey { get; set; }
|
||||
[Display(Name = "Premium Expiration Date")]
|
||||
public DateTime? PremiumExpirationDate { get; set; }
|
||||
public BillingInfo BillingInfo { get; set; }
|
||||
public string RandomLicenseKey => CoreHelpers.SecureRandomString(20);
|
||||
public string OneYearExpirationDate => DateTime.Now.AddYears(1).ToString("yyyy-MM-ddTHH:mm");
|
||||
public string BraintreeMerchantId { get; set; }
|
||||
|
||||
public User ToUser(User existingUser)
|
||||
{
|
||||
existingUser.Name = Name;
|
||||
existingUser.Email = Email;
|
||||
existingUser.EmailVerified = EmailVerified;
|
||||
existingUser.Premium = Premium;
|
||||
existingUser.MaxStorageGb = MaxStorageGb;
|
||||
existingUser.Gateway = Gateway;
|
||||
existingUser.GatewayCustomerId = GatewayCustomerId;
|
||||
existingUser.GatewaySubscriptionId = GatewaySubscriptionId;
|
||||
existingUser.LicenseKey = LicenseKey;
|
||||
existingUser.PremiumExpirationDate = PremiumExpirationDate;
|
||||
return existingUser;
|
||||
[Display(Name = "Name")]
|
||||
public string Name { get; set; }
|
||||
[Required]
|
||||
[Display(Name = "Email")]
|
||||
public string Email { get; set; }
|
||||
[Display(Name = "Email Verified")]
|
||||
public bool EmailVerified { get; set; }
|
||||
[Display(Name = "Premium")]
|
||||
public bool Premium { get; set; }
|
||||
[Display(Name = "Max. Storage GB")]
|
||||
public short? MaxStorageGb { get; set; }
|
||||
[Display(Name = "Gateway")]
|
||||
public Core.Enums.GatewayType? Gateway { get; set; }
|
||||
[Display(Name = "Gateway Customer Id")]
|
||||
public string GatewayCustomerId { get; set; }
|
||||
[Display(Name = "Gateway Subscription Id")]
|
||||
public string GatewaySubscriptionId { get; set; }
|
||||
[Display(Name = "License Key")]
|
||||
public string LicenseKey { get; set; }
|
||||
[Display(Name = "Premium Expiration Date")]
|
||||
public DateTime? PremiumExpirationDate { get; set; }
|
||||
|
||||
public User ToUser(User existingUser)
|
||||
{
|
||||
existingUser.Name = Name;
|
||||
existingUser.Email = Email;
|
||||
existingUser.EmailVerified = EmailVerified;
|
||||
existingUser.Premium = Premium;
|
||||
existingUser.MaxStorageGb = MaxStorageGb;
|
||||
existingUser.Gateway = Gateway;
|
||||
existingUser.GatewayCustomerId = GatewayCustomerId;
|
||||
existingUser.GatewaySubscriptionId = GatewaySubscriptionId;
|
||||
existingUser.LicenseKey = LicenseKey;
|
||||
existingUser.PremiumExpirationDate = PremiumExpirationDate;
|
||||
return existingUser;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,18 @@
|
||||
using Bit.Core.Entities;
|
||||
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class UserViewModel
|
||||
namespace Bit.Admin.Models
|
||||
{
|
||||
public UserViewModel() { }
|
||||
|
||||
public UserViewModel(User user, IEnumerable<Cipher> ciphers)
|
||||
public class UserViewModel
|
||||
{
|
||||
User = user;
|
||||
CipherCount = ciphers.Count();
|
||||
}
|
||||
public UserViewModel() { }
|
||||
|
||||
public User User { get; set; }
|
||||
public int CipherCount { get; set; }
|
||||
public UserViewModel(User user, IEnumerable<Cipher> ciphers)
|
||||
{
|
||||
User = user;
|
||||
CipherCount = ciphers.Count();
|
||||
}
|
||||
|
||||
public User User { get; set; }
|
||||
public int CipherCount { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
using Bit.Core.Entities;
|
||||
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class UsersModel : PagedModel<User>
|
||||
namespace Bit.Admin.Models
|
||||
{
|
||||
public string Email { get; set; }
|
||||
public string Action { get; set; }
|
||||
public class UsersModel : PagedModel<User>
|
||||
{
|
||||
public string Email { get; set; }
|
||||
public string Action { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,36 +1,37 @@
|
||||
using Bit.Core.Utilities;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Bit.Admin;
|
||||
|
||||
public class Program
|
||||
namespace Bit.Admin
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
public class Program
|
||||
{
|
||||
Host
|
||||
.CreateDefaultBuilder(args)
|
||||
.ConfigureCustomAppConfiguration(args)
|
||||
.ConfigureWebHostDefaults(webBuilder =>
|
||||
{
|
||||
webBuilder.ConfigureKestrel(o =>
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Host
|
||||
.CreateDefaultBuilder(args)
|
||||
.ConfigureCustomAppConfiguration(args)
|
||||
.ConfigureWebHostDefaults(webBuilder =>
|
||||
{
|
||||
o.Limits.MaxRequestLineSize = 20_000;
|
||||
});
|
||||
webBuilder.UseStartup<Startup>();
|
||||
webBuilder.ConfigureLogging((hostingContext, logging) =>
|
||||
logging.AddSerilog(hostingContext, e =>
|
||||
{
|
||||
var context = e.Properties["SourceContext"].ToString();
|
||||
if (e.Properties.ContainsKey("RequestPath") &&
|
||||
!string.IsNullOrWhiteSpace(e.Properties["RequestPath"]?.ToString()) &&
|
||||
(context.Contains(".Server.Kestrel") || context.Contains(".Core.IISHttpServer")))
|
||||
webBuilder.ConfigureKestrel(o =>
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return e.Level >= LogEventLevel.Error;
|
||||
}));
|
||||
})
|
||||
.Build()
|
||||
.Run();
|
||||
o.Limits.MaxRequestLineSize = 20_000;
|
||||
});
|
||||
webBuilder.UseStartup<Startup>();
|
||||
webBuilder.ConfigureLogging((hostingContext, logging) =>
|
||||
logging.AddSerilog(hostingContext, e =>
|
||||
{
|
||||
var context = e.Properties["SourceContext"].ToString();
|
||||
if (e.Properties.ContainsKey("RequestPath") &&
|
||||
!string.IsNullOrWhiteSpace(e.Properties["RequestPath"]?.ToString()) &&
|
||||
(context.Contains(".Server.Kestrel") || context.Contains(".Core.IISHttpServer")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return e.Level >= LogEventLevel.Error;
|
||||
}));
|
||||
})
|
||||
.Build()
|
||||
.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,127 +11,128 @@ using Stripe;
|
||||
using Bit.Commercial.Core.Utilities;
|
||||
#endif
|
||||
|
||||
namespace Bit.Admin;
|
||||
|
||||
public class Startup
|
||||
namespace Bit.Admin
|
||||
{
|
||||
public Startup(IWebHostEnvironment env, IConfiguration configuration)
|
||||
public class Startup
|
||||
{
|
||||
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("en-US");
|
||||
Configuration = configuration;
|
||||
Environment = env;
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; private set; }
|
||||
public IWebHostEnvironment Environment { get; set; }
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
// Options
|
||||
services.AddOptions();
|
||||
|
||||
// Settings
|
||||
var globalSettings = services.AddGlobalSettingsServices(Configuration, Environment);
|
||||
services.Configure<AdminSettings>(Configuration.GetSection("AdminSettings"));
|
||||
|
||||
// Data Protection
|
||||
services.AddCustomDataProtectionServices(Environment, globalSettings);
|
||||
|
||||
// Stripe Billing
|
||||
StripeConfiguration.ApiKey = globalSettings.Stripe.ApiKey;
|
||||
StripeConfiguration.MaxNetworkRetries = globalSettings.Stripe.MaxNetworkRetries;
|
||||
|
||||
// Repositories
|
||||
services.AddSqlServerRepositories(globalSettings);
|
||||
|
||||
// Context
|
||||
services.AddScoped<ICurrentContext, CurrentContext>();
|
||||
|
||||
// Identity
|
||||
services.AddPasswordlessIdentityServices<ReadOnlyEnvIdentityUserStore>(globalSettings);
|
||||
services.Configure<SecurityStampValidatorOptions>(options =>
|
||||
public Startup(IWebHostEnvironment env, IConfiguration configuration)
|
||||
{
|
||||
options.ValidationInterval = TimeSpan.FromMinutes(5);
|
||||
});
|
||||
if (globalSettings.SelfHosted)
|
||||
{
|
||||
services.ConfigureApplicationCookie(options =>
|
||||
{
|
||||
options.Cookie.Path = "/admin";
|
||||
});
|
||||
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("en-US");
|
||||
Configuration = configuration;
|
||||
Environment = env;
|
||||
}
|
||||
|
||||
// Services
|
||||
services.AddBaseServices(globalSettings);
|
||||
services.AddDefaultServices(globalSettings);
|
||||
public IConfiguration Configuration { get; private set; }
|
||||
public IWebHostEnvironment Environment { get; set; }
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
// Options
|
||||
services.AddOptions();
|
||||
|
||||
// Settings
|
||||
var globalSettings = services.AddGlobalSettingsServices(Configuration, Environment);
|
||||
services.Configure<AdminSettings>(Configuration.GetSection("AdminSettings"));
|
||||
|
||||
// Data Protection
|
||||
services.AddCustomDataProtectionServices(Environment, globalSettings);
|
||||
|
||||
// Stripe Billing
|
||||
StripeConfiguration.ApiKey = globalSettings.Stripe.ApiKey;
|
||||
StripeConfiguration.MaxNetworkRetries = globalSettings.Stripe.MaxNetworkRetries;
|
||||
|
||||
// Repositories
|
||||
services.AddSqlServerRepositories(globalSettings);
|
||||
|
||||
// Context
|
||||
services.AddScoped<ICurrentContext, CurrentContext>();
|
||||
|
||||
// Identity
|
||||
services.AddPasswordlessIdentityServices<ReadOnlyEnvIdentityUserStore>(globalSettings);
|
||||
services.Configure<SecurityStampValidatorOptions>(options =>
|
||||
{
|
||||
options.ValidationInterval = TimeSpan.FromMinutes(5);
|
||||
});
|
||||
if (globalSettings.SelfHosted)
|
||||
{
|
||||
services.ConfigureApplicationCookie(options =>
|
||||
{
|
||||
options.Cookie.Path = "/admin";
|
||||
});
|
||||
}
|
||||
|
||||
// Services
|
||||
services.AddBaseServices(globalSettings);
|
||||
services.AddDefaultServices(globalSettings);
|
||||
|
||||
#if OSS
|
||||
services.AddOosServices();
|
||||
services.AddOosServices();
|
||||
#else
|
||||
services.AddCommCoreServices();
|
||||
services.AddCommCoreServices();
|
||||
#endif
|
||||
|
||||
// Mvc
|
||||
services.AddMvc(config =>
|
||||
{
|
||||
config.Filters.Add(new LoggingExceptionHandlerFilterAttribute());
|
||||
});
|
||||
services.Configure<RouteOptions>(options => options.LowercaseUrls = true);
|
||||
// Mvc
|
||||
services.AddMvc(config =>
|
||||
{
|
||||
config.Filters.Add(new LoggingExceptionHandlerFilterAttribute());
|
||||
});
|
||||
services.Configure<RouteOptions>(options => options.LowercaseUrls = true);
|
||||
|
||||
// Jobs service
|
||||
Jobs.JobsHostedService.AddJobsServices(services, globalSettings.SelfHosted);
|
||||
services.AddHostedService<Jobs.JobsHostedService>();
|
||||
if (globalSettings.SelfHosted)
|
||||
{
|
||||
services.AddHostedService<HostedServices.DatabaseMigrationHostedService>();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (CoreHelpers.SettingHasValue(globalSettings.Storage.ConnectionString))
|
||||
// Jobs service
|
||||
Jobs.JobsHostedService.AddJobsServices(services, globalSettings.SelfHosted);
|
||||
services.AddHostedService<Jobs.JobsHostedService>();
|
||||
if (globalSettings.SelfHosted)
|
||||
{
|
||||
services.AddHostedService<HostedServices.AzureQueueBlockIpHostedService>();
|
||||
services.AddHostedService<HostedServices.DatabaseMigrationHostedService>();
|
||||
}
|
||||
else if (CoreHelpers.SettingHasValue(globalSettings.Amazon?.AccessKeySecret))
|
||||
else
|
||||
{
|
||||
services.AddHostedService<HostedServices.AmazonSqsBlockIpHostedService>();
|
||||
}
|
||||
if (CoreHelpers.SettingHasValue(globalSettings.Mail.ConnectionString))
|
||||
{
|
||||
services.AddHostedService<HostedServices.AzureQueueMailHostedService>();
|
||||
if (CoreHelpers.SettingHasValue(globalSettings.Storage.ConnectionString))
|
||||
{
|
||||
services.AddHostedService<HostedServices.AzureQueueBlockIpHostedService>();
|
||||
}
|
||||
else if (CoreHelpers.SettingHasValue(globalSettings.Amazon?.AccessKeySecret))
|
||||
{
|
||||
services.AddHostedService<HostedServices.AmazonSqsBlockIpHostedService>();
|
||||
}
|
||||
if (CoreHelpers.SettingHasValue(globalSettings.Mail.ConnectionString))
|
||||
{
|
||||
services.AddHostedService<HostedServices.AzureQueueMailHostedService>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Configure(
|
||||
IApplicationBuilder app,
|
||||
IWebHostEnvironment env,
|
||||
IHostApplicationLifetime appLifetime,
|
||||
GlobalSettings globalSettings)
|
||||
{
|
||||
app.UseSerilog(env, appLifetime, globalSettings);
|
||||
|
||||
// Add general security headers
|
||||
app.UseMiddleware<SecurityHeadersMiddleware>();
|
||||
|
||||
if (globalSettings.SelfHosted)
|
||||
public void Configure(
|
||||
IApplicationBuilder app,
|
||||
IWebHostEnvironment env,
|
||||
IHostApplicationLifetime appLifetime,
|
||||
GlobalSettings globalSettings)
|
||||
{
|
||||
app.UsePathBase("/admin");
|
||||
app.UseForwardedHeaders(globalSettings);
|
||||
}
|
||||
app.UseSerilog(env, appLifetime, globalSettings);
|
||||
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
else
|
||||
{
|
||||
app.UseExceptionHandler("/error");
|
||||
}
|
||||
// Add general security headers
|
||||
app.UseMiddleware<SecurityHeadersMiddleware>();
|
||||
|
||||
app.UseStaticFiles();
|
||||
app.UseRouting();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.UseEndpoints(endpoints => endpoints.MapDefaultControllerRoute());
|
||||
if (globalSettings.SelfHosted)
|
||||
{
|
||||
app.UsePathBase("/admin");
|
||||
app.UseForwardedHeaders(globalSettings);
|
||||
}
|
||||
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
else
|
||||
{
|
||||
app.UseExceptionHandler("/error");
|
||||
}
|
||||
|
||||
app.UseStaticFiles();
|
||||
app.UseRouting();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.UseEndpoints(endpoints => endpoints.MapDefaultControllerRoute());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,71 +3,72 @@ using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||
|
||||
namespace Bit.Admin.TagHelpers;
|
||||
|
||||
[HtmlTargetElement("li", Attributes = ActiveControllerName)]
|
||||
[HtmlTargetElement("li", Attributes = ActiveActionName)]
|
||||
public class ActivePageTagHelper : TagHelper
|
||||
namespace Bit.Admin.TagHelpers
|
||||
{
|
||||
private const string ActiveControllerName = "active-controller";
|
||||
private const string ActiveActionName = "active-action";
|
||||
|
||||
private readonly IHtmlGenerator _generator;
|
||||
|
||||
public ActivePageTagHelper(IHtmlGenerator generator)
|
||||
[HtmlTargetElement("li", Attributes = ActiveControllerName)]
|
||||
[HtmlTargetElement("li", Attributes = ActiveActionName)]
|
||||
public class ActivePageTagHelper : TagHelper
|
||||
{
|
||||
_generator = generator;
|
||||
}
|
||||
private const string ActiveControllerName = "active-controller";
|
||||
private const string ActiveActionName = "active-action";
|
||||
|
||||
[HtmlAttributeNotBound]
|
||||
[ViewContext]
|
||||
public ViewContext ViewContext { get; set; }
|
||||
[HtmlAttributeName(ActiveControllerName)]
|
||||
public string ActiveController { get; set; }
|
||||
[HtmlAttributeName(ActiveActionName)]
|
||||
public string ActiveAction { get; set; }
|
||||
private readonly IHtmlGenerator _generator;
|
||||
|
||||
public override void Process(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
if (context == null)
|
||||
public ActivePageTagHelper(IHtmlGenerator generator)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
_generator = generator;
|
||||
}
|
||||
|
||||
if (output == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(output));
|
||||
}
|
||||
[HtmlAttributeNotBound]
|
||||
[ViewContext]
|
||||
public ViewContext ViewContext { get; set; }
|
||||
[HtmlAttributeName(ActiveControllerName)]
|
||||
public string ActiveController { get; set; }
|
||||
[HtmlAttributeName(ActiveActionName)]
|
||||
public string ActiveAction { get; set; }
|
||||
|
||||
if (ActiveAction == null && ActiveController == null)
|
||||
public override void Process(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var descriptor = ViewContext.ActionDescriptor as ControllerActionDescriptor;
|
||||
if (descriptor == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var controllerMatch = ActiveMatch(ActiveController, descriptor.ControllerName);
|
||||
var actionMatch = ActiveMatch(ActiveAction, descriptor.ActionName);
|
||||
if (controllerMatch && actionMatch)
|
||||
{
|
||||
var classValue = "active";
|
||||
if (output.Attributes["class"] != null)
|
||||
if (context == null)
|
||||
{
|
||||
classValue += " " + output.Attributes["class"].Value;
|
||||
output.Attributes.Remove(output.Attributes["class"]);
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
output.Attributes.Add("class", classValue);
|
||||
if (output == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(output));
|
||||
}
|
||||
|
||||
if (ActiveAction == null && ActiveController == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var descriptor = ViewContext.ActionDescriptor as ControllerActionDescriptor;
|
||||
if (descriptor == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var controllerMatch = ActiveMatch(ActiveController, descriptor.ControllerName);
|
||||
var actionMatch = ActiveMatch(ActiveAction, descriptor.ActionName);
|
||||
if (controllerMatch && actionMatch)
|
||||
{
|
||||
var classValue = "active";
|
||||
if (output.Attributes["class"] != null)
|
||||
{
|
||||
classValue += " " + output.Attributes["class"].Value;
|
||||
output.Attributes.Remove(output.Attributes["class"]);
|
||||
}
|
||||
|
||||
output.Attributes.Add("class", classValue);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ActiveMatch(string route, string descriptor)
|
||||
{
|
||||
return route == null || route == "*" ||
|
||||
route.Split(',').Any(c => c.Trim().ToLower() == descriptor.ToLower());
|
||||
}
|
||||
}
|
||||
|
||||
private bool ActiveMatch(string route, string descriptor)
|
||||
{
|
||||
return route == null || route == "*" ||
|
||||
route.Split(',').Any(c => c.Trim().ToLower() == descriptor.ToLower());
|
||||
}
|
||||
}
|
||||
|
@ -1,42 +1,43 @@
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||
|
||||
namespace Bit.Admin.TagHelpers;
|
||||
|
||||
[HtmlTargetElement("option", Attributes = SelectedName)]
|
||||
public class OptionSelectedTagHelper : TagHelper
|
||||
namespace Bit.Admin.TagHelpers
|
||||
{
|
||||
private const string SelectedName = "asp-selected";
|
||||
|
||||
private readonly IHtmlGenerator _generator;
|
||||
|
||||
public OptionSelectedTagHelper(IHtmlGenerator generator)
|
||||
[HtmlTargetElement("option", Attributes = SelectedName)]
|
||||
public class OptionSelectedTagHelper : TagHelper
|
||||
{
|
||||
_generator = generator;
|
||||
}
|
||||
private const string SelectedName = "asp-selected";
|
||||
|
||||
[HtmlAttributeName(SelectedName)]
|
||||
public bool Selected { get; set; }
|
||||
private readonly IHtmlGenerator _generator;
|
||||
|
||||
public override void Process(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
if (context == null)
|
||||
public OptionSelectedTagHelper(IHtmlGenerator generator)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
_generator = generator;
|
||||
}
|
||||
|
||||
if (output == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(output));
|
||||
}
|
||||
[HtmlAttributeName(SelectedName)]
|
||||
public bool Selected { get; set; }
|
||||
|
||||
if (Selected)
|
||||
public override void Process(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
output.Attributes.Add("selected", "selected");
|
||||
}
|
||||
else
|
||||
{
|
||||
output.Attributes.RemoveAll("selected");
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (output == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(output));
|
||||
}
|
||||
|
||||
if (Selected)
|
||||
{
|
||||
output.Attributes.Add("selected", "selected");
|
||||
}
|
||||
else
|
||||
{
|
||||
output.Attributes.RemoveAll("selected");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user