1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 23:52:50 -05:00

Revert filescoped (#2227)

* Revert "Add git blame entry (#2226)"

This reverts commit 239286737d.

* Revert "Turn on file scoped namespaces (#2225)"

This reverts commit 34fb4cca2a.
This commit is contained in:
Justin Baur
2022-08-29 15:53:48 -04:00
committed by GitHub
parent 239286737d
commit bae03feffe
1208 changed files with 74317 additions and 73126 deletions

View File

@ -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; }
}
}
}

View File

@ -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");
}
}
}
}

View File

@ -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,
}

View File

@ -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());
}
}
}

View File

@ -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,
};
}
}

View File

@ -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());
}
}
}

View File

@ -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");
}
}

View File

@ -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

View File

@ -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");
}
}

View File

@ -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));
}
}
}
}

View File

@ -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));
}
}
}
}

View File

@ -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[] { };
}
}
}

View File

@ -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; }
}
}
}
}

View File

@ -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()
{ }
}
}

View File

@ -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);
}
}
}

View File

@ -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");
}
}
}

View File

@ -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");
}
}
}

View File

@ -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");
}
}
}

View File

@ -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");
}
}
}

View File

@ -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");
}
}

View File

@ -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);
}
}
}
}

View File

@ -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>();
}
}

View File

@ -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; }
}
}

View File

@ -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.");
}
}
}
}

View File

@ -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; }
}
}

View File

@ -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
};
}
}

View File

@ -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; }
}
}

View File

@ -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);
}
}

View File

@ -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; }
}
}

View File

@ -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.");
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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;
}
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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;
}
}

View File

@ -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; }
}
}

View File

@ -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;
}
}
}

View File

@ -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; }
}

View File

@ -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; }
}
}

View File

@ -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");
}
}
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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;
}
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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();
}
}
}

View File

@ -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());
}
}
}

View File

@ -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());
}
}

View File

@ -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");
}
}
}
}

View File

@ -4,48 +4,49 @@ using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Route("accounts/billing")]
[Authorize("Application")]
public class AccountsBillingController : Controller
namespace Bit.Api.Controllers
{
private readonly IPaymentService _paymentService;
private readonly IUserService _userService;
public AccountsBillingController(
IPaymentService paymentService,
IUserService userService)
[Route("accounts/billing")]
[Authorize("Application")]
public class AccountsBillingController : Controller
{
_paymentService = paymentService;
_userService = userService;
}
private readonly IPaymentService _paymentService;
private readonly IUserService _userService;
[HttpGet("history")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<BillingHistoryResponseModel> GetBillingHistory()
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
public AccountsBillingController(
IPaymentService paymentService,
IUserService userService)
{
throw new UnauthorizedAccessException();
_paymentService = paymentService;
_userService = userService;
}
var billingInfo = await _paymentService.GetBillingHistoryAsync(user);
return new BillingHistoryResponseModel(billingInfo);
}
[HttpGet("payment-method")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<BillingPaymentResponseModel> GetPaymentMethod()
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
[HttpGet("history")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<BillingHistoryResponseModel> GetBillingHistory()
{
throw new UnauthorizedAccessException();
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
var billingInfo = await _paymentService.GetBillingHistoryAsync(user);
return new BillingHistoryResponseModel(billingInfo);
}
var billingInfo = await _paymentService.GetBillingBalanceAndSourceAsync(user);
return new BillingPaymentResponseModel(billingInfo);
[HttpGet("payment-method")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<BillingPaymentResponseModel> GetPaymentMethod()
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
var billingInfo = await _paymentService.GetBillingBalanceAndSourceAsync(user);
return new BillingPaymentResponseModel(billingInfo);
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -8,260 +8,261 @@ using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Route("organizations/{orgId}/collections")]
[Authorize("Application")]
public class CollectionsController : Controller
namespace Bit.Api.Controllers
{
private readonly ICollectionRepository _collectionRepository;
private readonly ICollectionService _collectionService;
private readonly IUserService _userService;
private readonly ICurrentContext _currentContext;
public CollectionsController(
ICollectionRepository collectionRepository,
ICollectionService collectionService,
IUserService userService,
ICurrentContext currentContext)
[Route("organizations/{orgId}/collections")]
[Authorize("Application")]
public class CollectionsController : Controller
{
_collectionRepository = collectionRepository;
_collectionService = collectionService;
_userService = userService;
_currentContext = currentContext;
}
private readonly ICollectionRepository _collectionRepository;
private readonly ICollectionService _collectionService;
private readonly IUserService _userService;
private readonly ICurrentContext _currentContext;
[HttpGet("{id}")]
public async Task<CollectionResponseModel> Get(Guid orgId, Guid id)
{
if (!await CanViewCollectionAsync(orgId, id))
public CollectionsController(
ICollectionRepository collectionRepository,
ICollectionService collectionService,
IUserService userService,
ICurrentContext currentContext)
{
throw new NotFoundException();
_collectionRepository = collectionRepository;
_collectionService = collectionService;
_userService = userService;
_currentContext = currentContext;
}
var collection = await GetCollectionAsync(id, orgId);
return new CollectionResponseModel(collection);
}
[HttpGet("{id}/details")]
public async Task<CollectionGroupDetailsResponseModel> GetDetails(Guid orgId, Guid id)
{
if (!await ViewAtLeastOneCollectionAsync(orgId) && !await _currentContext.ManageUsers(orgId))
[HttpGet("{id}")]
public async Task<CollectionResponseModel> Get(Guid orgId, Guid id)
{
throw new NotFoundException();
}
if (await _currentContext.ViewAllCollections(orgId))
{
var collectionDetails = await _collectionRepository.GetByIdWithGroupsAsync(id);
if (collectionDetails?.Item1 == null || collectionDetails.Item1.OrganizationId != orgId)
if (!await CanViewCollectionAsync(orgId, id))
{
throw new NotFoundException();
}
return new CollectionGroupDetailsResponseModel(collectionDetails.Item1, collectionDetails.Item2);
var collection = await GetCollectionAsync(id, orgId);
return new CollectionResponseModel(collection);
}
else
[HttpGet("{id}/details")]
public async Task<CollectionGroupDetailsResponseModel> GetDetails(Guid orgId, Guid id)
{
var collectionDetails = await _collectionRepository.GetByIdWithGroupsAsync(id,
_currentContext.UserId.Value);
if (collectionDetails?.Item1 == null || collectionDetails.Item1.OrganizationId != orgId)
if (!await ViewAtLeastOneCollectionAsync(orgId) && !await _currentContext.ManageUsers(orgId))
{
throw new NotFoundException();
}
return new CollectionGroupDetailsResponseModel(collectionDetails.Item1, collectionDetails.Item2);
}
}
[HttpGet("")]
public async Task<ListResponseModel<CollectionResponseModel>> Get(Guid orgId)
{
IEnumerable<Collection> orgCollections = await _collectionService.GetOrganizationCollections(orgId);
var responses = orgCollections.Select(c => new CollectionResponseModel(c));
return new ListResponseModel<CollectionResponseModel>(responses);
}
[HttpGet("~/collections")]
public async Task<ListResponseModel<CollectionDetailsResponseModel>> GetUser()
{
var collections = await _collectionRepository.GetManyByUserIdAsync(
_userService.GetProperUserId(User).Value);
var responses = collections.Select(c => new CollectionDetailsResponseModel(c));
return new ListResponseModel<CollectionDetailsResponseModel>(responses);
}
[HttpGet("{id}/users")]
public async Task<IEnumerable<SelectionReadOnlyResponseModel>> GetUsers(Guid orgId, Guid id)
{
var collection = await GetCollectionAsync(id, orgId);
var collectionUsers = await _collectionRepository.GetManyUsersByIdAsync(collection.Id);
var responses = collectionUsers.Select(cu => new SelectionReadOnlyResponseModel(cu));
return responses;
}
[HttpPost("")]
public async Task<CollectionResponseModel> Post(Guid orgId, [FromBody] CollectionRequestModel model)
{
var collection = model.ToCollection(orgId);
if (!await CanCreateCollection(orgId, collection.Id) &&
!await CanEditCollectionAsync(orgId, collection.Id))
{
throw new NotFoundException();
if (await _currentContext.ViewAllCollections(orgId))
{
var collectionDetails = await _collectionRepository.GetByIdWithGroupsAsync(id);
if (collectionDetails?.Item1 == null || collectionDetails.Item1.OrganizationId != orgId)
{
throw new NotFoundException();
}
return new CollectionGroupDetailsResponseModel(collectionDetails.Item1, collectionDetails.Item2);
}
else
{
var collectionDetails = await _collectionRepository.GetByIdWithGroupsAsync(id,
_currentContext.UserId.Value);
if (collectionDetails?.Item1 == null || collectionDetails.Item1.OrganizationId != orgId)
{
throw new NotFoundException();
}
return new CollectionGroupDetailsResponseModel(collectionDetails.Item1, collectionDetails.Item2);
}
}
var assignUserToCollection = !(await _currentContext.EditAnyCollection(orgId)) &&
await _currentContext.EditAssignedCollections(orgId);
await _collectionService.SaveAsync(collection, model.Groups?.Select(g => g.ToSelectionReadOnly()),
assignUserToCollection ? _currentContext.UserId : null);
return new CollectionResponseModel(collection);
}
[HttpPut("{id}")]
[HttpPost("{id}")]
public async Task<CollectionResponseModel> Put(Guid orgId, Guid id, [FromBody] CollectionRequestModel model)
{
if (!await CanEditCollectionAsync(orgId, id))
[HttpGet("")]
public async Task<ListResponseModel<CollectionResponseModel>> Get(Guid orgId)
{
throw new NotFoundException();
IEnumerable<Collection> orgCollections = await _collectionService.GetOrganizationCollections(orgId);
var responses = orgCollections.Select(c => new CollectionResponseModel(c));
return new ListResponseModel<CollectionResponseModel>(responses);
}
var collection = await GetCollectionAsync(id, orgId);
await _collectionService.SaveAsync(model.ToCollection(collection),
model.Groups?.Select(g => g.ToSelectionReadOnly()));
return new CollectionResponseModel(collection);
}
[HttpPut("{id}/users")]
public async Task PutUsers(Guid orgId, Guid id, [FromBody] IEnumerable<SelectionReadOnlyRequestModel> model)
{
if (!await CanEditCollectionAsync(orgId, id))
[HttpGet("~/collections")]
public async Task<ListResponseModel<CollectionDetailsResponseModel>> GetUser()
{
throw new NotFoundException();
var collections = await _collectionRepository.GetManyByUserIdAsync(
_userService.GetProperUserId(User).Value);
var responses = collections.Select(c => new CollectionDetailsResponseModel(c));
return new ListResponseModel<CollectionDetailsResponseModel>(responses);
}
var collection = await GetCollectionAsync(id, orgId);
await _collectionRepository.UpdateUsersAsync(collection.Id, model?.Select(g => g.ToSelectionReadOnly()));
}
[HttpDelete("{id}")]
[HttpPost("{id}/delete")]
public async Task Delete(Guid orgId, Guid id)
{
if (!await CanDeleteCollectionAsync(orgId, id))
[HttpGet("{id}/users")]
public async Task<IEnumerable<SelectionReadOnlyResponseModel>> GetUsers(Guid orgId, Guid id)
{
throw new NotFoundException();
var collection = await GetCollectionAsync(id, orgId);
var collectionUsers = await _collectionRepository.GetManyUsersByIdAsync(collection.Id);
var responses = collectionUsers.Select(cu => new SelectionReadOnlyResponseModel(cu));
return responses;
}
var collection = await GetCollectionAsync(id, orgId);
await _collectionService.DeleteAsync(collection);
}
[HttpDelete("{id}/user/{orgUserId}")]
[HttpPost("{id}/delete-user/{orgUserId}")]
public async Task Delete(string orgId, string id, string orgUserId)
{
var collection = await GetCollectionAsync(new Guid(id), new Guid(orgId));
await _collectionService.DeleteUserAsync(collection, new Guid(orgUserId));
}
private async Task<Collection> GetCollectionAsync(Guid id, Guid orgId)
{
Collection collection = default;
if (await _currentContext.ViewAllCollections(orgId))
[HttpPost("")]
public async Task<CollectionResponseModel> Post(Guid orgId, [FromBody] CollectionRequestModel model)
{
collection = await _collectionRepository.GetByIdAsync(id);
}
else if (await _currentContext.ViewAssignedCollections(orgId))
{
collection = await _collectionRepository.GetByIdAsync(id, _currentContext.UserId.Value);
var collection = model.ToCollection(orgId);
if (!await CanCreateCollection(orgId, collection.Id) &&
!await CanEditCollectionAsync(orgId, collection.Id))
{
throw new NotFoundException();
}
var assignUserToCollection = !(await _currentContext.EditAnyCollection(orgId)) &&
await _currentContext.EditAssignedCollections(orgId);
await _collectionService.SaveAsync(collection, model.Groups?.Select(g => g.ToSelectionReadOnly()),
assignUserToCollection ? _currentContext.UserId : null);
return new CollectionResponseModel(collection);
}
if (collection == null || collection.OrganizationId != orgId)
[HttpPut("{id}")]
[HttpPost("{id}")]
public async Task<CollectionResponseModel> Put(Guid orgId, Guid id, [FromBody] CollectionRequestModel model)
{
throw new NotFoundException();
if (!await CanEditCollectionAsync(orgId, id))
{
throw new NotFoundException();
}
var collection = await GetCollectionAsync(id, orgId);
await _collectionService.SaveAsync(model.ToCollection(collection),
model.Groups?.Select(g => g.ToSelectionReadOnly()));
return new CollectionResponseModel(collection);
}
return collection;
}
private async Task<bool> CanCreateCollection(Guid orgId, Guid collectionId)
{
if (collectionId != default)
[HttpPut("{id}/users")]
public async Task PutUsers(Guid orgId, Guid id, [FromBody] IEnumerable<SelectionReadOnlyRequestModel> model)
{
if (!await CanEditCollectionAsync(orgId, id))
{
throw new NotFoundException();
}
var collection = await GetCollectionAsync(id, orgId);
await _collectionRepository.UpdateUsersAsync(collection.Id, model?.Select(g => g.ToSelectionReadOnly()));
}
[HttpDelete("{id}")]
[HttpPost("{id}/delete")]
public async Task Delete(Guid orgId, Guid id)
{
if (!await CanDeleteCollectionAsync(orgId, id))
{
throw new NotFoundException();
}
var collection = await GetCollectionAsync(id, orgId);
await _collectionService.DeleteAsync(collection);
}
[HttpDelete("{id}/user/{orgUserId}")]
[HttpPost("{id}/delete-user/{orgUserId}")]
public async Task Delete(string orgId, string id, string orgUserId)
{
var collection = await GetCollectionAsync(new Guid(id), new Guid(orgId));
await _collectionService.DeleteUserAsync(collection, new Guid(orgUserId));
}
private async Task<Collection> GetCollectionAsync(Guid id, Guid orgId)
{
Collection collection = default;
if (await _currentContext.ViewAllCollections(orgId))
{
collection = await _collectionRepository.GetByIdAsync(id);
}
else if (await _currentContext.ViewAssignedCollections(orgId))
{
collection = await _collectionRepository.GetByIdAsync(id, _currentContext.UserId.Value);
}
if (collection == null || collection.OrganizationId != orgId)
{
throw new NotFoundException();
}
return collection;
}
private async Task<bool> CanCreateCollection(Guid orgId, Guid collectionId)
{
if (collectionId != default)
{
return false;
}
return await _currentContext.CreateNewCollections(orgId);
}
private async Task<bool> CanEditCollectionAsync(Guid orgId, Guid collectionId)
{
if (collectionId == default)
{
return false;
}
if (await _currentContext.EditAnyCollection(orgId))
{
return true;
}
if (await _currentContext.EditAssignedCollections(orgId))
{
var collectionDetails = await _collectionRepository.GetByIdAsync(collectionId, _currentContext.UserId.Value);
return collectionDetails != null;
}
return false;
}
return await _currentContext.CreateNewCollections(orgId);
}
private async Task<bool> CanEditCollectionAsync(Guid orgId, Guid collectionId)
{
if (collectionId == default)
private async Task<bool> CanDeleteCollectionAsync(Guid orgId, Guid collectionId)
{
if (collectionId == default)
{
return false;
}
if (await _currentContext.DeleteAnyCollection(orgId))
{
return true;
}
if (await _currentContext.DeleteAssignedCollections(orgId))
{
var collectionDetails = await _collectionRepository.GetByIdAsync(collectionId, _currentContext.UserId.Value);
return collectionDetails != null;
}
return false;
}
if (await _currentContext.EditAnyCollection(orgId))
private async Task<bool> CanViewCollectionAsync(Guid orgId, Guid collectionId)
{
return true;
}
if (collectionId == default)
{
return false;
}
if (await _currentContext.EditAssignedCollections(orgId))
{
var collectionDetails = await _collectionRepository.GetByIdAsync(collectionId, _currentContext.UserId.Value);
return collectionDetails != null;
}
if (await _currentContext.ViewAllCollections(orgId))
{
return true;
}
return false;
}
if (await _currentContext.ViewAssignedCollections(orgId))
{
var collectionDetails = await _collectionRepository.GetByIdAsync(collectionId, _currentContext.UserId.Value);
return collectionDetails != null;
}
private async Task<bool> CanDeleteCollectionAsync(Guid orgId, Guid collectionId)
{
if (collectionId == default)
{
return false;
}
if (await _currentContext.DeleteAnyCollection(orgId))
private async Task<bool> ViewAtLeastOneCollectionAsync(Guid orgId)
{
return true;
return await _currentContext.ViewAllCollections(orgId) || await _currentContext.ViewAssignedCollections(orgId);
}
if (await _currentContext.DeleteAssignedCollections(orgId))
{
var collectionDetails = await _collectionRepository.GetByIdAsync(collectionId, _currentContext.UserId.Value);
return collectionDetails != null;
}
return false;
}
private async Task<bool> CanViewCollectionAsync(Guid orgId, Guid collectionId)
{
if (collectionId == default)
{
return false;
}
if (await _currentContext.ViewAllCollections(orgId))
{
return true;
}
if (await _currentContext.ViewAssignedCollections(orgId))
{
var collectionDetails = await _collectionRepository.GetByIdAsync(collectionId, _currentContext.UserId.Value);
return collectionDetails != null;
}
return false;
}
private async Task<bool> ViewAtLeastOneCollectionAsync(Guid orgId)
{
return await _currentContext.ViewAllCollections(orgId) || await _currentContext.ViewAssignedCollections(orgId);
}
}

View File

@ -7,123 +7,124 @@ using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Route("devices")]
[Authorize("Application")]
public class DevicesController : Controller
namespace Bit.Api.Controllers
{
private readonly IDeviceRepository _deviceRepository;
private readonly IDeviceService _deviceService;
private readonly IUserService _userService;
public DevicesController(
IDeviceRepository deviceRepository,
IDeviceService deviceService,
IUserService userService)
[Route("devices")]
[Authorize("Application")]
public class DevicesController : Controller
{
_deviceRepository = deviceRepository;
_deviceService = deviceService;
_userService = userService;
}
private readonly IDeviceRepository _deviceRepository;
private readonly IDeviceService _deviceService;
private readonly IUserService _userService;
[HttpGet("{id}")]
public async Task<DeviceResponseModel> Get(string id)
{
var device = await _deviceRepository.GetByIdAsync(new Guid(id), _userService.GetProperUserId(User).Value);
if (device == null)
public DevicesController(
IDeviceRepository deviceRepository,
IDeviceService deviceService,
IUserService userService)
{
throw new NotFoundException();
_deviceRepository = deviceRepository;
_deviceService = deviceService;
_userService = userService;
}
var response = new DeviceResponseModel(device);
return response;
}
[HttpGet("identifier/{identifier}")]
public async Task<DeviceResponseModel> GetByIdentifier(string identifier)
{
var device = await _deviceRepository.GetByIdentifierAsync(identifier, _userService.GetProperUserId(User).Value);
if (device == null)
[HttpGet("{id}")]
public async Task<DeviceResponseModel> Get(string id)
{
throw new NotFoundException();
var device = await _deviceRepository.GetByIdAsync(new Guid(id), _userService.GetProperUserId(User).Value);
if (device == null)
{
throw new NotFoundException();
}
var response = new DeviceResponseModel(device);
return response;
}
var response = new DeviceResponseModel(device);
return response;
}
[HttpGet("")]
public async Task<ListResponseModel<DeviceResponseModel>> Get()
{
ICollection<Device> devices = await _deviceRepository.GetManyByUserIdAsync(_userService.GetProperUserId(User).Value);
var responses = devices.Select(d => new DeviceResponseModel(d));
return new ListResponseModel<DeviceResponseModel>(responses);
}
[HttpPost("")]
public async Task<DeviceResponseModel> Post([FromBody] DeviceRequestModel model)
{
var device = model.ToDevice(_userService.GetProperUserId(User));
await _deviceService.SaveAsync(device);
var response = new DeviceResponseModel(device);
return response;
}
[HttpPut("{id}")]
[HttpPost("{id}")]
public async Task<DeviceResponseModel> Put(string id, [FromBody] DeviceRequestModel model)
{
var device = await _deviceRepository.GetByIdAsync(new Guid(id), _userService.GetProperUserId(User).Value);
if (device == null)
[HttpGet("identifier/{identifier}")]
public async Task<DeviceResponseModel> GetByIdentifier(string identifier)
{
throw new NotFoundException();
var device = await _deviceRepository.GetByIdentifierAsync(identifier, _userService.GetProperUserId(User).Value);
if (device == null)
{
throw new NotFoundException();
}
var response = new DeviceResponseModel(device);
return response;
}
await _deviceService.SaveAsync(model.ToDevice(device));
var response = new DeviceResponseModel(device);
return response;
}
[HttpPut("identifier/{identifier}/token")]
[HttpPost("identifier/{identifier}/token")]
public async Task PutToken(string identifier, [FromBody] DeviceTokenRequestModel model)
{
var device = await _deviceRepository.GetByIdentifierAsync(identifier, _userService.GetProperUserId(User).Value);
if (device == null)
[HttpGet("")]
public async Task<ListResponseModel<DeviceResponseModel>> Get()
{
throw new NotFoundException();
ICollection<Device> devices = await _deviceRepository.GetManyByUserIdAsync(_userService.GetProperUserId(User).Value);
var responses = devices.Select(d => new DeviceResponseModel(d));
return new ListResponseModel<DeviceResponseModel>(responses);
}
await _deviceService.SaveAsync(model.ToDevice(device));
}
[AllowAnonymous]
[HttpPut("identifier/{identifier}/clear-token")]
[HttpPost("identifier/{identifier}/clear-token")]
public async Task PutClearToken(string identifier)
{
var device = await _deviceRepository.GetByIdentifierAsync(identifier);
if (device == null)
[HttpPost("")]
public async Task<DeviceResponseModel> Post([FromBody] DeviceRequestModel model)
{
throw new NotFoundException();
var device = model.ToDevice(_userService.GetProperUserId(User));
await _deviceService.SaveAsync(device);
var response = new DeviceResponseModel(device);
return response;
}
await _deviceService.ClearTokenAsync(device);
}
[HttpDelete("{id}")]
[HttpPost("{id}/delete")]
public async Task Delete(string id)
{
var device = await _deviceRepository.GetByIdAsync(new Guid(id), _userService.GetProperUserId(User).Value);
if (device == null)
[HttpPut("{id}")]
[HttpPost("{id}")]
public async Task<DeviceResponseModel> Put(string id, [FromBody] DeviceRequestModel model)
{
throw new NotFoundException();
var device = await _deviceRepository.GetByIdAsync(new Guid(id), _userService.GetProperUserId(User).Value);
if (device == null)
{
throw new NotFoundException();
}
await _deviceService.SaveAsync(model.ToDevice(device));
var response = new DeviceResponseModel(device);
return response;
}
await _deviceService.DeleteAsync(device);
[HttpPut("identifier/{identifier}/token")]
[HttpPost("identifier/{identifier}/token")]
public async Task PutToken(string identifier, [FromBody] DeviceTokenRequestModel model)
{
var device = await _deviceRepository.GetByIdentifierAsync(identifier, _userService.GetProperUserId(User).Value);
if (device == null)
{
throw new NotFoundException();
}
await _deviceService.SaveAsync(model.ToDevice(device));
}
[AllowAnonymous]
[HttpPut("identifier/{identifier}/clear-token")]
[HttpPost("identifier/{identifier}/clear-token")]
public async Task PutClearToken(string identifier)
{
var device = await _deviceRepository.GetByIdentifierAsync(identifier);
if (device == null)
{
throw new NotFoundException();
}
await _deviceService.ClearTokenAsync(device);
}
[HttpDelete("{id}")]
[HttpPost("{id}/delete")]
public async Task Delete(string id)
{
var device = await _deviceRepository.GetByIdAsync(new Guid(id), _userService.GetProperUserId(User).Value);
if (device == null)
{
throw new NotFoundException();
}
await _deviceService.DeleteAsync(device);
}
}
}

View File

@ -9,169 +9,170 @@ using Bit.Core.Settings;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Route("emergency-access")]
[Authorize("Application")]
public class EmergencyAccessController : Controller
namespace Bit.Api.Controllers
{
private readonly IUserService _userService;
private readonly IEmergencyAccessRepository _emergencyAccessRepository;
private readonly IEmergencyAccessService _emergencyAccessService;
private readonly IGlobalSettings _globalSettings;
public EmergencyAccessController(
IUserService userService,
IEmergencyAccessRepository emergencyAccessRepository,
IEmergencyAccessService emergencyAccessService,
IGlobalSettings globalSettings)
[Route("emergency-access")]
[Authorize("Application")]
public class EmergencyAccessController : Controller
{
_userService = userService;
_emergencyAccessRepository = emergencyAccessRepository;
_emergencyAccessService = emergencyAccessService;
_globalSettings = globalSettings;
}
private readonly IUserService _userService;
private readonly IEmergencyAccessRepository _emergencyAccessRepository;
private readonly IEmergencyAccessService _emergencyAccessService;
private readonly IGlobalSettings _globalSettings;
[HttpGet("trusted")]
public async Task<ListResponseModel<EmergencyAccessGranteeDetailsResponseModel>> GetContacts()
{
var userId = _userService.GetProperUserId(User);
var granteeDetails = await _emergencyAccessRepository.GetManyDetailsByGrantorIdAsync(userId.Value);
var responses = granteeDetails.Select(d =>
new EmergencyAccessGranteeDetailsResponseModel(d));
return new ListResponseModel<EmergencyAccessGranteeDetailsResponseModel>(responses);
}
[HttpGet("granted")]
public async Task<ListResponseModel<EmergencyAccessGrantorDetailsResponseModel>> GetGrantees()
{
var userId = _userService.GetProperUserId(User);
var granteeDetails = await _emergencyAccessRepository.GetManyDetailsByGranteeIdAsync(userId.Value);
var responses = granteeDetails.Select(d => new EmergencyAccessGrantorDetailsResponseModel(d));
return new ListResponseModel<EmergencyAccessGrantorDetailsResponseModel>(responses);
}
[HttpGet("{id}")]
public async Task<EmergencyAccessGranteeDetailsResponseModel> Get(Guid id)
{
var userId = _userService.GetProperUserId(User);
var result = await _emergencyAccessService.GetAsync(id, userId.Value);
return new EmergencyAccessGranteeDetailsResponseModel(result);
}
[HttpGet("{id}/policies")]
public async Task<ListResponseModel<PolicyResponseModel>> Policies(Guid id)
{
var user = await _userService.GetUserByPrincipalAsync(User);
var policies = await _emergencyAccessService.GetPoliciesAsync(id, user);
var responses = policies.Select<Policy, PolicyResponseModel>(policy => new PolicyResponseModel(policy));
return new ListResponseModel<PolicyResponseModel>(responses);
}
[HttpPut("{id}")]
[HttpPost("{id}")]
public async Task Put(Guid id, [FromBody] EmergencyAccessUpdateRequestModel model)
{
var emergencyAccess = await _emergencyAccessRepository.GetByIdAsync(id);
if (emergencyAccess == null)
public EmergencyAccessController(
IUserService userService,
IEmergencyAccessRepository emergencyAccessRepository,
IEmergencyAccessService emergencyAccessService,
IGlobalSettings globalSettings)
{
throw new NotFoundException();
_userService = userService;
_emergencyAccessRepository = emergencyAccessRepository;
_emergencyAccessService = emergencyAccessService;
_globalSettings = globalSettings;
}
var user = await _userService.GetUserByPrincipalAsync(User);
await _emergencyAccessService.SaveAsync(model.ToEmergencyAccess(emergencyAccess), user);
}
[HttpGet("trusted")]
public async Task<ListResponseModel<EmergencyAccessGranteeDetailsResponseModel>> GetContacts()
{
var userId = _userService.GetProperUserId(User);
var granteeDetails = await _emergencyAccessRepository.GetManyDetailsByGrantorIdAsync(userId.Value);
[HttpDelete("{id}")]
[HttpPost("{id}/delete")]
public async Task Delete(Guid id)
{
var userId = _userService.GetProperUserId(User);
await _emergencyAccessService.DeleteAsync(id, userId.Value);
}
var responses = granteeDetails.Select(d =>
new EmergencyAccessGranteeDetailsResponseModel(d));
[HttpPost("invite")]
public async Task Invite([FromBody] EmergencyAccessInviteRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
await _emergencyAccessService.InviteAsync(user, model.Email, model.Type.Value, model.WaitTimeDays);
}
return new ListResponseModel<EmergencyAccessGranteeDetailsResponseModel>(responses);
}
[HttpPost("{id}/reinvite")]
public async Task Reinvite(Guid id)
{
var user = await _userService.GetUserByPrincipalAsync(User);
await _emergencyAccessService.ResendInviteAsync(user, id);
}
[HttpGet("granted")]
public async Task<ListResponseModel<EmergencyAccessGrantorDetailsResponseModel>> GetGrantees()
{
var userId = _userService.GetProperUserId(User);
var granteeDetails = await _emergencyAccessRepository.GetManyDetailsByGranteeIdAsync(userId.Value);
[HttpPost("{id}/accept")]
public async Task Accept(Guid id, [FromBody] OrganizationUserAcceptRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
await _emergencyAccessService.AcceptUserAsync(id, user, model.Token, _userService);
}
var responses = granteeDetails.Select(d => new EmergencyAccessGrantorDetailsResponseModel(d));
[HttpPost("{id}/confirm")]
public async Task Confirm(Guid id, [FromBody] OrganizationUserConfirmRequestModel model)
{
var userId = _userService.GetProperUserId(User);
await _emergencyAccessService.ConfirmUserAsync(id, model.Key, userId.Value);
}
return new ListResponseModel<EmergencyAccessGrantorDetailsResponseModel>(responses);
}
[HttpPost("{id}/initiate")]
public async Task Initiate(Guid id)
{
var user = await _userService.GetUserByPrincipalAsync(User);
await _emergencyAccessService.InitiateAsync(id, user);
}
[HttpGet("{id}")]
public async Task<EmergencyAccessGranteeDetailsResponseModel> Get(Guid id)
{
var userId = _userService.GetProperUserId(User);
var result = await _emergencyAccessService.GetAsync(id, userId.Value);
return new EmergencyAccessGranteeDetailsResponseModel(result);
}
[HttpPost("{id}/approve")]
public async Task Accept(Guid id)
{
var user = await _userService.GetUserByPrincipalAsync(User);
await _emergencyAccessService.ApproveAsync(id, user);
}
[HttpGet("{id}/policies")]
public async Task<ListResponseModel<PolicyResponseModel>> Policies(Guid id)
{
var user = await _userService.GetUserByPrincipalAsync(User);
var policies = await _emergencyAccessService.GetPoliciesAsync(id, user);
var responses = policies.Select<Policy, PolicyResponseModel>(policy => new PolicyResponseModel(policy));
return new ListResponseModel<PolicyResponseModel>(responses);
}
[HttpPost("{id}/reject")]
public async Task Reject(Guid id)
{
var user = await _userService.GetUserByPrincipalAsync(User);
await _emergencyAccessService.RejectAsync(id, user);
}
[HttpPut("{id}")]
[HttpPost("{id}")]
public async Task Put(Guid id, [FromBody] EmergencyAccessUpdateRequestModel model)
{
var emergencyAccess = await _emergencyAccessRepository.GetByIdAsync(id);
if (emergencyAccess == null)
{
throw new NotFoundException();
}
[HttpPost("{id}/takeover")]
public async Task<EmergencyAccessTakeoverResponseModel> Takeover(Guid id)
{
var user = await _userService.GetUserByPrincipalAsync(User);
var (result, grantor) = await _emergencyAccessService.TakeoverAsync(id, user);
return new EmergencyAccessTakeoverResponseModel(result, grantor);
}
var user = await _userService.GetUserByPrincipalAsync(User);
await _emergencyAccessService.SaveAsync(model.ToEmergencyAccess(emergencyAccess), user);
}
[HttpPost("{id}/password")]
public async Task Password(Guid id, [FromBody] EmergencyAccessPasswordRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
await _emergencyAccessService.PasswordAsync(id, user, model.NewMasterPasswordHash, model.Key);
}
[HttpDelete("{id}")]
[HttpPost("{id}/delete")]
public async Task Delete(Guid id)
{
var userId = _userService.GetProperUserId(User);
await _emergencyAccessService.DeleteAsync(id, userId.Value);
}
[HttpPost("{id}/view")]
public async Task<EmergencyAccessViewResponseModel> ViewCiphers(Guid id)
{
var user = await _userService.GetUserByPrincipalAsync(User);
var viewResult = await _emergencyAccessService.ViewAsync(id, user);
return new EmergencyAccessViewResponseModel(_globalSettings, viewResult.EmergencyAccess, viewResult.Ciphers);
}
[HttpPost("invite")]
public async Task Invite([FromBody] EmergencyAccessInviteRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
await _emergencyAccessService.InviteAsync(user, model.Email, model.Type.Value, model.WaitTimeDays);
}
[HttpGet("{id}/{cipherId}/attachment/{attachmentId}")]
public async Task<AttachmentResponseModel> GetAttachmentData(Guid id, Guid cipherId, string attachmentId)
{
var user = await _userService.GetUserByPrincipalAsync(User);
var result =
await _emergencyAccessService.GetAttachmentDownloadAsync(id, cipherId, attachmentId, user);
return new AttachmentResponseModel(result);
[HttpPost("{id}/reinvite")]
public async Task Reinvite(Guid id)
{
var user = await _userService.GetUserByPrincipalAsync(User);
await _emergencyAccessService.ResendInviteAsync(user, id);
}
[HttpPost("{id}/accept")]
public async Task Accept(Guid id, [FromBody] OrganizationUserAcceptRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
await _emergencyAccessService.AcceptUserAsync(id, user, model.Token, _userService);
}
[HttpPost("{id}/confirm")]
public async Task Confirm(Guid id, [FromBody] OrganizationUserConfirmRequestModel model)
{
var userId = _userService.GetProperUserId(User);
await _emergencyAccessService.ConfirmUserAsync(id, model.Key, userId.Value);
}
[HttpPost("{id}/initiate")]
public async Task Initiate(Guid id)
{
var user = await _userService.GetUserByPrincipalAsync(User);
await _emergencyAccessService.InitiateAsync(id, user);
}
[HttpPost("{id}/approve")]
public async Task Accept(Guid id)
{
var user = await _userService.GetUserByPrincipalAsync(User);
await _emergencyAccessService.ApproveAsync(id, user);
}
[HttpPost("{id}/reject")]
public async Task Reject(Guid id)
{
var user = await _userService.GetUserByPrincipalAsync(User);
await _emergencyAccessService.RejectAsync(id, user);
}
[HttpPost("{id}/takeover")]
public async Task<EmergencyAccessTakeoverResponseModel> Takeover(Guid id)
{
var user = await _userService.GetUserByPrincipalAsync(User);
var (result, grantor) = await _emergencyAccessService.TakeoverAsync(id, user);
return new EmergencyAccessTakeoverResponseModel(result, grantor);
}
[HttpPost("{id}/password")]
public async Task Password(Guid id, [FromBody] EmergencyAccessPasswordRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
await _emergencyAccessService.PasswordAsync(id, user, model.NewMasterPasswordHash, model.Key);
}
[HttpPost("{id}/view")]
public async Task<EmergencyAccessViewResponseModel> ViewCiphers(Guid id)
{
var user = await _userService.GetUserByPrincipalAsync(User);
var viewResult = await _emergencyAccessService.ViewAsync(id, user);
return new EmergencyAccessViewResponseModel(_globalSettings, viewResult.EmergencyAccess, viewResult.Ciphers);
}
[HttpGet("{id}/{cipherId}/attachment/{attachmentId}")]
public async Task<AttachmentResponseModel> GetAttachmentData(Guid id, Guid cipherId, string attachmentId)
{
var user = await _userService.GetUserByPrincipalAsync(User);
var result =
await _emergencyAccessService.GetAttachmentDownloadAsync(id, cipherId, attachmentId, user);
return new AttachmentResponseModel(result);
}
}
}

View File

@ -7,170 +7,171 @@ using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Route("events")]
[Authorize("Application")]
public class EventsController : Controller
namespace Bit.Api.Controllers
{
private readonly IUserService _userService;
private readonly ICipherRepository _cipherRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IProviderUserRepository _providerUserRepository;
private readonly IEventRepository _eventRepository;
private readonly ICurrentContext _currentContext;
public EventsController(
IUserService userService,
ICipherRepository cipherRepository,
IOrganizationUserRepository organizationUserRepository,
IProviderUserRepository providerUserRepository,
IEventRepository eventRepository,
ICurrentContext currentContext)
[Route("events")]
[Authorize("Application")]
public class EventsController : Controller
{
_userService = userService;
_cipherRepository = cipherRepository;
_organizationUserRepository = organizationUserRepository;
_providerUserRepository = providerUserRepository;
_eventRepository = eventRepository;
_currentContext = currentContext;
}
private readonly IUserService _userService;
private readonly ICipherRepository _cipherRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IProviderUserRepository _providerUserRepository;
private readonly IEventRepository _eventRepository;
private readonly ICurrentContext _currentContext;
[HttpGet("")]
public async Task<ListResponseModel<EventResponseModel>> GetUser(
[FromQuery] DateTime? start = null, [FromQuery] DateTime? end = null, [FromQuery] string continuationToken = null)
{
var dateRange = GetDateRange(start, end);
var userId = _userService.GetProperUserId(User).Value;
var result = await _eventRepository.GetManyByUserAsync(userId, dateRange.Item1, dateRange.Item2,
new PageOptions { ContinuationToken = continuationToken });
var responses = result.Data.Select(e => new EventResponseModel(e));
return new ListResponseModel<EventResponseModel>(responses, result.ContinuationToken);
}
[HttpGet("~/ciphers/{id}/events")]
public async Task<ListResponseModel<EventResponseModel>> GetCipher(string id,
[FromQuery] DateTime? start = null, [FromQuery] DateTime? end = null, [FromQuery] string continuationToken = null)
{
var cipher = await _cipherRepository.GetByIdAsync(new Guid(id));
if (cipher == null)
public EventsController(
IUserService userService,
ICipherRepository cipherRepository,
IOrganizationUserRepository organizationUserRepository,
IProviderUserRepository providerUserRepository,
IEventRepository eventRepository,
ICurrentContext currentContext)
{
throw new NotFoundException();
_userService = userService;
_cipherRepository = cipherRepository;
_organizationUserRepository = organizationUserRepository;
_providerUserRepository = providerUserRepository;
_eventRepository = eventRepository;
_currentContext = currentContext;
}
var canView = false;
if (cipher.OrganizationId.HasValue)
{
canView = await _currentContext.AccessEventLogs(cipher.OrganizationId.Value);
}
else if (cipher.UserId.HasValue)
[HttpGet("")]
public async Task<ListResponseModel<EventResponseModel>> GetUser(
[FromQuery] DateTime? start = null, [FromQuery] DateTime? end = null, [FromQuery] string continuationToken = null)
{
var dateRange = GetDateRange(start, end);
var userId = _userService.GetProperUserId(User).Value;
canView = userId == cipher.UserId.Value;
var result = await _eventRepository.GetManyByUserAsync(userId, dateRange.Item1, dateRange.Item2,
new PageOptions { ContinuationToken = continuationToken });
var responses = result.Data.Select(e => new EventResponseModel(e));
return new ListResponseModel<EventResponseModel>(responses, result.ContinuationToken);
}
if (!canView)
[HttpGet("~/ciphers/{id}/events")]
public async Task<ListResponseModel<EventResponseModel>> GetCipher(string id,
[FromQuery] DateTime? start = null, [FromQuery] DateTime? end = null, [FromQuery] string continuationToken = null)
{
throw new NotFoundException();
var cipher = await _cipherRepository.GetByIdAsync(new Guid(id));
if (cipher == null)
{
throw new NotFoundException();
}
var canView = false;
if (cipher.OrganizationId.HasValue)
{
canView = await _currentContext.AccessEventLogs(cipher.OrganizationId.Value);
}
else if (cipher.UserId.HasValue)
{
var userId = _userService.GetProperUserId(User).Value;
canView = userId == cipher.UserId.Value;
}
if (!canView)
{
throw new NotFoundException();
}
var dateRange = GetDateRange(start, end);
var result = await _eventRepository.GetManyByCipherAsync(cipher, dateRange.Item1, dateRange.Item2,
new PageOptions { ContinuationToken = continuationToken });
var responses = result.Data.Select(e => new EventResponseModel(e));
return new ListResponseModel<EventResponseModel>(responses, result.ContinuationToken);
}
var dateRange = GetDateRange(start, end);
var result = await _eventRepository.GetManyByCipherAsync(cipher, dateRange.Item1, dateRange.Item2,
new PageOptions { ContinuationToken = continuationToken });
var responses = result.Data.Select(e => new EventResponseModel(e));
return new ListResponseModel<EventResponseModel>(responses, result.ContinuationToken);
}
[HttpGet("~/organizations/{id}/events")]
public async Task<ListResponseModel<EventResponseModel>> GetOrganization(string id,
[FromQuery] DateTime? start = null, [FromQuery] DateTime? end = null, [FromQuery] string continuationToken = null)
{
var orgId = new Guid(id);
if (!await _currentContext.AccessEventLogs(orgId))
[HttpGet("~/organizations/{id}/events")]
public async Task<ListResponseModel<EventResponseModel>> GetOrganization(string id,
[FromQuery] DateTime? start = null, [FromQuery] DateTime? end = null, [FromQuery] string continuationToken = null)
{
throw new NotFoundException();
var orgId = new Guid(id);
if (!await _currentContext.AccessEventLogs(orgId))
{
throw new NotFoundException();
}
var dateRange = GetDateRange(start, end);
var result = await _eventRepository.GetManyByOrganizationAsync(orgId, dateRange.Item1, dateRange.Item2,
new PageOptions { ContinuationToken = continuationToken });
var responses = result.Data.Select(e => new EventResponseModel(e));
return new ListResponseModel<EventResponseModel>(responses, result.ContinuationToken);
}
var dateRange = GetDateRange(start, end);
var result = await _eventRepository.GetManyByOrganizationAsync(orgId, dateRange.Item1, dateRange.Item2,
new PageOptions { ContinuationToken = continuationToken });
var responses = result.Data.Select(e => new EventResponseModel(e));
return new ListResponseModel<EventResponseModel>(responses, result.ContinuationToken);
}
[HttpGet("~/organizations/{orgId}/users/{id}/events")]
public async Task<ListResponseModel<EventResponseModel>> GetOrganizationUser(string orgId, string id,
[FromQuery] DateTime? start = null, [FromQuery] DateTime? end = null, [FromQuery] string continuationToken = null)
{
var organizationUser = await _organizationUserRepository.GetByIdAsync(new Guid(id));
if (organizationUser == null || !organizationUser.UserId.HasValue ||
!await _currentContext.AccessEventLogs(organizationUser.OrganizationId))
[HttpGet("~/organizations/{orgId}/users/{id}/events")]
public async Task<ListResponseModel<EventResponseModel>> GetOrganizationUser(string orgId, string id,
[FromQuery] DateTime? start = null, [FromQuery] DateTime? end = null, [FromQuery] string continuationToken = null)
{
throw new NotFoundException();
var organizationUser = await _organizationUserRepository.GetByIdAsync(new Guid(id));
if (organizationUser == null || !organizationUser.UserId.HasValue ||
!await _currentContext.AccessEventLogs(organizationUser.OrganizationId))
{
throw new NotFoundException();
}
var dateRange = GetDateRange(start, end);
var result = await _eventRepository.GetManyByOrganizationActingUserAsync(organizationUser.OrganizationId,
organizationUser.UserId.Value, dateRange.Item1, dateRange.Item2,
new PageOptions { ContinuationToken = continuationToken });
var responses = result.Data.Select(e => new EventResponseModel(e));
return new ListResponseModel<EventResponseModel>(responses, result.ContinuationToken);
}
var dateRange = GetDateRange(start, end);
var result = await _eventRepository.GetManyByOrganizationActingUserAsync(organizationUser.OrganizationId,
organizationUser.UserId.Value, dateRange.Item1, dateRange.Item2,
new PageOptions { ContinuationToken = continuationToken });
var responses = result.Data.Select(e => new EventResponseModel(e));
return new ListResponseModel<EventResponseModel>(responses, result.ContinuationToken);
}
[HttpGet("~/providers/{providerId:guid}/events")]
public async Task<ListResponseModel<EventResponseModel>> GetProvider(Guid providerId,
[FromQuery] DateTime? start = null, [FromQuery] DateTime? end = null, [FromQuery] string continuationToken = null)
{
if (!_currentContext.ProviderAccessEventLogs(providerId))
[HttpGet("~/providers/{providerId:guid}/events")]
public async Task<ListResponseModel<EventResponseModel>> GetProvider(Guid providerId,
[FromQuery] DateTime? start = null, [FromQuery] DateTime? end = null, [FromQuery] string continuationToken = null)
{
throw new NotFoundException();
if (!_currentContext.ProviderAccessEventLogs(providerId))
{
throw new NotFoundException();
}
var dateRange = GetDateRange(start, end);
var result = await _eventRepository.GetManyByProviderAsync(providerId, dateRange.Item1, dateRange.Item2,
new PageOptions { ContinuationToken = continuationToken });
var responses = result.Data.Select(e => new EventResponseModel(e));
return new ListResponseModel<EventResponseModel>(responses, result.ContinuationToken);
}
var dateRange = GetDateRange(start, end);
var result = await _eventRepository.GetManyByProviderAsync(providerId, dateRange.Item1, dateRange.Item2,
new PageOptions { ContinuationToken = continuationToken });
var responses = result.Data.Select(e => new EventResponseModel(e));
return new ListResponseModel<EventResponseModel>(responses, result.ContinuationToken);
}
[HttpGet("~/providers/{providerId:guid}/users/{id:guid}/events")]
public async Task<ListResponseModel<EventResponseModel>> GetProviderUser(Guid providerId, Guid id,
[FromQuery] DateTime? start = null, [FromQuery] DateTime? end = null, [FromQuery] string continuationToken = null)
{
var providerUser = await _providerUserRepository.GetByIdAsync(id);
if (providerUser == null || !providerUser.UserId.HasValue ||
!_currentContext.ProviderAccessEventLogs(providerUser.ProviderId))
[HttpGet("~/providers/{providerId:guid}/users/{id:guid}/events")]
public async Task<ListResponseModel<EventResponseModel>> GetProviderUser(Guid providerId, Guid id,
[FromQuery] DateTime? start = null, [FromQuery] DateTime? end = null, [FromQuery] string continuationToken = null)
{
throw new NotFoundException();
var providerUser = await _providerUserRepository.GetByIdAsync(id);
if (providerUser == null || !providerUser.UserId.HasValue ||
!_currentContext.ProviderAccessEventLogs(providerUser.ProviderId))
{
throw new NotFoundException();
}
var dateRange = GetDateRange(start, end);
var result = await _eventRepository.GetManyByProviderActingUserAsync(providerUser.ProviderId,
providerUser.UserId.Value, dateRange.Item1, dateRange.Item2,
new PageOptions { ContinuationToken = continuationToken });
var responses = result.Data.Select(e => new EventResponseModel(e));
return new ListResponseModel<EventResponseModel>(responses, result.ContinuationToken);
}
var dateRange = GetDateRange(start, end);
var result = await _eventRepository.GetManyByProviderActingUserAsync(providerUser.ProviderId,
providerUser.UserId.Value, dateRange.Item1, dateRange.Item2,
new PageOptions { ContinuationToken = continuationToken });
var responses = result.Data.Select(e => new EventResponseModel(e));
return new ListResponseModel<EventResponseModel>(responses, result.ContinuationToken);
}
private Tuple<DateTime, DateTime> GetDateRange(DateTime? start, DateTime? end)
{
if (!end.HasValue || !start.HasValue)
private Tuple<DateTime, DateTime> GetDateRange(DateTime? start, DateTime? end)
{
end = DateTime.UtcNow.Date.AddDays(1).AddMilliseconds(-1);
start = DateTime.UtcNow.Date.AddDays(-30);
}
else if (start.Value > end.Value)
{
var newEnd = start;
start = end;
end = newEnd;
}
if (!end.HasValue || !start.HasValue)
{
end = DateTime.UtcNow.Date.AddDays(1).AddMilliseconds(-1);
start = DateTime.UtcNow.Date.AddDays(-30);
}
else if (start.Value > end.Value)
{
var newEnd = start;
start = end;
end = newEnd;
}
if ((end.Value - start.Value) > TimeSpan.FromDays(367))
{
throw new BadRequestException("Range too large.");
}
if ((end.Value - start.Value) > TimeSpan.FromDays(367))
{
throw new BadRequestException("Range too large.");
}
return new Tuple<DateTime, DateTime>(start.Value, end.Value);
return new Tuple<DateTime, DateTime>(start.Value, end.Value);
}
}
}

View File

@ -6,83 +6,84 @@ using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Route("folders")]
[Authorize("Application")]
public class FoldersController : Controller
namespace Bit.Api.Controllers
{
private readonly IFolderRepository _folderRepository;
private readonly ICipherService _cipherService;
private readonly IUserService _userService;
public FoldersController(
IFolderRepository folderRepository,
ICipherService cipherService,
IUserService userService)
[Route("folders")]
[Authorize("Application")]
public class FoldersController : Controller
{
_folderRepository = folderRepository;
_cipherService = cipherService;
_userService = userService;
}
private readonly IFolderRepository _folderRepository;
private readonly ICipherService _cipherService;
private readonly IUserService _userService;
[HttpGet("{id}")]
public async Task<FolderResponseModel> Get(string id)
{
var userId = _userService.GetProperUserId(User).Value;
var folder = await _folderRepository.GetByIdAsync(new Guid(id), userId);
if (folder == null)
public FoldersController(
IFolderRepository folderRepository,
ICipherService cipherService,
IUserService userService)
{
throw new NotFoundException();
_folderRepository = folderRepository;
_cipherService = cipherService;
_userService = userService;
}
return new FolderResponseModel(folder);
}
[HttpGet("")]
public async Task<ListResponseModel<FolderResponseModel>> Get()
{
var userId = _userService.GetProperUserId(User).Value;
var folders = await _folderRepository.GetManyByUserIdAsync(userId);
var responses = folders.Select(f => new FolderResponseModel(f));
return new ListResponseModel<FolderResponseModel>(responses);
}
[HttpPost("")]
public async Task<FolderResponseModel> Post([FromBody] FolderRequestModel model)
{
var userId = _userService.GetProperUserId(User).Value;
var folder = model.ToFolder(_userService.GetProperUserId(User).Value);
await _cipherService.SaveFolderAsync(folder);
return new FolderResponseModel(folder);
}
[HttpPut("{id}")]
[HttpPost("{id}")]
public async Task<FolderResponseModel> Put(string id, [FromBody] FolderRequestModel model)
{
var userId = _userService.GetProperUserId(User).Value;
var folder = await _folderRepository.GetByIdAsync(new Guid(id), userId);
if (folder == null)
[HttpGet("{id}")]
public async Task<FolderResponseModel> Get(string id)
{
throw new NotFoundException();
var userId = _userService.GetProperUserId(User).Value;
var folder = await _folderRepository.GetByIdAsync(new Guid(id), userId);
if (folder == null)
{
throw new NotFoundException();
}
return new FolderResponseModel(folder);
}
await _cipherService.SaveFolderAsync(model.ToFolder(folder));
return new FolderResponseModel(folder);
}
[HttpDelete("{id}")]
[HttpPost("{id}/delete")]
public async Task Delete(string id)
{
var userId = _userService.GetProperUserId(User).Value;
var folder = await _folderRepository.GetByIdAsync(new Guid(id), userId);
if (folder == null)
[HttpGet("")]
public async Task<ListResponseModel<FolderResponseModel>> Get()
{
throw new NotFoundException();
var userId = _userService.GetProperUserId(User).Value;
var folders = await _folderRepository.GetManyByUserIdAsync(userId);
var responses = folders.Select(f => new FolderResponseModel(f));
return new ListResponseModel<FolderResponseModel>(responses);
}
await _cipherService.DeleteFolderAsync(folder);
[HttpPost("")]
public async Task<FolderResponseModel> Post([FromBody] FolderRequestModel model)
{
var userId = _userService.GetProperUserId(User).Value;
var folder = model.ToFolder(_userService.GetProperUserId(User).Value);
await _cipherService.SaveFolderAsync(folder);
return new FolderResponseModel(folder);
}
[HttpPut("{id}")]
[HttpPost("{id}")]
public async Task<FolderResponseModel> Put(string id, [FromBody] FolderRequestModel model)
{
var userId = _userService.GetProperUserId(User).Value;
var folder = await _folderRepository.GetByIdAsync(new Guid(id), userId);
if (folder == null)
{
throw new NotFoundException();
}
await _cipherService.SaveFolderAsync(model.ToFolder(folder));
return new FolderResponseModel(folder);
}
[HttpDelete("{id}")]
[HttpPost("{id}/delete")]
public async Task Delete(string id)
{
var userId = _userService.GetProperUserId(User).Value;
var folder = await _folderRepository.GetByIdAsync(new Guid(id), userId);
if (folder == null)
{
throw new NotFoundException();
}
await _cipherService.DeleteFolderAsync(folder);
}
}
}

View File

@ -7,145 +7,146 @@ using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Route("organizations/{orgId}/groups")]
[Authorize("Application")]
public class GroupsController : Controller
namespace Bit.Api.Controllers
{
private readonly IGroupRepository _groupRepository;
private readonly IGroupService _groupService;
private readonly ICurrentContext _currentContext;
public GroupsController(
IGroupRepository groupRepository,
IGroupService groupService,
ICurrentContext currentContext)
[Route("organizations/{orgId}/groups")]
[Authorize("Application")]
public class GroupsController : Controller
{
_groupRepository = groupRepository;
_groupService = groupService;
_currentContext = currentContext;
}
private readonly IGroupRepository _groupRepository;
private readonly IGroupService _groupService;
private readonly ICurrentContext _currentContext;
[HttpGet("{id}")]
public async Task<GroupResponseModel> Get(string orgId, string id)
{
var group = await _groupRepository.GetByIdAsync(new Guid(id));
if (group == null || !await _currentContext.ManageGroups(group.OrganizationId))
public GroupsController(
IGroupRepository groupRepository,
IGroupService groupService,
ICurrentContext currentContext)
{
throw new NotFoundException();
_groupRepository = groupRepository;
_groupService = groupService;
_currentContext = currentContext;
}
return new GroupResponseModel(group);
}
[HttpGet("{id}/details")]
public async Task<GroupDetailsResponseModel> GetDetails(string orgId, string id)
{
var groupDetails = await _groupRepository.GetByIdWithCollectionsAsync(new Guid(id));
if (groupDetails?.Item1 == null || !await _currentContext.ManageGroups(groupDetails.Item1.OrganizationId))
[HttpGet("{id}")]
public async Task<GroupResponseModel> Get(string orgId, string id)
{
throw new NotFoundException();
var group = await _groupRepository.GetByIdAsync(new Guid(id));
if (group == null || !await _currentContext.ManageGroups(group.OrganizationId))
{
throw new NotFoundException();
}
return new GroupResponseModel(group);
}
return new GroupDetailsResponseModel(groupDetails.Item1, groupDetails.Item2);
}
[HttpGet("")]
public async Task<ListResponseModel<GroupResponseModel>> Get(string orgId)
{
var orgIdGuid = new Guid(orgId);
var canAccess = await _currentContext.ManageGroups(orgIdGuid) ||
await _currentContext.ViewAssignedCollections(orgIdGuid) ||
await _currentContext.ViewAllCollections(orgIdGuid) ||
await _currentContext.ManageUsers(orgIdGuid);
if (!canAccess)
[HttpGet("{id}/details")]
public async Task<GroupDetailsResponseModel> GetDetails(string orgId, string id)
{
throw new NotFoundException();
var groupDetails = await _groupRepository.GetByIdWithCollectionsAsync(new Guid(id));
if (groupDetails?.Item1 == null || !await _currentContext.ManageGroups(groupDetails.Item1.OrganizationId))
{
throw new NotFoundException();
}
return new GroupDetailsResponseModel(groupDetails.Item1, groupDetails.Item2);
}
var groups = await _groupRepository.GetManyByOrganizationIdAsync(orgIdGuid);
var responses = groups.Select(g => new GroupResponseModel(g));
return new ListResponseModel<GroupResponseModel>(responses);
}
[HttpGet("{id}/users")]
public async Task<IEnumerable<Guid>> GetUsers(string orgId, string id)
{
var idGuid = new Guid(id);
var group = await _groupRepository.GetByIdAsync(idGuid);
if (group == null || !await _currentContext.ManageGroups(group.OrganizationId))
[HttpGet("")]
public async Task<ListResponseModel<GroupResponseModel>> Get(string orgId)
{
throw new NotFoundException();
var orgIdGuid = new Guid(orgId);
var canAccess = await _currentContext.ManageGroups(orgIdGuid) ||
await _currentContext.ViewAssignedCollections(orgIdGuid) ||
await _currentContext.ViewAllCollections(orgIdGuid) ||
await _currentContext.ManageUsers(orgIdGuid);
if (!canAccess)
{
throw new NotFoundException();
}
var groups = await _groupRepository.GetManyByOrganizationIdAsync(orgIdGuid);
var responses = groups.Select(g => new GroupResponseModel(g));
return new ListResponseModel<GroupResponseModel>(responses);
}
var groupIds = await _groupRepository.GetManyUserIdsByIdAsync(idGuid);
return groupIds;
}
[HttpPost("")]
public async Task<GroupResponseModel> Post(string orgId, [FromBody] GroupRequestModel model)
{
var orgIdGuid = new Guid(orgId);
if (!await _currentContext.ManageGroups(orgIdGuid))
[HttpGet("{id}/users")]
public async Task<IEnumerable<Guid>> GetUsers(string orgId, string id)
{
throw new NotFoundException();
var idGuid = new Guid(id);
var group = await _groupRepository.GetByIdAsync(idGuid);
if (group == null || !await _currentContext.ManageGroups(group.OrganizationId))
{
throw new NotFoundException();
}
var groupIds = await _groupRepository.GetManyUserIdsByIdAsync(idGuid);
return groupIds;
}
var group = model.ToGroup(orgIdGuid);
await _groupService.SaveAsync(group, model.Collections?.Select(c => c.ToSelectionReadOnly()));
return new GroupResponseModel(group);
}
[HttpPut("{id}")]
[HttpPost("{id}")]
public async Task<GroupResponseModel> Put(string orgId, string id, [FromBody] GroupRequestModel model)
{
var group = await _groupRepository.GetByIdAsync(new Guid(id));
if (group == null || !await _currentContext.ManageGroups(group.OrganizationId))
[HttpPost("")]
public async Task<GroupResponseModel> Post(string orgId, [FromBody] GroupRequestModel model)
{
throw new NotFoundException();
var orgIdGuid = new Guid(orgId);
if (!await _currentContext.ManageGroups(orgIdGuid))
{
throw new NotFoundException();
}
var group = model.ToGroup(orgIdGuid);
await _groupService.SaveAsync(group, model.Collections?.Select(c => c.ToSelectionReadOnly()));
return new GroupResponseModel(group);
}
await _groupService.SaveAsync(model.ToGroup(group), model.Collections?.Select(c => c.ToSelectionReadOnly()));
return new GroupResponseModel(group);
}
[HttpPut("{id}/users")]
public async Task PutUsers(string orgId, string id, [FromBody] IEnumerable<Guid> model)
{
var group = await _groupRepository.GetByIdAsync(new Guid(id));
if (group == null || !await _currentContext.ManageGroups(group.OrganizationId))
[HttpPut("{id}")]
[HttpPost("{id}")]
public async Task<GroupResponseModel> Put(string orgId, string id, [FromBody] GroupRequestModel model)
{
throw new NotFoundException();
}
await _groupRepository.UpdateUsersAsync(group.Id, model);
}
var group = await _groupRepository.GetByIdAsync(new Guid(id));
if (group == null || !await _currentContext.ManageGroups(group.OrganizationId))
{
throw new NotFoundException();
}
[HttpDelete("{id}")]
[HttpPost("{id}/delete")]
public async Task Delete(string orgId, string id)
{
var group = await _groupRepository.GetByIdAsync(new Guid(id));
if (group == null || !await _currentContext.ManageGroups(group.OrganizationId))
{
throw new NotFoundException();
await _groupService.SaveAsync(model.ToGroup(group), model.Collections?.Select(c => c.ToSelectionReadOnly()));
return new GroupResponseModel(group);
}
await _groupService.DeleteAsync(group);
}
[HttpDelete("{id}/user/{orgUserId}")]
[HttpPost("{id}/delete-user/{orgUserId}")]
public async Task Delete(string orgId, string id, string orgUserId)
{
var group = await _groupRepository.GetByIdAsync(new Guid(id));
if (group == null || !await _currentContext.ManageGroups(group.OrganizationId))
[HttpPut("{id}/users")]
public async Task PutUsers(string orgId, string id, [FromBody] IEnumerable<Guid> model)
{
throw new NotFoundException();
var group = await _groupRepository.GetByIdAsync(new Guid(id));
if (group == null || !await _currentContext.ManageGroups(group.OrganizationId))
{
throw new NotFoundException();
}
await _groupRepository.UpdateUsersAsync(group.Id, model);
}
await _groupService.DeleteUserAsync(group, new Guid(orgUserId));
[HttpDelete("{id}")]
[HttpPost("{id}/delete")]
public async Task Delete(string orgId, string id)
{
var group = await _groupRepository.GetByIdAsync(new Guid(id));
if (group == null || !await _currentContext.ManageGroups(group.OrganizationId))
{
throw new NotFoundException();
}
await _groupService.DeleteAsync(group);
}
[HttpDelete("{id}/user/{orgUserId}")]
[HttpPost("{id}/delete-user/{orgUserId}")]
public async Task Delete(string orgId, string id, string orgUserId)
{
var group = await _groupRepository.GetByIdAsync(new Guid(id));
if (group == null || !await _currentContext.ManageGroups(group.OrganizationId))
{
throw new NotFoundException();
}
await _groupService.DeleteUserAsync(group, new Guid(orgUserId));
}
}
}

View File

@ -8,90 +8,91 @@ using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Route("hibp")]
[Authorize("Application")]
public class HibpController : Controller
namespace Bit.Api.Controllers
{
private const string HibpBreachApi = "https://haveibeenpwned.com/api/v3/breachedaccount/{0}" +
"?truncateResponse=false&includeUnverified=false";
private static HttpClient _httpClient;
private readonly IUserService _userService;
private readonly ICurrentContext _currentContext;
private readonly GlobalSettings _globalSettings;
private readonly string _userAgent;
static HibpController()
[Route("hibp")]
[Authorize("Application")]
public class HibpController : Controller
{
_httpClient = new HttpClient();
}
private const string HibpBreachApi = "https://haveibeenpwned.com/api/v3/breachedaccount/{0}" +
"?truncateResponse=false&includeUnverified=false";
private static HttpClient _httpClient;
public HibpController(
IUserService userService,
ICurrentContext currentContext,
GlobalSettings globalSettings)
{
_userService = userService;
_currentContext = currentContext;
_globalSettings = globalSettings;
_userAgent = _globalSettings.SelfHosted ? "Bitwarden Self-Hosted" : "Bitwarden";
}
private readonly IUserService _userService;
private readonly ICurrentContext _currentContext;
private readonly GlobalSettings _globalSettings;
private readonly string _userAgent;
[HttpGet("breach")]
public async Task<IActionResult> Get(string username)
{
return await SendAsync(WebUtility.UrlEncode(username), true);
}
private async Task<IActionResult> SendAsync(string username, bool retry)
{
if (!CoreHelpers.SettingHasValue(_globalSettings.HibpApiKey))
static HibpController()
{
throw new BadRequestException("HaveIBeenPwned API key not set.");
_httpClient = new HttpClient();
}
var request = new HttpRequestMessage(HttpMethod.Get, string.Format(HibpBreachApi, username));
request.Headers.Add("hibp-api-key", _globalSettings.HibpApiKey);
request.Headers.Add("hibp-client-id", GetClientId());
request.Headers.Add("User-Agent", _userAgent);
var response = await _httpClient.SendAsync(request);
if (response.IsSuccessStatusCode)
public HibpController(
IUserService userService,
ICurrentContext currentContext,
GlobalSettings globalSettings)
{
var data = await response.Content.ReadAsStringAsync();
return Content(data, "application/json");
_userService = userService;
_currentContext = currentContext;
_globalSettings = globalSettings;
_userAgent = _globalSettings.SelfHosted ? "Bitwarden Self-Hosted" : "Bitwarden";
}
else if (response.StatusCode == HttpStatusCode.NotFound)
[HttpGet("breach")]
public async Task<IActionResult> Get(string username)
{
return new NotFoundResult();
return await SendAsync(WebUtility.UrlEncode(username), true);
}
else if (response.StatusCode == HttpStatusCode.TooManyRequests && retry)
private async Task<IActionResult> SendAsync(string username, bool retry)
{
var delay = 2000;
if (response.Headers.Contains("retry-after"))
if (!CoreHelpers.SettingHasValue(_globalSettings.HibpApiKey))
{
var vals = response.Headers.GetValues("retry-after");
if (vals.Any() && int.TryParse(vals.FirstOrDefault(), out var secDelay))
{
delay = (secDelay * 1000) + 200;
}
throw new BadRequestException("HaveIBeenPwned API key not set.");
}
var request = new HttpRequestMessage(HttpMethod.Get, string.Format(HibpBreachApi, username));
request.Headers.Add("hibp-api-key", _globalSettings.HibpApiKey);
request.Headers.Add("hibp-client-id", GetClientId());
request.Headers.Add("User-Agent", _userAgent);
var response = await _httpClient.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var data = await response.Content.ReadAsStringAsync();
return Content(data, "application/json");
}
else if (response.StatusCode == HttpStatusCode.NotFound)
{
return new NotFoundResult();
}
else if (response.StatusCode == HttpStatusCode.TooManyRequests && retry)
{
var delay = 2000;
if (response.Headers.Contains("retry-after"))
{
var vals = response.Headers.GetValues("retry-after");
if (vals.Any() && int.TryParse(vals.FirstOrDefault(), out var secDelay))
{
delay = (secDelay * 1000) + 200;
}
}
await Task.Delay(delay);
return await SendAsync(username, false);
}
else
{
throw new BadRequestException("Request failed. Status code: " + response.StatusCode);
}
await Task.Delay(delay);
return await SendAsync(username, false);
}
else
{
throw new BadRequestException("Request failed. Status code: " + response.StatusCode);
}
}
private string GetClientId()
{
var userId = _userService.GetProperUserId(User).Value;
using (var sha256 = SHA256.Create())
private string GetClientId()
{
var hash = sha256.ComputeHash(userId.ToByteArray());
return Convert.ToBase64String(hash);
var userId = _userService.GetProperUserId(User).Value;
using (var sha256 = SHA256.Create())
{
var hash = sha256.ComputeHash(userId.ToByteArray());
return Convert.ToBase64String(hash);
}
}
}
}

View File

@ -1,34 +1,35 @@
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
public class InfoController : Controller
namespace Bit.Api.Controllers
{
[HttpGet("~/alive")]
[HttpGet("~/now")]
public DateTime GetAlive()
public class InfoController : Controller
{
return DateTime.UtcNow;
}
[HttpGet("~/version")]
public JsonResult GetVersion()
{
return Json(CoreHelpers.GetVersion());
}
[HttpGet("~/ip")]
public JsonResult Ip()
{
var headerSet = new HashSet<string> { "x-forwarded-for", "cf-connecting-ip", "client-ip" };
var headers = HttpContext.Request?.Headers
.Where(h => headerSet.Contains(h.Key.ToLower()))
.ToDictionary(h => h.Key);
return new JsonResult(new
[HttpGet("~/alive")]
[HttpGet("~/now")]
public DateTime GetAlive()
{
Ip = HttpContext.Connection?.RemoteIpAddress?.ToString(),
Headers = headers,
});
return DateTime.UtcNow;
}
[HttpGet("~/version")]
public JsonResult GetVersion()
{
return Json(CoreHelpers.GetVersion());
}
[HttpGet("~/ip")]
public JsonResult Ip()
{
var headerSet = new HashSet<string> { "x-forwarded-for", "cf-connecting-ip", "client-ip" };
var headers = HttpContext.Request?.Headers
.Where(h => headerSet.Contains(h.Key.ToLower()))
.ToDictionary(h => h.Key);
return new JsonResult(new
{
Ip = HttpContext.Connection?.RemoteIpAddress?.ToString(),
Headers = headers,
});
}
}
}

View File

@ -6,39 +6,40 @@ using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Route("installations")]
[SelfHosted(NotSelfHostedOnly = true)]
public class InstallationsController : Controller
namespace Bit.Api.Controllers
{
private readonly IInstallationRepository _installationRepository;
public InstallationsController(
IInstallationRepository installationRepository)
[Route("installations")]
[SelfHosted(NotSelfHostedOnly = true)]
public class InstallationsController : Controller
{
_installationRepository = installationRepository;
}
private readonly IInstallationRepository _installationRepository;
[HttpGet("{id}")]
[AllowAnonymous]
public async Task<InstallationResponseModel> Get(Guid id)
{
var installation = await _installationRepository.GetByIdAsync(id);
if (installation == null)
public InstallationsController(
IInstallationRepository installationRepository)
{
throw new NotFoundException();
_installationRepository = installationRepository;
}
return new InstallationResponseModel(installation, false);
}
[HttpGet("{id}")]
[AllowAnonymous]
public async Task<InstallationResponseModel> Get(Guid id)
{
var installation = await _installationRepository.GetByIdAsync(id);
if (installation == null)
{
throw new NotFoundException();
}
[HttpPost("")]
[AllowAnonymous]
public async Task<InstallationResponseModel> Post([FromBody] InstallationRequestModel model)
{
var installation = model.ToInstallation();
await _installationRepository.CreateAsync(installation);
return new InstallationResponseModel(installation, true);
return new InstallationResponseModel(installation, false);
}
[HttpPost("")]
[AllowAnonymous]
public async Task<InstallationResponseModel> Post([FromBody] InstallationRequestModel model)
{
var installation = model.ToInstallation();
await _installationRepository.CreateAsync(installation);
return new InstallationResponseModel(installation, true);
}
}
}

View File

@ -7,69 +7,70 @@ using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Route("licenses")]
[Authorize("Licensing")]
[SelfHosted(NotSelfHostedOnly = true)]
public class LicensesController : Controller
namespace Bit.Api.Controllers
{
private readonly ILicensingService _licensingService;
private readonly IUserRepository _userRepository;
private readonly IUserService _userService;
private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationService _organizationService;
private readonly ICurrentContext _currentContext;
public LicensesController(
ILicensingService licensingService,
IUserRepository userRepository,
IUserService userService,
IOrganizationRepository organizationRepository,
IOrganizationService organizationService,
ICurrentContext currentContext)
[Route("licenses")]
[Authorize("Licensing")]
[SelfHosted(NotSelfHostedOnly = true)]
public class LicensesController : Controller
{
_licensingService = licensingService;
_userRepository = userRepository;
_userService = userService;
_organizationRepository = organizationRepository;
_organizationService = organizationService;
_currentContext = currentContext;
}
private readonly ILicensingService _licensingService;
private readonly IUserRepository _userRepository;
private readonly IUserService _userService;
private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationService _organizationService;
private readonly ICurrentContext _currentContext;
[HttpGet("user/{id}")]
public async Task<UserLicense> GetUser(string id, [FromQuery] string key)
{
var user = await _userRepository.GetByIdAsync(new Guid(id));
if (user == null)
public LicensesController(
ILicensingService licensingService,
IUserRepository userRepository,
IUserService userService,
IOrganizationRepository organizationRepository,
IOrganizationService organizationService,
ICurrentContext currentContext)
{
return null;
}
else if (!user.LicenseKey.Equals(key))
{
await Task.Delay(2000);
throw new BadRequestException("Invalid license key.");
_licensingService = licensingService;
_userRepository = userRepository;
_userService = userService;
_organizationRepository = organizationRepository;
_organizationService = organizationService;
_currentContext = currentContext;
}
var license = await _userService.GenerateLicenseAsync(user, null);
return license;
}
[HttpGet("user/{id}")]
public async Task<UserLicense> GetUser(string id, [FromQuery] string key)
{
var user = await _userRepository.GetByIdAsync(new Guid(id));
if (user == null)
{
return null;
}
else if (!user.LicenseKey.Equals(key))
{
await Task.Delay(2000);
throw new BadRequestException("Invalid license key.");
}
[HttpGet("organization/{id}")]
public async Task<OrganizationLicense> GetOrganization(string id, [FromQuery] string key)
{
var org = await _organizationRepository.GetByIdAsync(new Guid(id));
if (org == null)
{
return null;
}
else if (!org.LicenseKey.Equals(key))
{
await Task.Delay(2000);
throw new BadRequestException("Invalid license key.");
var license = await _userService.GenerateLicenseAsync(user, null);
return license;
}
var license = await _organizationService.GenerateLicenseAsync(org, _currentContext.InstallationId.Value);
return license;
[HttpGet("organization/{id}")]
public async Task<OrganizationLicense> GetOrganization(string id, [FromQuery] string key)
{
var org = await _organizationRepository.GetByIdAsync(new Guid(id));
if (org == null)
{
return null;
}
else if (!org.LicenseKey.Equals(key))
{
await Task.Delay(2000);
throw new BadRequestException("Invalid license key.");
}
var license = await _organizationService.GenerateLicenseAsync(org, _currentContext.InstallationId.Value);
return license;
}
}
}

View File

@ -5,41 +5,42 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Stripe;
namespace Bit.Api.Controllers;
public class MiscController : Controller
namespace Bit.Api.Controllers
{
private readonly BitPayClient _bitPayClient;
private readonly GlobalSettings _globalSettings;
public MiscController(
BitPayClient bitPayClient,
GlobalSettings globalSettings)
public class MiscController : Controller
{
_bitPayClient = bitPayClient;
_globalSettings = globalSettings;
}
private readonly BitPayClient _bitPayClient;
private readonly GlobalSettings _globalSettings;
[Authorize("Application")]
[HttpPost("~/bitpay-invoice")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<string> PostBitPayInvoice([FromBody] BitPayInvoiceRequestModel model)
{
var invoice = await _bitPayClient.CreateInvoiceAsync(model.ToBitpayInvoice(_globalSettings));
return invoice.Url;
}
[Authorize("Application")]
[HttpPost("~/setup-payment")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<string> PostSetupPayment()
{
var options = new SetupIntentCreateOptions
public MiscController(
BitPayClient bitPayClient,
GlobalSettings globalSettings)
{
Usage = "off_session"
};
var service = new SetupIntentService();
var setupIntent = await service.CreateAsync(options);
return setupIntent.ClientSecret;
_bitPayClient = bitPayClient;
_globalSettings = globalSettings;
}
[Authorize("Application")]
[HttpPost("~/bitpay-invoice")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<string> PostBitPayInvoice([FromBody] BitPayInvoiceRequestModel model)
{
var invoice = await _bitPayClient.CreateInvoiceAsync(model.ToBitpayInvoice(_globalSettings));
return invoice.Url;
}
[Authorize("Application")]
[HttpPost("~/setup-payment")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<string> PostSetupPayment()
{
var options = new SetupIntentCreateOptions
{
Usage = "off_session"
};
var service = new SetupIntentService();
var setupIntent = await service.CreateAsync(options);
return setupIntent.ClientSecret;
}
}
}

View File

@ -12,198 +12,199 @@ using Bit.Core.Settings;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Authorize("Application")]
[Route("organizations/connections")]
public class OrganizationConnectionsController : Controller
namespace Bit.Api.Controllers
{
private readonly ICreateOrganizationConnectionCommand _createOrganizationConnectionCommand;
private readonly IUpdateOrganizationConnectionCommand _updateOrganizationConnectionCommand;
private readonly IDeleteOrganizationConnectionCommand _deleteOrganizationConnectionCommand;
private readonly IOrganizationConnectionRepository _organizationConnectionRepository;
private readonly ICurrentContext _currentContext;
private readonly IGlobalSettings _globalSettings;
private readonly ILicensingService _licensingService;
public OrganizationConnectionsController(
ICreateOrganizationConnectionCommand createOrganizationConnectionCommand,
IUpdateOrganizationConnectionCommand updateOrganizationConnectionCommand,
IDeleteOrganizationConnectionCommand deleteOrganizationConnectionCommand,
IOrganizationConnectionRepository organizationConnectionRepository,
ICurrentContext currentContext,
IGlobalSettings globalSettings,
ILicensingService licensingService)
[Authorize("Application")]
[Route("organizations/connections")]
public class OrganizationConnectionsController : Controller
{
_createOrganizationConnectionCommand = createOrganizationConnectionCommand;
_updateOrganizationConnectionCommand = updateOrganizationConnectionCommand;
_deleteOrganizationConnectionCommand = deleteOrganizationConnectionCommand;
_organizationConnectionRepository = organizationConnectionRepository;
_currentContext = currentContext;
_globalSettings = globalSettings;
_licensingService = licensingService;
}
private readonly ICreateOrganizationConnectionCommand _createOrganizationConnectionCommand;
private readonly IUpdateOrganizationConnectionCommand _updateOrganizationConnectionCommand;
private readonly IDeleteOrganizationConnectionCommand _deleteOrganizationConnectionCommand;
private readonly IOrganizationConnectionRepository _organizationConnectionRepository;
private readonly ICurrentContext _currentContext;
private readonly IGlobalSettings _globalSettings;
private readonly ILicensingService _licensingService;
[HttpGet("enabled")]
public bool ConnectionsEnabled()
{
return _globalSettings.SelfHosted && _globalSettings.EnableCloudCommunication;
}
[HttpPost]
public async Task<OrganizationConnectionResponseModel> CreateConnection([FromBody] OrganizationConnectionRequestModel model)
{
if (!await HasPermissionAsync(model?.OrganizationId))
public OrganizationConnectionsController(
ICreateOrganizationConnectionCommand createOrganizationConnectionCommand,
IUpdateOrganizationConnectionCommand updateOrganizationConnectionCommand,
IDeleteOrganizationConnectionCommand deleteOrganizationConnectionCommand,
IOrganizationConnectionRepository organizationConnectionRepository,
ICurrentContext currentContext,
IGlobalSettings globalSettings,
ILicensingService licensingService)
{
throw new BadRequestException($"You do not have permission to create a connection of type {model.Type}.");
_createOrganizationConnectionCommand = createOrganizationConnectionCommand;
_updateOrganizationConnectionCommand = updateOrganizationConnectionCommand;
_deleteOrganizationConnectionCommand = deleteOrganizationConnectionCommand;
_organizationConnectionRepository = organizationConnectionRepository;
_currentContext = currentContext;
_globalSettings = globalSettings;
_licensingService = licensingService;
}
if (await HasConnectionTypeAsync(model, null, model.Type))
[HttpGet("enabled")]
public bool ConnectionsEnabled()
{
throw new BadRequestException($"The requested organization already has a connection of type {model.Type}. Only one of each connection type may exist per organization.");
return _globalSettings.SelfHosted && _globalSettings.EnableCloudCommunication;
}
switch (model.Type)
[HttpPost]
public async Task<OrganizationConnectionResponseModel> CreateConnection([FromBody] OrganizationConnectionRequestModel model)
{
case OrganizationConnectionType.CloudBillingSync:
return await CreateOrUpdateOrganizationConnectionAsync<BillingSyncConfig>(null, model, ValidateBillingSyncConfig);
case OrganizationConnectionType.Scim:
return await CreateOrUpdateOrganizationConnectionAsync<ScimConfig>(null, model);
default:
throw new BadRequestException($"Unknown Organization connection Type: {model.Type}");
}
}
if (!await HasPermissionAsync(model?.OrganizationId))
{
throw new BadRequestException($"You do not have permission to create a connection of type {model.Type}.");
}
[HttpPut("{organizationConnectionId}")]
public async Task<OrganizationConnectionResponseModel> UpdateConnection(Guid organizationConnectionId, [FromBody] OrganizationConnectionRequestModel model)
{
var existingOrganizationConnection = await _organizationConnectionRepository.GetByIdAsync(organizationConnectionId);
if (existingOrganizationConnection == null)
{
throw new NotFoundException();
if (await HasConnectionTypeAsync(model, null, model.Type))
{
throw new BadRequestException($"The requested organization already has a connection of type {model.Type}. Only one of each connection type may exist per organization.");
}
switch (model.Type)
{
case OrganizationConnectionType.CloudBillingSync:
return await CreateOrUpdateOrganizationConnectionAsync<BillingSyncConfig>(null, model, ValidateBillingSyncConfig);
case OrganizationConnectionType.Scim:
return await CreateOrUpdateOrganizationConnectionAsync<ScimConfig>(null, model);
default:
throw new BadRequestException($"Unknown Organization connection Type: {model.Type}");
}
}
if (!await HasPermissionAsync(model?.OrganizationId, model?.Type))
[HttpPut("{organizationConnectionId}")]
public async Task<OrganizationConnectionResponseModel> UpdateConnection(Guid organizationConnectionId, [FromBody] OrganizationConnectionRequestModel model)
{
throw new BadRequestException("You do not have permission to update this connection.");
var existingOrganizationConnection = await _organizationConnectionRepository.GetByIdAsync(organizationConnectionId);
if (existingOrganizationConnection == null)
{
throw new NotFoundException();
}
if (!await HasPermissionAsync(model?.OrganizationId, model?.Type))
{
throw new BadRequestException("You do not have permission to update this connection.");
}
if (await HasConnectionTypeAsync(model, organizationConnectionId, model.Type))
{
throw new BadRequestException($"The requested organization already has a connection of type {model.Type}. Only one of each connection type may exist per organization.");
}
switch (model.Type)
{
case OrganizationConnectionType.CloudBillingSync:
return await CreateOrUpdateOrganizationConnectionAsync<BillingSyncConfig>(organizationConnectionId, model);
case OrganizationConnectionType.Scim:
return await CreateOrUpdateOrganizationConnectionAsync<ScimConfig>(organizationConnectionId, model);
default:
throw new BadRequestException($"Unkown Organization connection Type: {model.Type}");
}
}
if (await HasConnectionTypeAsync(model, organizationConnectionId, model.Type))
[HttpGet("{organizationId}/{type}")]
public async Task<OrganizationConnectionResponseModel> GetConnection(Guid organizationId, OrganizationConnectionType type)
{
throw new BadRequestException($"The requested organization already has a connection of type {model.Type}. Only one of each connection type may exist per organization.");
if (!await HasPermissionAsync(organizationId, type))
{
throw new BadRequestException($"You do not have permission to retrieve a connection of type {type}.");
}
var connections = await GetConnectionsAsync(organizationId, type);
var connection = connections.FirstOrDefault(c => c.Type == type);
switch (type)
{
case OrganizationConnectionType.CloudBillingSync:
if (!_globalSettings.SelfHosted)
{
throw new BadRequestException($"Cannot get a {type} connection outside of a self-hosted instance.");
}
return new OrganizationConnectionResponseModel(connection, typeof(BillingSyncConfig));
case OrganizationConnectionType.Scim:
return new OrganizationConnectionResponseModel(connection, typeof(ScimConfig));
default:
throw new BadRequestException($"Unkown Organization connection Type: {type}");
}
}
switch (model.Type)
[HttpDelete("{organizationConnectionId}")]
[HttpPost("{organizationConnectionId}/delete")]
public async Task DeleteConnection(Guid organizationConnectionId)
{
case OrganizationConnectionType.CloudBillingSync:
return await CreateOrUpdateOrganizationConnectionAsync<BillingSyncConfig>(organizationConnectionId, model);
case OrganizationConnectionType.Scim:
return await CreateOrUpdateOrganizationConnectionAsync<ScimConfig>(organizationConnectionId, model);
default:
throw new BadRequestException($"Unkown Organization connection Type: {model.Type}");
}
}
var connection = await _organizationConnectionRepository.GetByIdAsync(organizationConnectionId);
[HttpGet("{organizationId}/{type}")]
public async Task<OrganizationConnectionResponseModel> GetConnection(Guid organizationId, OrganizationConnectionType type)
{
if (!await HasPermissionAsync(organizationId, type))
{
throw new BadRequestException($"You do not have permission to retrieve a connection of type {type}.");
if (connection == null)
{
throw new NotFoundException();
}
if (!await HasPermissionAsync(connection.OrganizationId, connection.Type))
{
throw new BadRequestException($"You do not have permission to remove this connection of type {connection.Type}.");
}
await _deleteOrganizationConnectionCommand.DeleteAsync(connection);
}
var connections = await GetConnectionsAsync(organizationId, type);
var connection = connections.FirstOrDefault(c => c.Type == type);
private async Task<ICollection<OrganizationConnection>> GetConnectionsAsync(Guid organizationId, OrganizationConnectionType type) =>
await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(organizationId, type);
switch (type)
private async Task<bool> HasConnectionTypeAsync(OrganizationConnectionRequestModel model, Guid? connectionId,
OrganizationConnectionType type)
{
case OrganizationConnectionType.CloudBillingSync:
if (!_globalSettings.SelfHosted)
{
throw new BadRequestException($"Cannot get a {type} connection outside of a self-hosted instance.");
}
return new OrganizationConnectionResponseModel(connection, typeof(BillingSyncConfig));
case OrganizationConnectionType.Scim:
return new OrganizationConnectionResponseModel(connection, typeof(ScimConfig));
default:
throw new BadRequestException($"Unkown Organization connection Type: {type}");
}
}
var existingConnections = await GetConnectionsAsync(model.OrganizationId, type);
[HttpDelete("{organizationConnectionId}")]
[HttpPost("{organizationConnectionId}/delete")]
public async Task DeleteConnection(Guid organizationConnectionId)
{
var connection = await _organizationConnectionRepository.GetByIdAsync(organizationConnectionId);
if (connection == null)
{
throw new NotFoundException();
return existingConnections.Any(c => c.Type == model.Type && (!connectionId.HasValue || c.Id != connectionId.Value));
}
if (!await HasPermissionAsync(connection.OrganizationId, connection.Type))
private async Task<bool> HasPermissionAsync(Guid? organizationId, OrganizationConnectionType? type = null)
{
throw new BadRequestException($"You do not have permission to remove this connection of type {connection.Type}.");
if (!organizationId.HasValue)
{
return false;
}
return type switch
{
OrganizationConnectionType.Scim => await _currentContext.ManageScim(organizationId.Value),
_ => await _currentContext.OrganizationOwner(organizationId.Value),
};
}
await _deleteOrganizationConnectionCommand.DeleteAsync(connection);
}
private async Task<ICollection<OrganizationConnection>> GetConnectionsAsync(Guid organizationId, OrganizationConnectionType type) =>
await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(organizationId, type);
private async Task<bool> HasConnectionTypeAsync(OrganizationConnectionRequestModel model, Guid? connectionId,
OrganizationConnectionType type)
{
var existingConnections = await GetConnectionsAsync(model.OrganizationId, type);
return existingConnections.Any(c => c.Type == model.Type && (!connectionId.HasValue || c.Id != connectionId.Value));
}
private async Task<bool> HasPermissionAsync(Guid? organizationId, OrganizationConnectionType? type = null)
{
if (!organizationId.HasValue)
private async Task ValidateBillingSyncConfig(OrganizationConnectionRequestModel<BillingSyncConfig> typedModel)
{
return false;
}
return type switch
{
OrganizationConnectionType.Scim => await _currentContext.ManageScim(organizationId.Value),
_ => await _currentContext.OrganizationOwner(organizationId.Value),
};
}
private async Task ValidateBillingSyncConfig(OrganizationConnectionRequestModel<BillingSyncConfig> typedModel)
{
if (!_globalSettings.SelfHosted)
{
throw new BadRequestException($"Cannot create a {typedModel.Type} connection outside of a self-hosted instance.");
}
var license = await _licensingService.ReadOrganizationLicenseAsync(typedModel.OrganizationId);
if (!_licensingService.VerifyLicense(license))
{
throw new BadRequestException("Cannot verify license file.");
}
typedModel.ParsedConfig.CloudOrganizationId = license.Id;
}
private async Task<OrganizationConnectionResponseModel> CreateOrUpdateOrganizationConnectionAsync<T>(
Guid? organizationConnectionId,
OrganizationConnectionRequestModel model,
Func<OrganizationConnectionRequestModel<T>, Task> validateAction = null)
where T : new()
{
var typedModel = new OrganizationConnectionRequestModel<T>(model);
if (validateAction != null)
{
await validateAction(typedModel);
if (!_globalSettings.SelfHosted)
{
throw new BadRequestException($"Cannot create a {typedModel.Type} connection outside of a self-hosted instance.");
}
var license = await _licensingService.ReadOrganizationLicenseAsync(typedModel.OrganizationId);
if (!_licensingService.VerifyLicense(license))
{
throw new BadRequestException("Cannot verify license file.");
}
typedModel.ParsedConfig.CloudOrganizationId = license.Id;
}
var data = typedModel.ToData(organizationConnectionId);
var connection = organizationConnectionId.HasValue
? await _updateOrganizationConnectionCommand.UpdateAsync(data)
: await _createOrganizationConnectionCommand.CreateAsync(data);
private async Task<OrganizationConnectionResponseModel> CreateOrUpdateOrganizationConnectionAsync<T>(
Guid? organizationConnectionId,
OrganizationConnectionRequestModel model,
Func<OrganizationConnectionRequestModel<T>, Task> validateAction = null)
where T : new()
{
var typedModel = new OrganizationConnectionRequestModel<T>(model);
if (validateAction != null)
{
await validateAction(typedModel);
}
return new OrganizationConnectionResponseModel(connection, typeof(T));
var data = typedModel.ToData(organizationConnectionId);
var connection = organizationConnectionId.HasValue
? await _updateOrganizationConnectionCommand.UpdateAsync(data)
: await _createOrganizationConnectionCommand.CreateAsync(data);
return new OrganizationConnectionResponseModel(connection, typeof(T));
}
}
}

View File

@ -6,58 +6,59 @@ using Core.Models.Data;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Route("organizations/{organizationId}")]
[Authorize("Application")]
public class OrganizationExportController : Controller
namespace Bit.Api.Controllers
{
private readonly IUserService _userService;
private readonly ICollectionService _collectionService;
private readonly ICipherService _cipherService;
private readonly GlobalSettings _globalSettings;
public OrganizationExportController(
ICipherService cipherService,
ICollectionService collectionService,
IUserService userService,
GlobalSettings globalSettings)
[Route("organizations/{organizationId}")]
[Authorize("Application")]
public class OrganizationExportController : Controller
{
_cipherService = cipherService;
_collectionService = collectionService;
_userService = userService;
_globalSettings = globalSettings;
}
private readonly IUserService _userService;
private readonly ICollectionService _collectionService;
private readonly ICipherService _cipherService;
private readonly GlobalSettings _globalSettings;
[HttpGet("export")]
public async Task<OrganizationExportResponseModel> Export(Guid organizationId)
{
var userId = _userService.GetProperUserId(User).Value;
IEnumerable<Collection> orgCollections = await _collectionService.GetOrganizationCollections(organizationId);
(IEnumerable<CipherOrganizationDetails> orgCiphers, Dictionary<Guid, IGrouping<Guid, CollectionCipher>> collectionCiphersGroupDict) = await _cipherService.GetOrganizationCiphers(userId, organizationId);
var result = new OrganizationExportResponseModel
public OrganizationExportController(
ICipherService cipherService,
ICollectionService collectionService,
IUserService userService,
GlobalSettings globalSettings)
{
Collections = GetOrganizationCollectionsResponse(orgCollections),
Ciphers = GetOrganizationCiphersResponse(orgCiphers, collectionCiphersGroupDict)
};
_cipherService = cipherService;
_collectionService = collectionService;
_userService = userService;
_globalSettings = globalSettings;
}
return result;
}
[HttpGet("export")]
public async Task<OrganizationExportResponseModel> Export(Guid organizationId)
{
var userId = _userService.GetProperUserId(User).Value;
private ListResponseModel<CollectionResponseModel> GetOrganizationCollectionsResponse(IEnumerable<Collection> orgCollections)
{
var collections = orgCollections.Select(c => new CollectionResponseModel(c));
return new ListResponseModel<CollectionResponseModel>(collections);
}
IEnumerable<Collection> orgCollections = await _collectionService.GetOrganizationCollections(organizationId);
(IEnumerable<CipherOrganizationDetails> orgCiphers, Dictionary<Guid, IGrouping<Guid, CollectionCipher>> collectionCiphersGroupDict) = await _cipherService.GetOrganizationCiphers(userId, organizationId);
private ListResponseModel<CipherMiniDetailsResponseModel> GetOrganizationCiphersResponse(IEnumerable<CipherOrganizationDetails> orgCiphers,
Dictionary<Guid, IGrouping<Guid, CollectionCipher>> collectionCiphersGroupDict)
{
var responses = orgCiphers.Select(c => new CipherMiniDetailsResponseModel(c, _globalSettings,
collectionCiphersGroupDict, c.OrganizationUseTotp));
var result = new OrganizationExportResponseModel
{
Collections = GetOrganizationCollectionsResponse(orgCollections),
Ciphers = GetOrganizationCiphersResponse(orgCiphers, collectionCiphersGroupDict)
};
return new ListResponseModel<CipherMiniDetailsResponseModel>(responses);
return result;
}
private ListResponseModel<CollectionResponseModel> GetOrganizationCollectionsResponse(IEnumerable<Collection> orgCollections)
{
var collections = orgCollections.Select(c => new CollectionResponseModel(c));
return new ListResponseModel<CollectionResponseModel>(collections);
}
private ListResponseModel<CipherMiniDetailsResponseModel> GetOrganizationCiphersResponse(IEnumerable<CipherOrganizationDetails> orgCiphers,
Dictionary<Guid, IGrouping<Guid, CollectionCipher>> collectionCiphersGroupDict)
{
var responses = orgCiphers.Select(c => new CipherMiniDetailsResponseModel(c, _globalSettings,
collectionCiphersGroupDict, c.OrganizationUseTotp));
return new ListResponseModel<CipherMiniDetailsResponseModel>(responses);
}
}
}

View File

@ -12,179 +12,180 @@ using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Route("organization/sponsorship")]
public class OrganizationSponsorshipsController : Controller
namespace Bit.Api.Controllers
{
private readonly IOrganizationSponsorshipRepository _organizationSponsorshipRepository;
private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IValidateRedemptionTokenCommand _validateRedemptionTokenCommand;
private readonly IValidateBillingSyncKeyCommand _validateBillingSyncKeyCommand;
private readonly ICreateSponsorshipCommand _createSponsorshipCommand;
private readonly ISendSponsorshipOfferCommand _sendSponsorshipOfferCommand;
private readonly ISetUpSponsorshipCommand _setUpSponsorshipCommand;
private readonly IRevokeSponsorshipCommand _revokeSponsorshipCommand;
private readonly IRemoveSponsorshipCommand _removeSponsorshipCommand;
private readonly ICloudSyncSponsorshipsCommand _syncSponsorshipsCommand;
private readonly ICurrentContext _currentContext;
private readonly IUserService _userService;
public OrganizationSponsorshipsController(
IOrganizationSponsorshipRepository organizationSponsorshipRepository,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
IValidateRedemptionTokenCommand validateRedemptionTokenCommand,
IValidateBillingSyncKeyCommand validateBillingSyncKeyCommand,
ICreateSponsorshipCommand createSponsorshipCommand,
ISendSponsorshipOfferCommand sendSponsorshipOfferCommand,
ISetUpSponsorshipCommand setUpSponsorshipCommand,
IRevokeSponsorshipCommand revokeSponsorshipCommand,
IRemoveSponsorshipCommand removeSponsorshipCommand,
ICloudSyncSponsorshipsCommand syncSponsorshipsCommand,
IUserService userService,
ICurrentContext currentContext)
[Route("organization/sponsorship")]
public class OrganizationSponsorshipsController : Controller
{
_organizationSponsorshipRepository = organizationSponsorshipRepository;
_organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository;
_validateRedemptionTokenCommand = validateRedemptionTokenCommand;
_validateBillingSyncKeyCommand = validateBillingSyncKeyCommand;
_createSponsorshipCommand = createSponsorshipCommand;
_sendSponsorshipOfferCommand = sendSponsorshipOfferCommand;
_setUpSponsorshipCommand = setUpSponsorshipCommand;
_revokeSponsorshipCommand = revokeSponsorshipCommand;
_removeSponsorshipCommand = removeSponsorshipCommand;
_syncSponsorshipsCommand = syncSponsorshipsCommand;
_userService = userService;
_currentContext = currentContext;
}
private readonly IOrganizationSponsorshipRepository _organizationSponsorshipRepository;
private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IValidateRedemptionTokenCommand _validateRedemptionTokenCommand;
private readonly IValidateBillingSyncKeyCommand _validateBillingSyncKeyCommand;
private readonly ICreateSponsorshipCommand _createSponsorshipCommand;
private readonly ISendSponsorshipOfferCommand _sendSponsorshipOfferCommand;
private readonly ISetUpSponsorshipCommand _setUpSponsorshipCommand;
private readonly IRevokeSponsorshipCommand _revokeSponsorshipCommand;
private readonly IRemoveSponsorshipCommand _removeSponsorshipCommand;
private readonly ICloudSyncSponsorshipsCommand _syncSponsorshipsCommand;
private readonly ICurrentContext _currentContext;
private readonly IUserService _userService;
[Authorize("Application")]
[HttpPost("{sponsoringOrgId}/families-for-enterprise")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task CreateSponsorship(Guid sponsoringOrgId, [FromBody] OrganizationSponsorshipCreateRequestModel model)
{
var sponsoringOrg = await _organizationRepository.GetByIdAsync(sponsoringOrgId);
var sponsorship = await _createSponsorshipCommand.CreateSponsorshipAsync(
sponsoringOrg,
await _organizationUserRepository.GetByOrganizationAsync(sponsoringOrgId, _currentContext.UserId ?? default),
model.PlanSponsorshipType, model.SponsoredEmail, model.FriendlyName);
await _sendSponsorshipOfferCommand.SendSponsorshipOfferAsync(sponsorship, sponsoringOrg.Name);
}
[Authorize("Application")]
[HttpPost("{sponsoringOrgId}/families-for-enterprise/resend")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task ResendSponsorshipOffer(Guid sponsoringOrgId)
{
var sponsoringOrgUser = await _organizationUserRepository
.GetByOrganizationAsync(sponsoringOrgId, _currentContext.UserId ?? default);
await _sendSponsorshipOfferCommand.SendSponsorshipOfferAsync(
await _organizationRepository.GetByIdAsync(sponsoringOrgId),
sponsoringOrgUser,
await _organizationSponsorshipRepository
.GetBySponsoringOrganizationUserIdAsync(sponsoringOrgUser.Id));
}
[Authorize("Application")]
[HttpPost("validate-token")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<bool> PreValidateSponsorshipToken([FromQuery] string sponsorshipToken)
{
return (await _validateRedemptionTokenCommand.ValidateRedemptionTokenAsync(sponsorshipToken, (await CurrentUser).Email)).valid;
}
[Authorize("Application")]
[HttpPost("redeem")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task RedeemSponsorship([FromQuery] string sponsorshipToken, [FromBody] OrganizationSponsorshipRedeemRequestModel model)
{
var (valid, sponsorship) = await _validateRedemptionTokenCommand.ValidateRedemptionTokenAsync(sponsorshipToken, (await CurrentUser).Email);
if (!valid)
public OrganizationSponsorshipsController(
IOrganizationSponsorshipRepository organizationSponsorshipRepository,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
IValidateRedemptionTokenCommand validateRedemptionTokenCommand,
IValidateBillingSyncKeyCommand validateBillingSyncKeyCommand,
ICreateSponsorshipCommand createSponsorshipCommand,
ISendSponsorshipOfferCommand sendSponsorshipOfferCommand,
ISetUpSponsorshipCommand setUpSponsorshipCommand,
IRevokeSponsorshipCommand revokeSponsorshipCommand,
IRemoveSponsorshipCommand removeSponsorshipCommand,
ICloudSyncSponsorshipsCommand syncSponsorshipsCommand,
IUserService userService,
ICurrentContext currentContext)
{
throw new BadRequestException("Failed to parse sponsorship token.");
_organizationSponsorshipRepository = organizationSponsorshipRepository;
_organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository;
_validateRedemptionTokenCommand = validateRedemptionTokenCommand;
_validateBillingSyncKeyCommand = validateBillingSyncKeyCommand;
_createSponsorshipCommand = createSponsorshipCommand;
_sendSponsorshipOfferCommand = sendSponsorshipOfferCommand;
_setUpSponsorshipCommand = setUpSponsorshipCommand;
_revokeSponsorshipCommand = revokeSponsorshipCommand;
_removeSponsorshipCommand = removeSponsorshipCommand;
_syncSponsorshipsCommand = syncSponsorshipsCommand;
_userService = userService;
_currentContext = currentContext;
}
if (!await _currentContext.OrganizationOwner(model.SponsoredOrganizationId))
[Authorize("Application")]
[HttpPost("{sponsoringOrgId}/families-for-enterprise")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task CreateSponsorship(Guid sponsoringOrgId, [FromBody] OrganizationSponsorshipCreateRequestModel model)
{
throw new BadRequestException("Can only redeem sponsorship for an organization you own.");
var sponsoringOrg = await _organizationRepository.GetByIdAsync(sponsoringOrgId);
var sponsorship = await _createSponsorshipCommand.CreateSponsorshipAsync(
sponsoringOrg,
await _organizationUserRepository.GetByOrganizationAsync(sponsoringOrgId, _currentContext.UserId ?? default),
model.PlanSponsorshipType, model.SponsoredEmail, model.FriendlyName);
await _sendSponsorshipOfferCommand.SendSponsorshipOfferAsync(sponsorship, sponsoringOrg.Name);
}
await _setUpSponsorshipCommand.SetUpSponsorshipAsync(
sponsorship,
await _organizationRepository.GetByIdAsync(model.SponsoredOrganizationId));
}
[Authorize("Installation")]
[HttpPost("sync")]
public async Task<OrganizationSponsorshipSyncResponseModel> Sync([FromBody] OrganizationSponsorshipSyncRequestModel model)
{
var sponsoringOrg = await _organizationRepository.GetByIdAsync(model.SponsoringOrganizationCloudId);
if (!await _validateBillingSyncKeyCommand.ValidateBillingSyncKeyAsync(sponsoringOrg, model.BillingSyncKey))
[Authorize("Application")]
[HttpPost("{sponsoringOrgId}/families-for-enterprise/resend")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task ResendSponsorshipOffer(Guid sponsoringOrgId)
{
throw new BadRequestException("Invalid Billing Sync Key");
var sponsoringOrgUser = await _organizationUserRepository
.GetByOrganizationAsync(sponsoringOrgId, _currentContext.UserId ?? default);
await _sendSponsorshipOfferCommand.SendSponsorshipOfferAsync(
await _organizationRepository.GetByIdAsync(sponsoringOrgId),
sponsoringOrgUser,
await _organizationSponsorshipRepository
.GetBySponsoringOrganizationUserIdAsync(sponsoringOrgUser.Id));
}
var (syncResponseData, offersToSend) = await _syncSponsorshipsCommand.SyncOrganization(sponsoringOrg, model.ToOrganizationSponsorshipSync().SponsorshipsBatch);
await _sendSponsorshipOfferCommand.BulkSendSponsorshipOfferAsync(sponsoringOrg.Name, offersToSend);
return new OrganizationSponsorshipSyncResponseModel(syncResponseData);
}
[Authorize("Application")]
[HttpDelete("{sponsoringOrganizationId}")]
[HttpPost("{sponsoringOrganizationId}/delete")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task RevokeSponsorship(Guid sponsoringOrganizationId)
{
var orgUser = await _organizationUserRepository.GetByOrganizationAsync(sponsoringOrganizationId, _currentContext.UserId ?? default);
if (_currentContext.UserId != orgUser?.UserId)
[Authorize("Application")]
[HttpPost("validate-token")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<bool> PreValidateSponsorshipToken([FromQuery] string sponsorshipToken)
{
throw new BadRequestException("Can only revoke a sponsorship you granted.");
return (await _validateRedemptionTokenCommand.ValidateRedemptionTokenAsync(sponsorshipToken, (await CurrentUser).Email)).valid;
}
var existingOrgSponsorship = await _organizationSponsorshipRepository
.GetBySponsoringOrganizationUserIdAsync(orgUser.Id);
await _revokeSponsorshipCommand.RevokeSponsorshipAsync(existingOrgSponsorship);
}
[Authorize("Application")]
[HttpDelete("sponsored/{sponsoredOrgId}")]
[HttpPost("sponsored/{sponsoredOrgId}/remove")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task RemoveSponsorship(Guid sponsoredOrgId)
{
if (!await _currentContext.OrganizationOwner(sponsoredOrgId))
[Authorize("Application")]
[HttpPost("redeem")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task RedeemSponsorship([FromQuery] string sponsorshipToken, [FromBody] OrganizationSponsorshipRedeemRequestModel model)
{
throw new BadRequestException("Only the owner of an organization can remove sponsorship.");
var (valid, sponsorship) = await _validateRedemptionTokenCommand.ValidateRedemptionTokenAsync(sponsorshipToken, (await CurrentUser).Email);
if (!valid)
{
throw new BadRequestException("Failed to parse sponsorship token.");
}
if (!await _currentContext.OrganizationOwner(model.SponsoredOrganizationId))
{
throw new BadRequestException("Can only redeem sponsorship for an organization you own.");
}
await _setUpSponsorshipCommand.SetUpSponsorshipAsync(
sponsorship,
await _organizationRepository.GetByIdAsync(model.SponsoredOrganizationId));
}
var existingOrgSponsorship = await _organizationSponsorshipRepository
.GetBySponsoredOrganizationIdAsync(sponsoredOrgId);
await _removeSponsorshipCommand.RemoveSponsorshipAsync(existingOrgSponsorship);
}
[HttpGet("{sponsoringOrgId}/sync-status")]
public async Task<object> GetSyncStatus(Guid sponsoringOrgId)
{
var sponsoringOrg = await _organizationRepository.GetByIdAsync(sponsoringOrgId);
if (!await _currentContext.OrganizationOwner(sponsoringOrg.Id))
[Authorize("Installation")]
[HttpPost("sync")]
public async Task<OrganizationSponsorshipSyncResponseModel> Sync([FromBody] OrganizationSponsorshipSyncRequestModel model)
{
throw new NotFoundException();
var sponsoringOrg = await _organizationRepository.GetByIdAsync(model.SponsoringOrganizationCloudId);
if (!await _validateBillingSyncKeyCommand.ValidateBillingSyncKeyAsync(sponsoringOrg, model.BillingSyncKey))
{
throw new BadRequestException("Invalid Billing Sync Key");
}
var (syncResponseData, offersToSend) = await _syncSponsorshipsCommand.SyncOrganization(sponsoringOrg, model.ToOrganizationSponsorshipSync().SponsorshipsBatch);
await _sendSponsorshipOfferCommand.BulkSendSponsorshipOfferAsync(sponsoringOrg.Name, offersToSend);
return new OrganizationSponsorshipSyncResponseModel(syncResponseData);
}
var lastSyncDate = await _organizationSponsorshipRepository.GetLatestSyncDateBySponsoringOrganizationIdAsync(sponsoringOrg.Id);
return new OrganizationSponsorshipSyncStatusResponseModel(lastSyncDate);
}
[Authorize("Application")]
[HttpDelete("{sponsoringOrganizationId}")]
[HttpPost("{sponsoringOrganizationId}/delete")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task RevokeSponsorship(Guid sponsoringOrganizationId)
{
private Task<User> CurrentUser => _userService.GetUserByIdAsync(_currentContext.UserId.Value);
var orgUser = await _organizationUserRepository.GetByOrganizationAsync(sponsoringOrganizationId, _currentContext.UserId ?? default);
if (_currentContext.UserId != orgUser?.UserId)
{
throw new BadRequestException("Can only revoke a sponsorship you granted.");
}
var existingOrgSponsorship = await _organizationSponsorshipRepository
.GetBySponsoringOrganizationUserIdAsync(orgUser.Id);
await _revokeSponsorshipCommand.RevokeSponsorshipAsync(existingOrgSponsorship);
}
[Authorize("Application")]
[HttpDelete("sponsored/{sponsoredOrgId}")]
[HttpPost("sponsored/{sponsoredOrgId}/remove")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task RemoveSponsorship(Guid sponsoredOrgId)
{
if (!await _currentContext.OrganizationOwner(sponsoredOrgId))
{
throw new BadRequestException("Only the owner of an organization can remove sponsorship.");
}
var existingOrgSponsorship = await _organizationSponsorshipRepository
.GetBySponsoredOrganizationIdAsync(sponsoredOrgId);
await _removeSponsorshipCommand.RemoveSponsorshipAsync(existingOrgSponsorship);
}
[HttpGet("{sponsoringOrgId}/sync-status")]
public async Task<object> GetSyncStatus(Guid sponsoringOrgId)
{
var sponsoringOrg = await _organizationRepository.GetByIdAsync(sponsoringOrgId);
if (!await _currentContext.OrganizationOwner(sponsoringOrg.Id))
{
throw new NotFoundException();
}
var lastSyncDate = await _organizationSponsorshipRepository.GetLatestSyncDateBySponsoringOrganizationIdAsync(sponsoringOrg.Id);
return new OrganizationSponsorshipSyncStatusResponseModel(lastSyncDate);
}
private Task<User> CurrentUser => _userService.GetUserByIdAsync(_currentContext.UserId.Value);
}
}

View File

@ -13,459 +13,460 @@ using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Route("organizations/{orgId}/users")]
[Authorize("Application")]
public class OrganizationUsersController : Controller
namespace Bit.Api.Controllers
{
private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IOrganizationService _organizationService;
private readonly ICollectionRepository _collectionRepository;
private readonly IGroupRepository _groupRepository;
private readonly IUserService _userService;
private readonly IPolicyRepository _policyRepository;
private readonly ICurrentContext _currentContext;
public OrganizationUsersController(
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
IOrganizationService organizationService,
ICollectionRepository collectionRepository,
IGroupRepository groupRepository,
IUserService userService,
IPolicyRepository policyRepository,
ICurrentContext currentContext)
[Route("organizations/{orgId}/users")]
[Authorize("Application")]
public class OrganizationUsersController : Controller
{
_organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository;
_organizationService = organizationService;
_collectionRepository = collectionRepository;
_groupRepository = groupRepository;
_userService = userService;
_policyRepository = policyRepository;
_currentContext = currentContext;
}
private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IOrganizationService _organizationService;
private readonly ICollectionRepository _collectionRepository;
private readonly IGroupRepository _groupRepository;
private readonly IUserService _userService;
private readonly IPolicyRepository _policyRepository;
private readonly ICurrentContext _currentContext;
[HttpGet("{id}")]
public async Task<OrganizationUserDetailsResponseModel> Get(string orgId, string id)
{
var organizationUser = await _organizationUserRepository.GetByIdWithCollectionsAsync(new Guid(id));
if (organizationUser == null || !await _currentContext.ManageUsers(organizationUser.Item1.OrganizationId))
public OrganizationUsersController(
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
IOrganizationService organizationService,
ICollectionRepository collectionRepository,
IGroupRepository groupRepository,
IUserService userService,
IPolicyRepository policyRepository,
ICurrentContext currentContext)
{
throw new NotFoundException();
_organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository;
_organizationService = organizationService;
_collectionRepository = collectionRepository;
_groupRepository = groupRepository;
_userService = userService;
_policyRepository = policyRepository;
_currentContext = currentContext;
}
return new OrganizationUserDetailsResponseModel(organizationUser.Item1, organizationUser.Item2);
}
[HttpGet("")]
public async Task<ListResponseModel<OrganizationUserUserDetailsResponseModel>> Get(string orgId)
{
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ViewAllCollections(orgGuidId) &&
!await _currentContext.ViewAssignedCollections(orgGuidId) &&
!await _currentContext.ManageGroups(orgGuidId) &&
!await _currentContext.ManageUsers(orgGuidId))
[HttpGet("{id}")]
public async Task<OrganizationUserDetailsResponseModel> Get(string orgId, string id)
{
throw new NotFoundException();
var organizationUser = await _organizationUserRepository.GetByIdWithCollectionsAsync(new Guid(id));
if (organizationUser == null || !await _currentContext.ManageUsers(organizationUser.Item1.OrganizationId))
{
throw new NotFoundException();
}
return new OrganizationUserDetailsResponseModel(organizationUser.Item1, organizationUser.Item2);
}
var organizationUsers = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(orgGuidId);
var responseTasks = organizationUsers.Select(async o => new OrganizationUserUserDetailsResponseModel(o,
await _userService.TwoFactorIsEnabledAsync(o)));
var responses = await Task.WhenAll(responseTasks);
return new ListResponseModel<OrganizationUserUserDetailsResponseModel>(responses);
}
[HttpGet("{id}/groups")]
public async Task<IEnumerable<string>> GetGroups(string orgId, string id)
{
var organizationUser = await _organizationUserRepository.GetByIdAsync(new Guid(id));
if (organizationUser == null || (!await _currentContext.ManageGroups(organizationUser.OrganizationId) &&
!await _currentContext.ManageUsers(organizationUser.OrganizationId)))
[HttpGet("")]
public async Task<ListResponseModel<OrganizationUserUserDetailsResponseModel>> Get(string orgId)
{
throw new NotFoundException();
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ViewAllCollections(orgGuidId) &&
!await _currentContext.ViewAssignedCollections(orgGuidId) &&
!await _currentContext.ManageGroups(orgGuidId) &&
!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
var organizationUsers = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(orgGuidId);
var responseTasks = organizationUsers.Select(async o => new OrganizationUserUserDetailsResponseModel(o,
await _userService.TwoFactorIsEnabledAsync(o)));
var responses = await Task.WhenAll(responseTasks);
return new ListResponseModel<OrganizationUserUserDetailsResponseModel>(responses);
}
var groupIds = await _groupRepository.GetManyIdsByUserIdAsync(organizationUser.Id);
var responses = groupIds.Select(g => g.ToString());
return responses;
}
[HttpGet("{id}/reset-password-details")]
public async Task<OrganizationUserResetPasswordDetailsResponseModel> GetResetPasswordDetails(string orgId, string id)
{
// Make sure the calling user can reset passwords for this org
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageResetPassword(orgGuidId))
[HttpGet("{id}/groups")]
public async Task<IEnumerable<string>> GetGroups(string orgId, string id)
{
throw new NotFoundException();
var organizationUser = await _organizationUserRepository.GetByIdAsync(new Guid(id));
if (organizationUser == null || (!await _currentContext.ManageGroups(organizationUser.OrganizationId) &&
!await _currentContext.ManageUsers(organizationUser.OrganizationId)))
{
throw new NotFoundException();
}
var groupIds = await _groupRepository.GetManyIdsByUserIdAsync(organizationUser.Id);
var responses = groupIds.Select(g => g.ToString());
return responses;
}
var organizationUser = await _organizationUserRepository.GetByIdAsync(new Guid(id));
if (organizationUser == null || !organizationUser.UserId.HasValue)
[HttpGet("{id}/reset-password-details")]
public async Task<OrganizationUserResetPasswordDetailsResponseModel> GetResetPasswordDetails(string orgId, string id)
{
throw new NotFoundException();
// Make sure the calling user can reset passwords for this org
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageResetPassword(orgGuidId))
{
throw new NotFoundException();
}
var organizationUser = await _organizationUserRepository.GetByIdAsync(new Guid(id));
if (organizationUser == null || !organizationUser.UserId.HasValue)
{
throw new NotFoundException();
}
// Retrieve data necessary for response (KDF, KDF Iterations, ResetPasswordKey)
// TODO Reset Password - Revisit this and create SPROC to reduce DB calls
var user = await _userService.GetUserByIdAsync(organizationUser.UserId.Value);
if (user == null)
{
throw new NotFoundException();
}
// Retrieve Encrypted Private Key from organization
var org = await _organizationRepository.GetByIdAsync(orgGuidId);
if (org == null)
{
throw new NotFoundException();
}
return new OrganizationUserResetPasswordDetailsResponseModel(new OrganizationUserResetPasswordDetails(organizationUser, user, org));
}
// Retrieve data necessary for response (KDF, KDF Iterations, ResetPasswordKey)
// TODO Reset Password - Revisit this and create SPROC to reduce DB calls
var user = await _userService.GetUserByIdAsync(organizationUser.UserId.Value);
if (user == null)
[HttpPost("invite")]
public async Task Invite(string orgId, [FromBody] OrganizationUserInviteRequestModel model)
{
throw new NotFoundException();
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
var result = await _organizationService.InviteUsersAsync(orgGuidId, userId.Value,
new (OrganizationUserInvite, string)[] { (new OrganizationUserInvite(model.ToData()), null) });
}
// Retrieve Encrypted Private Key from organization
var org = await _organizationRepository.GetByIdAsync(orgGuidId);
if (org == null)
[HttpPost("reinvite")]
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkReinvite(string orgId, [FromBody] OrganizationUserBulkRequestModel model)
{
throw new NotFoundException();
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
var result = await _organizationService.ResendInvitesAsync(orgGuidId, userId.Value, model.Ids);
return new ListResponseModel<OrganizationUserBulkResponseModel>(
result.Select(t => new OrganizationUserBulkResponseModel(t.Item1.Id, t.Item2)));
}
return new OrganizationUserResetPasswordDetailsResponseModel(new OrganizationUserResetPasswordDetails(organizationUser, user, org));
}
[HttpPost("invite")]
public async Task Invite(string orgId, [FromBody] OrganizationUserInviteRequestModel model)
{
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageUsers(orgGuidId))
[HttpPost("{id}/reinvite")]
public async Task Reinvite(string orgId, string id)
{
throw new NotFoundException();
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
await _organizationService.ResendInviteAsync(orgGuidId, userId.Value, new Guid(id));
}
var userId = _userService.GetProperUserId(User);
var result = await _organizationService.InviteUsersAsync(orgGuidId, userId.Value,
new (OrganizationUserInvite, string)[] { (new OrganizationUserInvite(model.ToData()), null) });
}
[HttpPost("reinvite")]
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkReinvite(string orgId, [FromBody] OrganizationUserBulkRequestModel model)
{
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageUsers(orgGuidId))
[HttpPost("{organizationUserId}/accept")]
public async Task Accept(Guid orgId, Guid organizationUserId, [FromBody] OrganizationUserAcceptRequestModel model)
{
throw new NotFoundException();
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
var masterPasswordPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(orgId, PolicyType.ResetPassword);
var useMasterPasswordPolicy = masterPasswordPolicy != null &&
masterPasswordPolicy.Enabled &&
masterPasswordPolicy.GetDataModel<ResetPasswordDataModel>().AutoEnrollEnabled;
if (useMasterPasswordPolicy &&
string.IsNullOrWhiteSpace(model.ResetPasswordKey))
{
throw new BadRequestException(string.Empty, "Master Password reset is required, but not provided.");
}
await _organizationService.AcceptUserAsync(organizationUserId, user, model.Token, _userService);
if (useMasterPasswordPolicy)
{
await _organizationService.UpdateUserResetPasswordEnrollmentAsync(orgId, user.Id, model.ResetPasswordKey, user.Id);
}
}
var userId = _userService.GetProperUserId(User);
var result = await _organizationService.ResendInvitesAsync(orgGuidId, userId.Value, model.Ids);
return new ListResponseModel<OrganizationUserBulkResponseModel>(
result.Select(t => new OrganizationUserBulkResponseModel(t.Item1.Id, t.Item2)));
}
[HttpPost("{id}/reinvite")]
public async Task Reinvite(string orgId, string id)
{
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageUsers(orgGuidId))
[HttpPost("{id}/confirm")]
public async Task Confirm(string orgId, string id, [FromBody] OrganizationUserConfirmRequestModel model)
{
throw new NotFoundException();
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
var result = await _organizationService.ConfirmUserAsync(orgGuidId, new Guid(id), model.Key, userId.Value,
_userService);
}
var userId = _userService.GetProperUserId(User);
await _organizationService.ResendInviteAsync(orgGuidId, userId.Value, new Guid(id));
}
[HttpPost("{organizationUserId}/accept")]
public async Task Accept(Guid orgId, Guid organizationUserId, [FromBody] OrganizationUserAcceptRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
[HttpPost("confirm")]
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkConfirm(string orgId,
[FromBody] OrganizationUserBulkConfirmRequestModel model)
{
throw new UnauthorizedAccessException();
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
var results = await _organizationService.ConfirmUsersAsync(orgGuidId, model.ToDictionary(), userId.Value,
_userService);
return new ListResponseModel<OrganizationUserBulkResponseModel>(results.Select(r =>
new OrganizationUserBulkResponseModel(r.Item1.Id, r.Item2)));
}
var masterPasswordPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(orgId, PolicyType.ResetPassword);
var useMasterPasswordPolicy = masterPasswordPolicy != null &&
masterPasswordPolicy.Enabled &&
masterPasswordPolicy.GetDataModel<ResetPasswordDataModel>().AutoEnrollEnabled;
if (useMasterPasswordPolicy &&
string.IsNullOrWhiteSpace(model.ResetPasswordKey))
[HttpPost("public-keys")]
public async Task<ListResponseModel<OrganizationUserPublicKeyResponseModel>> UserPublicKeys(string orgId, [FromBody] OrganizationUserBulkRequestModel model)
{
throw new BadRequestException(string.Empty, "Master Password reset is required, but not provided.");
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
var result = await _organizationUserRepository.GetManyPublicKeysByOrganizationUserAsync(orgGuidId, model.Ids);
var responses = result.Select(r => new OrganizationUserPublicKeyResponseModel(r.Id, r.UserId, r.PublicKey)).ToList();
return new ListResponseModel<OrganizationUserPublicKeyResponseModel>(responses);
}
await _organizationService.AcceptUserAsync(organizationUserId, user, model.Token, _userService);
if (useMasterPasswordPolicy)
[HttpPut("{id}")]
[HttpPost("{id}")]
public async Task Put(string orgId, string id, [FromBody] OrganizationUserUpdateRequestModel model)
{
await _organizationService.UpdateUserResetPasswordEnrollmentAsync(orgId, user.Id, model.ResetPasswordKey, user.Id);
}
}
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
[HttpPost("{id}/confirm")]
public async Task Confirm(string orgId, string id, [FromBody] OrganizationUserConfirmRequestModel model)
{
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
var organizationUser = await _organizationUserRepository.GetByIdAsync(new Guid(id));
if (organizationUser == null || organizationUser.OrganizationId != orgGuidId)
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
await _organizationService.SaveUserAsync(model.ToOrganizationUser(organizationUser), userId.Value,
model.Collections?.Select(c => c.ToSelectionReadOnly()));
}
var userId = _userService.GetProperUserId(User);
var result = await _organizationService.ConfirmUserAsync(orgGuidId, new Guid(id), model.Key, userId.Value,
_userService);
}
[HttpPost("confirm")]
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkConfirm(string orgId,
[FromBody] OrganizationUserBulkConfirmRequestModel model)
{
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageUsers(orgGuidId))
[HttpPut("{id}/groups")]
[HttpPost("{id}/groups")]
public async Task PutGroups(string orgId, string id, [FromBody] OrganizationUserUpdateGroupsRequestModel model)
{
throw new NotFoundException();
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
var organizationUser = await _organizationUserRepository.GetByIdAsync(new Guid(id));
if (organizationUser == null || organizationUser.OrganizationId != orgGuidId)
{
throw new NotFoundException();
}
var loggedInUserId = _userService.GetProperUserId(User);
await _organizationService.UpdateUserGroupsAsync(organizationUser, model.GroupIds.Select(g => new Guid(g)), loggedInUserId);
}
var userId = _userService.GetProperUserId(User);
var results = await _organizationService.ConfirmUsersAsync(orgGuidId, model.ToDictionary(), userId.Value,
_userService);
return new ListResponseModel<OrganizationUserBulkResponseModel>(results.Select(r =>
new OrganizationUserBulkResponseModel(r.Item1.Id, r.Item2)));
}
[HttpPost("public-keys")]
public async Task<ListResponseModel<OrganizationUserPublicKeyResponseModel>> UserPublicKeys(string orgId, [FromBody] OrganizationUserBulkRequestModel model)
{
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageUsers(orgGuidId))
[HttpPut("{userId}/reset-password-enrollment")]
public async Task PutResetPasswordEnrollment(string orgId, string userId, [FromBody] OrganizationUserResetPasswordEnrollmentRequestModel model)
{
throw new NotFoundException();
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
if (model.ResetPasswordKey != null && !await _userService.VerifySecretAsync(user, model.Secret))
{
await Task.Delay(2000);
throw new BadRequestException("MasterPasswordHash", "Invalid password.");
}
else
{
var callingUserId = user.Id;
await _organizationService.UpdateUserResetPasswordEnrollmentAsync(
new Guid(orgId), new Guid(userId), model.ResetPasswordKey, callingUserId);
}
}
var result = await _organizationUserRepository.GetManyPublicKeysByOrganizationUserAsync(orgGuidId, model.Ids);
var responses = result.Select(r => new OrganizationUserPublicKeyResponseModel(r.Id, r.UserId, r.PublicKey)).ToList();
return new ListResponseModel<OrganizationUserPublicKeyResponseModel>(responses);
}
[HttpPut("{id}")]
[HttpPost("{id}")]
public async Task Put(string orgId, string id, [FromBody] OrganizationUserUpdateRequestModel model)
{
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageUsers(orgGuidId))
[HttpPut("{id}/reset-password")]
public async Task PutResetPassword(string orgId, string id, [FromBody] OrganizationUserResetPasswordRequestModel model)
{
throw new NotFoundException();
}
var organizationUser = await _organizationUserRepository.GetByIdAsync(new Guid(id));
if (organizationUser == null || organizationUser.OrganizationId != orgGuidId)
{
throw new NotFoundException();
}
var orgGuidId = new Guid(orgId);
var userId = _userService.GetProperUserId(User);
await _organizationService.SaveUserAsync(model.ToOrganizationUser(organizationUser), userId.Value,
model.Collections?.Select(c => c.ToSelectionReadOnly()));
}
// Calling user must have Manage Reset Password permission
if (!await _currentContext.ManageResetPassword(orgGuidId))
{
throw new NotFoundException();
}
[HttpPut("{id}/groups")]
[HttpPost("{id}/groups")]
public async Task PutGroups(string orgId, string id, [FromBody] OrganizationUserUpdateGroupsRequestModel model)
{
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
// Get the users role, since provider users aren't a member of the organization we use the owner check
var orgUserType = await _currentContext.OrganizationOwner(orgGuidId)
? OrganizationUserType.Owner
: _currentContext.Organizations?.FirstOrDefault(o => o.Id == orgGuidId)?.Type;
if (orgUserType == null)
{
throw new NotFoundException();
}
var organizationUser = await _organizationUserRepository.GetByIdAsync(new Guid(id));
if (organizationUser == null || organizationUser.OrganizationId != orgGuidId)
{
throw new NotFoundException();
}
var result = await _userService.AdminResetPasswordAsync(orgUserType.Value, orgGuidId, new Guid(id), model.NewMasterPasswordHash, model.Key);
if (result.Succeeded)
{
return;
}
var loggedInUserId = _userService.GetProperUserId(User);
await _organizationService.UpdateUserGroupsAsync(organizationUser, model.GroupIds.Select(g => new Guid(g)), loggedInUserId);
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
[HttpPut("{userId}/reset-password-enrollment")]
public async Task PutResetPasswordEnrollment(string orgId, string userId, [FromBody] OrganizationUserResetPasswordEnrollmentRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
if (model.ResetPasswordKey != null && !await _userService.VerifySecretAsync(user, model.Secret))
{
await Task.Delay(2000);
throw new BadRequestException("MasterPasswordHash", "Invalid password.");
throw new BadRequestException(ModelState);
}
else
[HttpDelete("{id}")]
[HttpPost("{id}/delete")]
public async Task Delete(string orgId, string id)
{
var callingUserId = user.Id;
await _organizationService.UpdateUserResetPasswordEnrollmentAsync(
new Guid(orgId), new Guid(userId), model.ResetPasswordKey, callingUserId);
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
await _organizationService.DeleteUserAsync(orgGuidId, new Guid(id), userId.Value);
}
}
[HttpPut("{id}/reset-password")]
public async Task PutResetPassword(string orgId, string id, [FromBody] OrganizationUserResetPasswordRequestModel model)
{
var orgGuidId = new Guid(orgId);
// Calling user must have Manage Reset Password permission
if (!await _currentContext.ManageResetPassword(orgGuidId))
[HttpDelete("")]
[HttpPost("delete")]
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkDelete(string orgId, [FromBody] OrganizationUserBulkRequestModel model)
{
throw new NotFoundException();
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
var result = await _organizationService.DeleteUsersAsync(orgGuidId, model.Ids, userId.Value);
return new ListResponseModel<OrganizationUserBulkResponseModel>(result.Select(r =>
new OrganizationUserBulkResponseModel(r.Item1.Id, r.Item2)));
}
// Get the users role, since provider users aren't a member of the organization we use the owner check
var orgUserType = await _currentContext.OrganizationOwner(orgGuidId)
? OrganizationUserType.Owner
: _currentContext.Organizations?.FirstOrDefault(o => o.Id == orgGuidId)?.Type;
if (orgUserType == null)
[Obsolete("2022-07-22 Moved to {id}/revoke endpoint")]
[HttpPatch("{id}/deactivate")]
[HttpPut("{id}/deactivate")]
public async Task Deactivate(Guid orgId, Guid id)
{
throw new NotFoundException();
await RevokeAsync(orgId, id);
}
var result = await _userService.AdminResetPasswordAsync(orgUserType.Value, orgGuidId, new Guid(id), model.NewMasterPasswordHash, model.Key);
if (result.Succeeded)
[Obsolete("2022-07-22 Moved to /revoke endpoint")]
[HttpPatch("deactivate")]
[HttpPut("deactivate")]
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkDeactivate(Guid orgId, [FromBody] OrganizationUserBulkRequestModel model)
{
return;
return await BulkRevokeAsync(orgId, model);
}
foreach (var error in result.Errors)
[Obsolete("2022-07-22 Moved to {id}/restore endpoint")]
[HttpPatch("{id}/activate")]
[HttpPut("{id}/activate")]
public async Task Activate(Guid orgId, Guid id)
{
ModelState.AddModelError(string.Empty, error.Description);
await RestoreAsync(orgId, id);
}
await Task.Delay(2000);
throw new BadRequestException(ModelState);
}
[HttpDelete("{id}")]
[HttpPost("{id}/delete")]
public async Task Delete(string orgId, string id)
{
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageUsers(orgGuidId))
[Obsolete("2022-07-22 Moved to /restore endpoint")]
[HttpPatch("activate")]
[HttpPut("activate")]
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkActivate(Guid orgId, [FromBody] OrganizationUserBulkRequestModel model)
{
throw new NotFoundException();
return await BulkRestoreAsync(orgId, model);
}
var userId = _userService.GetProperUserId(User);
await _organizationService.DeleteUserAsync(orgGuidId, new Guid(id), userId.Value);
}
[HttpDelete("")]
[HttpPost("delete")]
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkDelete(string orgId, [FromBody] OrganizationUserBulkRequestModel model)
{
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageUsers(orgGuidId))
[HttpPatch("{id}/revoke")]
[HttpPut("{id}/revoke")]
public async Task RevokeAsync(Guid orgId, Guid id)
{
throw new NotFoundException();
await RestoreOrRevokeUserAsync(orgId, id, _organizationService.RevokeUserAsync);
}
var userId = _userService.GetProperUserId(User);
var result = await _organizationService.DeleteUsersAsync(orgGuidId, model.Ids, userId.Value);
return new ListResponseModel<OrganizationUserBulkResponseModel>(result.Select(r =>
new OrganizationUserBulkResponseModel(r.Item1.Id, r.Item2)));
}
[Obsolete("2022-07-22 Moved to {id}/revoke endpoint")]
[HttpPatch("{id}/deactivate")]
[HttpPut("{id}/deactivate")]
public async Task Deactivate(Guid orgId, Guid id)
{
await RevokeAsync(orgId, id);
}
[Obsolete("2022-07-22 Moved to /revoke endpoint")]
[HttpPatch("deactivate")]
[HttpPut("deactivate")]
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkDeactivate(Guid orgId, [FromBody] OrganizationUserBulkRequestModel model)
{
return await BulkRevokeAsync(orgId, model);
}
[Obsolete("2022-07-22 Moved to {id}/restore endpoint")]
[HttpPatch("{id}/activate")]
[HttpPut("{id}/activate")]
public async Task Activate(Guid orgId, Guid id)
{
await RestoreAsync(orgId, id);
}
[Obsolete("2022-07-22 Moved to /restore endpoint")]
[HttpPatch("activate")]
[HttpPut("activate")]
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkActivate(Guid orgId, [FromBody] OrganizationUserBulkRequestModel model)
{
return await BulkRestoreAsync(orgId, model);
}
[HttpPatch("{id}/revoke")]
[HttpPut("{id}/revoke")]
public async Task RevokeAsync(Guid orgId, Guid id)
{
await RestoreOrRevokeUserAsync(orgId, id, _organizationService.RevokeUserAsync);
}
[HttpPatch("revoke")]
[HttpPut("revoke")]
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkRevokeAsync(Guid orgId, [FromBody] OrganizationUserBulkRequestModel model)
{
return await RestoreOrRevokeUsersAsync(orgId, model, _organizationService.RevokeUsersAsync);
}
[HttpPatch("{id}/restore")]
[HttpPut("{id}/restore")]
public async Task RestoreAsync(Guid orgId, Guid id)
{
await RestoreOrRevokeUserAsync(orgId, id, (orgUser, userId) => _organizationService.RestoreUserAsync(orgUser, userId, _userService));
}
[HttpPatch("restore")]
[HttpPut("restore")]
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkRestoreAsync(Guid orgId, [FromBody] OrganizationUserBulkRequestModel model)
{
return await RestoreOrRevokeUsersAsync(orgId, model, (orgId, orgUserIds, restoringUserId) => _organizationService.RestoreUsersAsync(orgId, orgUserIds, restoringUserId, _userService));
}
private async Task RestoreOrRevokeUserAsync(
Guid orgId,
Guid id,
Func<OrganizationUser, Guid?, Task> statusAction)
{
if (!await _currentContext.ManageUsers(orgId))
[HttpPatch("revoke")]
[HttpPut("revoke")]
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkRevokeAsync(Guid orgId, [FromBody] OrganizationUserBulkRequestModel model)
{
throw new NotFoundException();
return await RestoreOrRevokeUsersAsync(orgId, model, _organizationService.RevokeUsersAsync);
}
var userId = _userService.GetProperUserId(User);
var orgUser = await _organizationUserRepository.GetByIdAsync(id);
if (orgUser == null || orgUser.OrganizationId != orgId)
[HttpPatch("{id}/restore")]
[HttpPut("{id}/restore")]
public async Task RestoreAsync(Guid orgId, Guid id)
{
throw new NotFoundException();
await RestoreOrRevokeUserAsync(orgId, id, (orgUser, userId) => _organizationService.RestoreUserAsync(orgUser, userId, _userService));
}
await statusAction(orgUser, userId);
}
private async Task<ListResponseModel<OrganizationUserBulkResponseModel>> RestoreOrRevokeUsersAsync(
Guid orgId,
OrganizationUserBulkRequestModel model,
Func<Guid, IEnumerable<Guid>, Guid?, Task<List<Tuple<OrganizationUser, string>>>> statusAction)
{
if (!await _currentContext.ManageUsers(orgId))
[HttpPatch("restore")]
[HttpPut("restore")]
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkRestoreAsync(Guid orgId, [FromBody] OrganizationUserBulkRequestModel model)
{
throw new NotFoundException();
return await RestoreOrRevokeUsersAsync(orgId, model, (orgId, orgUserIds, restoringUserId) => _organizationService.RestoreUsersAsync(orgId, orgUserIds, restoringUserId, _userService));
}
var userId = _userService.GetProperUserId(User);
var result = await statusAction(orgId, model.Ids, userId.Value);
return new ListResponseModel<OrganizationUserBulkResponseModel>(result.Select(r =>
new OrganizationUserBulkResponseModel(r.Item1.Id, r.Item2)));
private async Task RestoreOrRevokeUserAsync(
Guid orgId,
Guid id,
Func<OrganizationUser, Guid?, Task> statusAction)
{
if (!await _currentContext.ManageUsers(orgId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
var orgUser = await _organizationUserRepository.GetByIdAsync(id);
if (orgUser == null || orgUser.OrganizationId != orgId)
{
throw new NotFoundException();
}
await statusAction(orgUser, userId);
}
private async Task<ListResponseModel<OrganizationUserBulkResponseModel>> RestoreOrRevokeUsersAsync(
Guid orgId,
OrganizationUserBulkRequestModel model,
Func<Guid, IEnumerable<Guid>, Guid?, Task<List<Tuple<OrganizationUser, string>>>> statusAction)
{
if (!await _currentContext.ManageUsers(orgId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
var result = await statusAction(orgId, model.Ids, userId.Value);
return new ListResponseModel<OrganizationUserBulkResponseModel>(result.Select(r =>
new OrganizationUserBulkResponseModel(r.Item1.Id, r.Item2)));
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -4,32 +4,33 @@ using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Route("plans")]
[Authorize("Web")]
public class PlansController : Controller
namespace Bit.Api.Controllers
{
private readonly ITaxRateRepository _taxRateRepository;
public PlansController(ITaxRateRepository taxRateRepository)
[Route("plans")]
[Authorize("Web")]
public class PlansController : Controller
{
_taxRateRepository = taxRateRepository;
}
private readonly ITaxRateRepository _taxRateRepository;
public PlansController(ITaxRateRepository taxRateRepository)
{
_taxRateRepository = taxRateRepository;
}
[HttpGet("")]
[AllowAnonymous]
public ListResponseModel<PlanResponseModel> Get()
{
var data = StaticStore.Plans;
var responses = data.Select(plan => new PlanResponseModel(plan));
return new ListResponseModel<PlanResponseModel>(responses);
}
[HttpGet("")]
[AllowAnonymous]
public ListResponseModel<PlanResponseModel> Get()
{
var data = StaticStore.Plans;
var responses = data.Select(plan => new PlanResponseModel(plan));
return new ListResponseModel<PlanResponseModel>(responses);
}
[HttpGet("sales-tax-rates")]
public async Task<ListResponseModel<TaxRateResponseModel>> GetTaxRates()
{
var data = await _taxRateRepository.GetAllActiveAsync();
var responses = data.Select(x => new TaxRateResponseModel(x));
return new ListResponseModel<TaxRateResponseModel>(responses);
[HttpGet("sales-tax-rates")]
public async Task<ListResponseModel<TaxRateResponseModel>> GetTaxRates()
{
var data = await _taxRateRepository.GetAllActiveAsync();
var responses = data.Select(x => new TaxRateResponseModel(x));
return new ListResponseModel<TaxRateResponseModel>(responses);
}
}
}

View File

@ -11,144 +11,145 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Route("organizations/{orgId}/policies")]
[Authorize("Application")]
public class PoliciesController : Controller
namespace Bit.Api.Controllers
{
private readonly IPolicyRepository _policyRepository;
private readonly IPolicyService _policyService;
private readonly IOrganizationService _organizationService;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IUserService _userService;
private readonly ICurrentContext _currentContext;
private readonly GlobalSettings _globalSettings;
private readonly IDataProtector _organizationServiceDataProtector;
public PoliciesController(
IPolicyRepository policyRepository,
IPolicyService policyService,
IOrganizationService organizationService,
IOrganizationUserRepository organizationUserRepository,
IUserService userService,
ICurrentContext currentContext,
GlobalSettings globalSettings,
IDataProtectionProvider dataProtectionProvider)
[Route("organizations/{orgId}/policies")]
[Authorize("Application")]
public class PoliciesController : Controller
{
_policyRepository = policyRepository;
_policyService = policyService;
_organizationService = organizationService;
_organizationUserRepository = organizationUserRepository;
_userService = userService;
_currentContext = currentContext;
_globalSettings = globalSettings;
_organizationServiceDataProtector = dataProtectionProvider.CreateProtector(
"OrganizationServiceDataProtector");
}
private readonly IPolicyRepository _policyRepository;
private readonly IPolicyService _policyService;
private readonly IOrganizationService _organizationService;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IUserService _userService;
private readonly ICurrentContext _currentContext;
private readonly GlobalSettings _globalSettings;
private readonly IDataProtector _organizationServiceDataProtector;
[HttpGet("{type}")]
public async Task<PolicyResponseModel> Get(string orgId, int type)
{
var orgIdGuid = new Guid(orgId);
if (!await _currentContext.ManagePolicies(orgIdGuid))
public PoliciesController(
IPolicyRepository policyRepository,
IPolicyService policyService,
IOrganizationService organizationService,
IOrganizationUserRepository organizationUserRepository,
IUserService userService,
ICurrentContext currentContext,
GlobalSettings globalSettings,
IDataProtectionProvider dataProtectionProvider)
{
throw new NotFoundException();
}
var policy = await _policyRepository.GetByOrganizationIdTypeAsync(orgIdGuid, (PolicyType)type);
if (policy == null)
{
throw new NotFoundException();
_policyRepository = policyRepository;
_policyService = policyService;
_organizationService = organizationService;
_organizationUserRepository = organizationUserRepository;
_userService = userService;
_currentContext = currentContext;
_globalSettings = globalSettings;
_organizationServiceDataProtector = dataProtectionProvider.CreateProtector(
"OrganizationServiceDataProtector");
}
return new PolicyResponseModel(policy);
}
[HttpGet("")]
public async Task<ListResponseModel<PolicyResponseModel>> Get(string orgId)
{
var orgIdGuid = new Guid(orgId);
if (!await _currentContext.ManagePolicies(orgIdGuid))
[HttpGet("{type}")]
public async Task<PolicyResponseModel> Get(string orgId, int type)
{
throw new NotFoundException();
var orgIdGuid = new Guid(orgId);
if (!await _currentContext.ManagePolicies(orgIdGuid))
{
throw new NotFoundException();
}
var policy = await _policyRepository.GetByOrganizationIdTypeAsync(orgIdGuid, (PolicyType)type);
if (policy == null)
{
throw new NotFoundException();
}
return new PolicyResponseModel(policy);
}
var policies = await _policyRepository.GetManyByOrganizationIdAsync(orgIdGuid);
var responses = policies.Select(p => new PolicyResponseModel(p));
return new ListResponseModel<PolicyResponseModel>(responses);
}
[AllowAnonymous]
[HttpGet("token")]
public async Task<ListResponseModel<PolicyResponseModel>> GetByToken(string orgId, [FromQuery] string email,
[FromQuery] string token, [FromQuery] string organizationUserId)
{
var orgUserId = new Guid(organizationUserId);
var tokenValid = CoreHelpers.UserInviteTokenIsValid(_organizationServiceDataProtector, token,
email, orgUserId, _globalSettings);
if (!tokenValid)
[HttpGet("")]
public async Task<ListResponseModel<PolicyResponseModel>> Get(string orgId)
{
throw new NotFoundException();
var orgIdGuid = new Guid(orgId);
if (!await _currentContext.ManagePolicies(orgIdGuid))
{
throw new NotFoundException();
}
var policies = await _policyRepository.GetManyByOrganizationIdAsync(orgIdGuid);
var responses = policies.Select(p => new PolicyResponseModel(p));
return new ListResponseModel<PolicyResponseModel>(responses);
}
var orgIdGuid = new Guid(orgId);
var orgUser = await _organizationUserRepository.GetByIdAsync(orgUserId);
if (orgUser == null || orgUser.OrganizationId != orgIdGuid)
[AllowAnonymous]
[HttpGet("token")]
public async Task<ListResponseModel<PolicyResponseModel>> GetByToken(string orgId, [FromQuery] string email,
[FromQuery] string token, [FromQuery] string organizationUserId)
{
throw new NotFoundException();
var orgUserId = new Guid(organizationUserId);
var tokenValid = CoreHelpers.UserInviteTokenIsValid(_organizationServiceDataProtector, token,
email, orgUserId, _globalSettings);
if (!tokenValid)
{
throw new NotFoundException();
}
var orgIdGuid = new Guid(orgId);
var orgUser = await _organizationUserRepository.GetByIdAsync(orgUserId);
if (orgUser == null || orgUser.OrganizationId != orgIdGuid)
{
throw new NotFoundException();
}
var policies = await _policyRepository.GetManyByOrganizationIdAsync(orgIdGuid);
var responses = policies.Where(p => p.Enabled).Select(p => new PolicyResponseModel(p));
return new ListResponseModel<PolicyResponseModel>(responses);
}
var policies = await _policyRepository.GetManyByOrganizationIdAsync(orgIdGuid);
var responses = policies.Where(p => p.Enabled).Select(p => new PolicyResponseModel(p));
return new ListResponseModel<PolicyResponseModel>(responses);
}
[AllowAnonymous]
[HttpGet("invited-user")]
public async Task<ListResponseModel<PolicyResponseModel>> GetByInvitedUser(string orgId, [FromQuery] string userId)
{
var user = await _userService.GetUserByIdAsync(new Guid(userId));
if (user == null)
{
throw new UnauthorizedAccessException();
}
var orgIdGuid = new Guid(orgId);
var orgUsersByUserId = await _organizationUserRepository.GetManyByUserAsync(user.Id);
var orgUser = orgUsersByUserId.SingleOrDefault(u => u.OrganizationId == orgIdGuid);
if (orgUser == null)
{
throw new NotFoundException();
}
if (orgUser.Status != OrganizationUserStatusType.Invited)
{
throw new UnauthorizedAccessException();
}
[AllowAnonymous]
[HttpGet("invited-user")]
public async Task<ListResponseModel<PolicyResponseModel>> GetByInvitedUser(string orgId, [FromQuery] string userId)
{
var user = await _userService.GetUserByIdAsync(new Guid(userId));
if (user == null)
{
throw new UnauthorizedAccessException();
}
var orgIdGuid = new Guid(orgId);
var orgUsersByUserId = await _organizationUserRepository.GetManyByUserAsync(user.Id);
var orgUser = orgUsersByUserId.SingleOrDefault(u => u.OrganizationId == orgIdGuid);
if (orgUser == null)
{
throw new NotFoundException();
}
if (orgUser.Status != OrganizationUserStatusType.Invited)
{
throw new UnauthorizedAccessException();
var policies = await _policyRepository.GetManyByOrganizationIdAsync(orgIdGuid);
var responses = policies.Where(p => p.Enabled).Select(p => new PolicyResponseModel(p));
return new ListResponseModel<PolicyResponseModel>(responses);
}
var policies = await _policyRepository.GetManyByOrganizationIdAsync(orgIdGuid);
var responses = policies.Where(p => p.Enabled).Select(p => new PolicyResponseModel(p));
return new ListResponseModel<PolicyResponseModel>(responses);
}
[HttpPut("{type}")]
public async Task<PolicyResponseModel> Put(string orgId, int type, [FromBody] PolicyRequestModel model)
{
var orgIdGuid = new Guid(orgId);
if (!await _currentContext.ManagePolicies(orgIdGuid))
{
throw new NotFoundException();
}
var policy = await _policyRepository.GetByOrganizationIdTypeAsync(new Guid(orgId), (PolicyType)type);
if (policy == null)
{
policy = model.ToPolicy(orgIdGuid);
}
else
{
policy = model.ToPolicy(policy);
}
[HttpPut("{type}")]
public async Task<PolicyResponseModel> Put(string orgId, int type, [FromBody] PolicyRequestModel model)
{
var orgIdGuid = new Guid(orgId);
if (!await _currentContext.ManagePolicies(orgIdGuid))
{
throw new NotFoundException();
var userId = _userService.GetProperUserId(User);
await _policyService.SaveAsync(policy, _userService, _organizationService, userId);
return new PolicyResponseModel(policy);
}
var policy = await _policyRepository.GetByOrganizationIdTypeAsync(new Guid(orgId), (PolicyType)type);
if (policy == null)
{
policy = model.ToPolicy(orgIdGuid);
}
else
{
policy = model.ToPolicy(policy);
}
var userId = _userService.GetProperUserId(User);
await _policyService.SaveAsync(policy, _userService, _organizationService, userId);
return new PolicyResponseModel(policy);
}
}

View File

@ -9,86 +9,87 @@ using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Route("providers/{providerId:guid}/organizations")]
[Authorize("Application")]
public class ProviderOrganizationsController : Controller
namespace Bit.Api.Controllers
{
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
private readonly IProviderService _providerService;
private readonly IUserService _userService;
private readonly ICurrentContext _currentContext;
public ProviderOrganizationsController(
IProviderOrganizationRepository providerOrganizationRepository,
IProviderService providerService,
IUserService userService,
ICurrentContext currentContext)
[Route("providers/{providerId:guid}/organizations")]
[Authorize("Application")]
public class ProviderOrganizationsController : Controller
{
_providerOrganizationRepository = providerOrganizationRepository;
_providerService = providerService;
_userService = userService;
_currentContext = currentContext;
}
[HttpGet("")]
public async Task<ListResponseModel<ProviderOrganizationOrganizationDetailsResponseModel>> Get(Guid providerId)
{
if (!_currentContext.AccessProviderOrganizations(providerId))
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
private readonly IProviderService _providerService;
private readonly IUserService _userService;
private readonly ICurrentContext _currentContext;
public ProviderOrganizationsController(
IProviderOrganizationRepository providerOrganizationRepository,
IProviderService providerService,
IUserService userService,
ICurrentContext currentContext)
{
throw new NotFoundException();
_providerOrganizationRepository = providerOrganizationRepository;
_providerService = providerService;
_userService = userService;
_currentContext = currentContext;
}
var providerOrganizations = await _providerOrganizationRepository.GetManyDetailsByProviderAsync(providerId);
var responses = providerOrganizations.Select(o => new ProviderOrganizationOrganizationDetailsResponseModel(o));
return new ListResponseModel<ProviderOrganizationOrganizationDetailsResponseModel>(responses);
}
[HttpPost("add")]
public async Task Add(Guid providerId, [FromBody] ProviderOrganizationAddRequestModel model)
{
if (!_currentContext.ManageProviderOrganizations(providerId))
[HttpGet("")]
public async Task<ListResponseModel<ProviderOrganizationOrganizationDetailsResponseModel>> Get(Guid providerId)
{
throw new NotFoundException();
if (!_currentContext.AccessProviderOrganizations(providerId))
{
throw new NotFoundException();
}
var providerOrganizations = await _providerOrganizationRepository.GetManyDetailsByProviderAsync(providerId);
var responses = providerOrganizations.Select(o => new ProviderOrganizationOrganizationDetailsResponseModel(o));
return new ListResponseModel<ProviderOrganizationOrganizationDetailsResponseModel>(responses);
}
var userId = _userService.GetProperUserId(User).Value;
await _providerService.AddOrganization(providerId, model.OrganizationId, userId, model.Key);
}
[HttpPost("")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<ProviderOrganizationResponseModel> Post(Guid providerId, [FromBody] ProviderOrganizationCreateRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
[HttpPost("add")]
public async Task Add(Guid providerId, [FromBody] ProviderOrganizationAddRequestModel model)
{
throw new UnauthorizedAccessException();
if (!_currentContext.ManageProviderOrganizations(providerId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User).Value;
await _providerService.AddOrganization(providerId, model.OrganizationId, userId, model.Key);
}
if (!_currentContext.ManageProviderOrganizations(providerId))
[HttpPost("")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<ProviderOrganizationResponseModel> Post(Guid providerId, [FromBody] ProviderOrganizationCreateRequestModel model)
{
throw new NotFoundException();
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
if (!_currentContext.ManageProviderOrganizations(providerId))
{
throw new NotFoundException();
}
var organizationSignup = model.OrganizationCreateRequest.ToOrganizationSignup(user);
var result = await _providerService.CreateOrganizationAsync(providerId, organizationSignup, model.ClientOwnerEmail, user);
return new ProviderOrganizationResponseModel(result);
}
var organizationSignup = model.OrganizationCreateRequest.ToOrganizationSignup(user);
var result = await _providerService.CreateOrganizationAsync(providerId, organizationSignup, model.ClientOwnerEmail, user);
return new ProviderOrganizationResponseModel(result);
}
[HttpDelete("{id:guid}")]
[HttpPost("{id:guid}/delete")]
public async Task Delete(Guid providerId, Guid id)
{
if (!_currentContext.ManageProviderOrganizations(providerId))
[HttpDelete("{id:guid}")]
[HttpPost("{id:guid}/delete")]
public async Task Delete(Guid providerId, Guid id)
{
throw new NotFoundException();
}
if (!_currentContext.ManageProviderOrganizations(providerId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
await _providerService.RemoveOrganizationAsync(providerId, id, userId.Value);
var userId = _userService.GetProperUserId(User);
await _providerService.RemoveOrganizationAsync(providerId, id, userId.Value);
}
}
}

View File

@ -9,191 +9,192 @@ using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Route("providers/{providerId:guid}/users")]
[Authorize("Application")]
public class ProviderUsersController : Controller
namespace Bit.Api.Controllers
{
private readonly IProviderUserRepository _providerUserRepository;
private readonly IProviderService _providerService;
private readonly IUserService _userService;
private readonly ICurrentContext _currentContext;
public ProviderUsersController(
IProviderUserRepository providerUserRepository,
IProviderService providerService,
IUserService userService,
ICurrentContext currentContext)
[Route("providers/{providerId:guid}/users")]
[Authorize("Application")]
public class ProviderUsersController : Controller
{
_providerUserRepository = providerUserRepository;
_providerService = providerService;
_userService = userService;
_currentContext = currentContext;
}
private readonly IProviderUserRepository _providerUserRepository;
private readonly IProviderService _providerService;
private readonly IUserService _userService;
private readonly ICurrentContext _currentContext;
[HttpGet("{id:guid}")]
public async Task<ProviderUserResponseModel> Get(Guid providerId, Guid id)
{
var providerUser = await _providerUserRepository.GetByIdAsync(id);
if (providerUser == null || !_currentContext.ProviderManageUsers(providerUser.ProviderId))
public ProviderUsersController(
IProviderUserRepository providerUserRepository,
IProviderService providerService,
IUserService userService,
ICurrentContext currentContext)
{
throw new NotFoundException();
_providerUserRepository = providerUserRepository;
_providerService = providerService;
_userService = userService;
_currentContext = currentContext;
}
return new ProviderUserResponseModel(providerUser);
}
[HttpGet("")]
public async Task<ListResponseModel<ProviderUserUserDetailsResponseModel>> Get(Guid providerId)
{
if (!_currentContext.ProviderManageUsers(providerId))
[HttpGet("{id:guid}")]
public async Task<ProviderUserResponseModel> Get(Guid providerId, Guid id)
{
throw new NotFoundException();
var providerUser = await _providerUserRepository.GetByIdAsync(id);
if (providerUser == null || !_currentContext.ProviderManageUsers(providerUser.ProviderId))
{
throw new NotFoundException();
}
return new ProviderUserResponseModel(providerUser);
}
var providerUsers = await _providerUserRepository.GetManyDetailsByProviderAsync(providerId);
var responses = providerUsers.Select(o => new ProviderUserUserDetailsResponseModel(o));
return new ListResponseModel<ProviderUserUserDetailsResponseModel>(responses);
}
[HttpPost("invite")]
public async Task Invite(Guid providerId, [FromBody] ProviderUserInviteRequestModel model)
{
if (!_currentContext.ProviderManageUsers(providerId))
[HttpGet("")]
public async Task<ListResponseModel<ProviderUserUserDetailsResponseModel>> Get(Guid providerId)
{
throw new NotFoundException();
if (!_currentContext.ProviderManageUsers(providerId))
{
throw new NotFoundException();
}
var providerUsers = await _providerUserRepository.GetManyDetailsByProviderAsync(providerId);
var responses = providerUsers.Select(o => new ProviderUserUserDetailsResponseModel(o));
return new ListResponseModel<ProviderUserUserDetailsResponseModel>(responses);
}
var invite = ProviderUserInviteFactory.CreateIntialInvite(model.Emails, model.Type.Value,
_userService.GetProperUserId(User).Value, providerId);
await _providerService.InviteUserAsync(invite);
}
[HttpPost("reinvite")]
public async Task<ListResponseModel<ProviderUserBulkResponseModel>> BulkReinvite(Guid providerId, [FromBody] ProviderUserBulkRequestModel model)
{
if (!_currentContext.ProviderManageUsers(providerId))
[HttpPost("invite")]
public async Task Invite(Guid providerId, [FromBody] ProviderUserInviteRequestModel model)
{
throw new NotFoundException();
if (!_currentContext.ProviderManageUsers(providerId))
{
throw new NotFoundException();
}
var invite = ProviderUserInviteFactory.CreateIntialInvite(model.Emails, model.Type.Value,
_userService.GetProperUserId(User).Value, providerId);
await _providerService.InviteUserAsync(invite);
}
var invite = ProviderUserInviteFactory.CreateReinvite(model.Ids, _userService.GetProperUserId(User).Value, providerId);
var result = await _providerService.ResendInvitesAsync(invite);
return new ListResponseModel<ProviderUserBulkResponseModel>(
result.Select(t => new ProviderUserBulkResponseModel(t.Item1.Id, t.Item2)));
}
[HttpPost("{id:guid}/reinvite")]
public async Task Reinvite(Guid providerId, Guid id)
{
if (!_currentContext.ProviderManageUsers(providerId))
[HttpPost("reinvite")]
public async Task<ListResponseModel<ProviderUserBulkResponseModel>> BulkReinvite(Guid providerId, [FromBody] ProviderUserBulkRequestModel model)
{
throw new NotFoundException();
if (!_currentContext.ProviderManageUsers(providerId))
{
throw new NotFoundException();
}
var invite = ProviderUserInviteFactory.CreateReinvite(model.Ids, _userService.GetProperUserId(User).Value, providerId);
var result = await _providerService.ResendInvitesAsync(invite);
return new ListResponseModel<ProviderUserBulkResponseModel>(
result.Select(t => new ProviderUserBulkResponseModel(t.Item1.Id, t.Item2)));
}
var invite = ProviderUserInviteFactory.CreateReinvite(new[] { id },
_userService.GetProperUserId(User).Value, providerId);
await _providerService.ResendInvitesAsync(invite);
}
[HttpPost("{id:guid}/accept")]
public async Task Accept(Guid providerId, Guid id, [FromBody] ProviderUserAcceptRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
[HttpPost("{id:guid}/reinvite")]
public async Task Reinvite(Guid providerId, Guid id)
{
throw new UnauthorizedAccessException();
if (!_currentContext.ProviderManageUsers(providerId))
{
throw new NotFoundException();
}
var invite = ProviderUserInviteFactory.CreateReinvite(new[] { id },
_userService.GetProperUserId(User).Value, providerId);
await _providerService.ResendInvitesAsync(invite);
}
await _providerService.AcceptUserAsync(id, user, model.Token);
}
[HttpPost("{id:guid}/confirm")]
public async Task Confirm(Guid providerId, Guid id, [FromBody] ProviderUserConfirmRequestModel model)
{
if (!_currentContext.ProviderManageUsers(providerId))
[HttpPost("{id:guid}/accept")]
public async Task Accept(Guid providerId, Guid id, [FromBody] ProviderUserAcceptRequestModel model)
{
throw new NotFoundException();
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
await _providerService.AcceptUserAsync(id, user, model.Token);
}
var userId = _userService.GetProperUserId(User);
await _providerService.ConfirmUsersAsync(providerId, new Dictionary<Guid, string> { [id] = model.Key }, userId.Value);
}
[HttpPost("confirm")]
public async Task<ListResponseModel<ProviderUserBulkResponseModel>> BulkConfirm(Guid providerId,
[FromBody] ProviderUserBulkConfirmRequestModel model)
{
if (!_currentContext.ProviderManageUsers(providerId))
[HttpPost("{id:guid}/confirm")]
public async Task Confirm(Guid providerId, Guid id, [FromBody] ProviderUserConfirmRequestModel model)
{
throw new NotFoundException();
if (!_currentContext.ProviderManageUsers(providerId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
await _providerService.ConfirmUsersAsync(providerId, new Dictionary<Guid, string> { [id] = model.Key }, userId.Value);
}
var userId = _userService.GetProperUserId(User);
var results = await _providerService.ConfirmUsersAsync(providerId, model.ToDictionary(), userId.Value);
return new ListResponseModel<ProviderUserBulkResponseModel>(results.Select(r =>
new ProviderUserBulkResponseModel(r.Item1.Id, r.Item2)));
}
[HttpPost("public-keys")]
public async Task<ListResponseModel<ProviderUserPublicKeyResponseModel>> UserPublicKeys(Guid providerId, [FromBody] ProviderUserBulkRequestModel model)
{
if (!_currentContext.ProviderManageUsers(providerId))
[HttpPost("confirm")]
public async Task<ListResponseModel<ProviderUserBulkResponseModel>> BulkConfirm(Guid providerId,
[FromBody] ProviderUserBulkConfirmRequestModel model)
{
throw new NotFoundException();
if (!_currentContext.ProviderManageUsers(providerId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
var results = await _providerService.ConfirmUsersAsync(providerId, model.ToDictionary(), userId.Value);
return new ListResponseModel<ProviderUserBulkResponseModel>(results.Select(r =>
new ProviderUserBulkResponseModel(r.Item1.Id, r.Item2)));
}
var result = await _providerUserRepository.GetManyPublicKeysByProviderUserAsync(providerId, model.Ids);
var responses = result.Select(r => new ProviderUserPublicKeyResponseModel(r.Id, r.UserId, r.PublicKey)).ToList();
return new ListResponseModel<ProviderUserPublicKeyResponseModel>(responses);
}
[HttpPut("{id:guid}")]
[HttpPost("{id:guid}")]
public async Task Put(Guid providerId, Guid id, [FromBody] ProviderUserUpdateRequestModel model)
{
if (!_currentContext.ProviderManageUsers(providerId))
[HttpPost("public-keys")]
public async Task<ListResponseModel<ProviderUserPublicKeyResponseModel>> UserPublicKeys(Guid providerId, [FromBody] ProviderUserBulkRequestModel model)
{
throw new NotFoundException();
if (!_currentContext.ProviderManageUsers(providerId))
{
throw new NotFoundException();
}
var result = await _providerUserRepository.GetManyPublicKeysByProviderUserAsync(providerId, model.Ids);
var responses = result.Select(r => new ProviderUserPublicKeyResponseModel(r.Id, r.UserId, r.PublicKey)).ToList();
return new ListResponseModel<ProviderUserPublicKeyResponseModel>(responses);
}
var providerUser = await _providerUserRepository.GetByIdAsync(id);
if (providerUser == null || providerUser.ProviderId != providerId)
[HttpPut("{id:guid}")]
[HttpPost("{id:guid}")]
public async Task Put(Guid providerId, Guid id, [FromBody] ProviderUserUpdateRequestModel model)
{
throw new NotFoundException();
if (!_currentContext.ProviderManageUsers(providerId))
{
throw new NotFoundException();
}
var providerUser = await _providerUserRepository.GetByIdAsync(id);
if (providerUser == null || providerUser.ProviderId != providerId)
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
await _providerService.SaveUserAsync(model.ToProviderUser(providerUser), userId.Value);
}
var userId = _userService.GetProperUserId(User);
await _providerService.SaveUserAsync(model.ToProviderUser(providerUser), userId.Value);
}
[HttpDelete("{id:guid}")]
[HttpPost("{id:guid}/delete")]
public async Task Delete(Guid providerId, Guid id)
{
if (!_currentContext.ProviderManageUsers(providerId))
[HttpDelete("{id:guid}")]
[HttpPost("{id:guid}/delete")]
public async Task Delete(Guid providerId, Guid id)
{
throw new NotFoundException();
if (!_currentContext.ProviderManageUsers(providerId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
await _providerService.DeleteUsersAsync(providerId, new[] { id }, userId.Value);
}
var userId = _userService.GetProperUserId(User);
await _providerService.DeleteUsersAsync(providerId, new[] { id }, userId.Value);
}
[HttpDelete("")]
[HttpPost("delete")]
public async Task<ListResponseModel<ProviderUserBulkResponseModel>> BulkDelete(Guid providerId, [FromBody] ProviderUserBulkRequestModel model)
{
if (!_currentContext.ProviderManageUsers(providerId))
[HttpDelete("")]
[HttpPost("delete")]
public async Task<ListResponseModel<ProviderUserBulkResponseModel>> BulkDelete(Guid providerId, [FromBody] ProviderUserBulkRequestModel model)
{
throw new NotFoundException();
}
if (!_currentContext.ProviderManageUsers(providerId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
var result = await _providerService.DeleteUsersAsync(providerId, model.Ids, userId.Value);
return new ListResponseModel<ProviderUserBulkResponseModel>(result.Select(r =>
new ProviderUserBulkResponseModel(r.Item1.Id, r.Item2)));
var userId = _userService.GetProperUserId(User);
var result = await _providerService.DeleteUsersAsync(providerId, model.Ids, userId.Value);
return new ListResponseModel<ProviderUserBulkResponseModel>(result.Select(r =>
new ProviderUserBulkResponseModel(r.Item1.Id, r.Item2)));
}
}
}

View File

@ -8,83 +8,84 @@ using Bit.Core.Settings;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Route("providers")]
[Authorize("Application")]
public class ProvidersController : Controller
namespace Bit.Api.Controllers
{
private readonly IUserService _userService;
private readonly IProviderRepository _providerRepository;
private readonly IProviderService _providerService;
private readonly ICurrentContext _currentContext;
private readonly GlobalSettings _globalSettings;
public ProvidersController(IUserService userService, IProviderRepository providerRepository,
IProviderService providerService, ICurrentContext currentContext, GlobalSettings globalSettings)
[Route("providers")]
[Authorize("Application")]
public class ProvidersController : Controller
{
_userService = userService;
_providerRepository = providerRepository;
_providerService = providerService;
_currentContext = currentContext;
_globalSettings = globalSettings;
}
private readonly IUserService _userService;
private readonly IProviderRepository _providerRepository;
private readonly IProviderService _providerService;
private readonly ICurrentContext _currentContext;
private readonly GlobalSettings _globalSettings;
[HttpGet("{id:guid}")]
public async Task<ProviderResponseModel> Get(Guid id)
{
if (!_currentContext.ProviderUser(id))
public ProvidersController(IUserService userService, IProviderRepository providerRepository,
IProviderService providerService, ICurrentContext currentContext, GlobalSettings globalSettings)
{
throw new NotFoundException();
_userService = userService;
_providerRepository = providerRepository;
_providerService = providerService;
_currentContext = currentContext;
_globalSettings = globalSettings;
}
var provider = await _providerRepository.GetByIdAsync(id);
if (provider == null)
[HttpGet("{id:guid}")]
public async Task<ProviderResponseModel> Get(Guid id)
{
throw new NotFoundException();
if (!_currentContext.ProviderUser(id))
{
throw new NotFoundException();
}
var provider = await _providerRepository.GetByIdAsync(id);
if (provider == null)
{
throw new NotFoundException();
}
return new ProviderResponseModel(provider);
}
return new ProviderResponseModel(provider);
}
[HttpPut("{id:guid}")]
[HttpPost("{id:guid}")]
public async Task<ProviderResponseModel> Put(Guid id, [FromBody] ProviderUpdateRequestModel model)
{
if (!_currentContext.ProviderProviderAdmin(id))
[HttpPut("{id:guid}")]
[HttpPost("{id:guid}")]
public async Task<ProviderResponseModel> Put(Guid id, [FromBody] ProviderUpdateRequestModel model)
{
throw new NotFoundException();
if (!_currentContext.ProviderProviderAdmin(id))
{
throw new NotFoundException();
}
var provider = await _providerRepository.GetByIdAsync(id);
if (provider == null)
{
throw new NotFoundException();
}
await _providerService.UpdateAsync(model.ToProvider(provider, _globalSettings));
return new ProviderResponseModel(provider);
}
var provider = await _providerRepository.GetByIdAsync(id);
if (provider == null)
[HttpPost("{id:guid}/setup")]
public async Task<ProviderResponseModel> Setup(Guid id, [FromBody] ProviderSetupRequestModel model)
{
throw new NotFoundException();
if (!_currentContext.ProviderProviderAdmin(id))
{
throw new NotFoundException();
}
var provider = await _providerRepository.GetByIdAsync(id);
if (provider == null)
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User).Value;
var response =
await _providerService.CompleteSetupAsync(model.ToProvider(provider), userId, model.Token, model.Key);
return new ProviderResponseModel(response);
}
await _providerService.UpdateAsync(model.ToProvider(provider, _globalSettings));
return new ProviderResponseModel(provider);
}
[HttpPost("{id:guid}/setup")]
public async Task<ProviderResponseModel> Setup(Guid id, [FromBody] ProviderSetupRequestModel model)
{
if (!_currentContext.ProviderProviderAdmin(id))
{
throw new NotFoundException();
}
var provider = await _providerRepository.GetByIdAsync(id);
if (provider == null)
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User).Value;
var response =
await _providerService.CompleteSetupAsync(model.ToProvider(provider), userId, model.Token, model.Key);
return new ProviderResponseModel(response);
}
}

View File

@ -7,108 +7,109 @@ using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Route("push")]
[Authorize("Push")]
[SelfHosted(NotSelfHostedOnly = true)]
public class PushController : Controller
namespace Bit.Api.Controllers
{
private readonly IPushRegistrationService _pushRegistrationService;
private readonly IPushNotificationService _pushNotificationService;
private readonly IWebHostEnvironment _environment;
private readonly ICurrentContext _currentContext;
private readonly GlobalSettings _globalSettings;
public PushController(
IPushRegistrationService pushRegistrationService,
IPushNotificationService pushNotificationService,
IWebHostEnvironment environment,
ICurrentContext currentContext,
GlobalSettings globalSettings)
[Route("push")]
[Authorize("Push")]
[SelfHosted(NotSelfHostedOnly = true)]
public class PushController : Controller
{
_currentContext = currentContext;
_environment = environment;
_pushRegistrationService = pushRegistrationService;
_pushNotificationService = pushNotificationService;
_globalSettings = globalSettings;
}
private readonly IPushRegistrationService _pushRegistrationService;
private readonly IPushNotificationService _pushNotificationService;
private readonly IWebHostEnvironment _environment;
private readonly ICurrentContext _currentContext;
private readonly GlobalSettings _globalSettings;
[HttpPost("register")]
public async Task PostRegister([FromBody] PushRegistrationRequestModel model)
{
CheckUsage();
await _pushRegistrationService.CreateOrUpdateRegistrationAsync(model.PushToken, Prefix(model.DeviceId),
Prefix(model.UserId), Prefix(model.Identifier), model.Type);
}
[HttpDelete("{id}")]
public async Task Delete(string id)
{
CheckUsage();
await _pushRegistrationService.DeleteRegistrationAsync(Prefix(id));
}
[HttpPut("add-organization")]
public async Task PutAddOrganization([FromBody] PushUpdateRequestModel model)
{
CheckUsage();
await _pushRegistrationService.AddUserRegistrationOrganizationAsync(
model.DeviceIds.Select(d => Prefix(d)), Prefix(model.OrganizationId));
}
[HttpPut("delete-organization")]
public async Task PutDeleteOrganization([FromBody] PushUpdateRequestModel model)
{
CheckUsage();
await _pushRegistrationService.DeleteUserRegistrationOrganizationAsync(
model.DeviceIds.Select(d => Prefix(d)), Prefix(model.OrganizationId));
}
[HttpPost("send")]
public async Task PostSend([FromBody] PushSendRequestModel model)
{
CheckUsage();
if (!string.IsNullOrWhiteSpace(model.UserId))
public PushController(
IPushRegistrationService pushRegistrationService,
IPushNotificationService pushNotificationService,
IWebHostEnvironment environment,
ICurrentContext currentContext,
GlobalSettings globalSettings)
{
await _pushNotificationService.SendPayloadToUserAsync(Prefix(model.UserId),
model.Type.Value, model.Payload, Prefix(model.Identifier), Prefix(model.DeviceId));
}
else if (!string.IsNullOrWhiteSpace(model.OrganizationId))
{
await _pushNotificationService.SendPayloadToOrganizationAsync(Prefix(model.OrganizationId),
model.Type.Value, model.Payload, Prefix(model.Identifier), Prefix(model.DeviceId));
}
}
private string Prefix(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return null;
_currentContext = currentContext;
_environment = environment;
_pushRegistrationService = pushRegistrationService;
_pushNotificationService = pushNotificationService;
_globalSettings = globalSettings;
}
return $"{_currentContext.InstallationId.Value}_{value}";
}
private void CheckUsage()
{
if (CanUse())
[HttpPost("register")]
public async Task PostRegister([FromBody] PushRegistrationRequestModel model)
{
return;
CheckUsage();
await _pushRegistrationService.CreateOrUpdateRegistrationAsync(model.PushToken, Prefix(model.DeviceId),
Prefix(model.UserId), Prefix(model.Identifier), model.Type);
}
throw new BadRequestException("Not correctly configured for push relays.");
}
private bool CanUse()
{
if (_environment.IsDevelopment())
[HttpDelete("{id}")]
public async Task Delete(string id)
{
return true;
CheckUsage();
await _pushRegistrationService.DeleteRegistrationAsync(Prefix(id));
}
return _currentContext.InstallationId.HasValue && !_globalSettings.SelfHosted;
[HttpPut("add-organization")]
public async Task PutAddOrganization([FromBody] PushUpdateRequestModel model)
{
CheckUsage();
await _pushRegistrationService.AddUserRegistrationOrganizationAsync(
model.DeviceIds.Select(d => Prefix(d)), Prefix(model.OrganizationId));
}
[HttpPut("delete-organization")]
public async Task PutDeleteOrganization([FromBody] PushUpdateRequestModel model)
{
CheckUsage();
await _pushRegistrationService.DeleteUserRegistrationOrganizationAsync(
model.DeviceIds.Select(d => Prefix(d)), Prefix(model.OrganizationId));
}
[HttpPost("send")]
public async Task PostSend([FromBody] PushSendRequestModel model)
{
CheckUsage();
if (!string.IsNullOrWhiteSpace(model.UserId))
{
await _pushNotificationService.SendPayloadToUserAsync(Prefix(model.UserId),
model.Type.Value, model.Payload, Prefix(model.Identifier), Prefix(model.DeviceId));
}
else if (!string.IsNullOrWhiteSpace(model.OrganizationId))
{
await _pushNotificationService.SendPayloadToOrganizationAsync(Prefix(model.OrganizationId),
model.Type.Value, model.Payload, Prefix(model.Identifier), Prefix(model.DeviceId));
}
}
private string Prefix(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
return $"{_currentContext.InstallationId.Value}_{value}";
}
private void CheckUsage()
{
if (CanUse())
{
return;
}
throw new BadRequestException("Not correctly configured for push relays.");
}
private bool CanUse()
{
if (_environment.IsDevelopment())
{
return true;
}
return _currentContext.InstallationId.HasValue && !_globalSettings.SelfHosted;
}
}
}

View File

@ -7,60 +7,61 @@ using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers.SelfHosted;
[Route("organization/sponsorship/self-hosted")]
[Authorize("Application")]
[SelfHosted(SelfHostedOnly = true)]
public class SelfHostedOrganizationSponsorshipsController : Controller
namespace Bit.Api.Controllers.SelfHosted
{
private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IOrganizationSponsorshipRepository _organizationSponsorshipRepository;
private readonly ICreateSponsorshipCommand _offerSponsorshipCommand;
private readonly IRevokeSponsorshipCommand _revokeSponsorshipCommand;
private readonly ICurrentContext _currentContext;
public SelfHostedOrganizationSponsorshipsController(
ICreateSponsorshipCommand offerSponsorshipCommand,
IRevokeSponsorshipCommand revokeSponsorshipCommand,
IOrganizationRepository organizationRepository,
IOrganizationSponsorshipRepository organizationSponsorshipRepository,
IOrganizationUserRepository organizationUserRepository,
ICurrentContext currentContext
)
[Route("organization/sponsorship/self-hosted")]
[Authorize("Application")]
[SelfHosted(SelfHostedOnly = true)]
public class SelfHostedOrganizationSponsorshipsController : Controller
{
_offerSponsorshipCommand = offerSponsorshipCommand;
_revokeSponsorshipCommand = revokeSponsorshipCommand;
_organizationRepository = organizationRepository;
_organizationSponsorshipRepository = organizationSponsorshipRepository;
_organizationUserRepository = organizationUserRepository;
_currentContext = currentContext;
}
private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IOrganizationSponsorshipRepository _organizationSponsorshipRepository;
private readonly ICreateSponsorshipCommand _offerSponsorshipCommand;
private readonly IRevokeSponsorshipCommand _revokeSponsorshipCommand;
private readonly ICurrentContext _currentContext;
[HttpPost("{sponsoringOrgId}/families-for-enterprise")]
public async Task CreateSponsorship(Guid sponsoringOrgId, [FromBody] OrganizationSponsorshipCreateRequestModel model)
{
await _offerSponsorshipCommand.CreateSponsorshipAsync(
await _organizationRepository.GetByIdAsync(sponsoringOrgId),
await _organizationUserRepository.GetByOrganizationAsync(sponsoringOrgId, _currentContext.UserId ?? default),
model.PlanSponsorshipType, model.SponsoredEmail, model.FriendlyName);
}
[HttpDelete("{sponsoringOrgId}")]
[HttpPost("{sponsoringOrgId}/delete")]
public async Task RevokeSponsorship(Guid sponsoringOrgId)
{
var orgUser = await _organizationUserRepository.GetByOrganizationAsync(sponsoringOrgId, _currentContext.UserId ?? default);
if (orgUser == null)
public SelfHostedOrganizationSponsorshipsController(
ICreateSponsorshipCommand offerSponsorshipCommand,
IRevokeSponsorshipCommand revokeSponsorshipCommand,
IOrganizationRepository organizationRepository,
IOrganizationSponsorshipRepository organizationSponsorshipRepository,
IOrganizationUserRepository organizationUserRepository,
ICurrentContext currentContext
)
{
throw new BadRequestException("Unknown Organization User");
_offerSponsorshipCommand = offerSponsorshipCommand;
_revokeSponsorshipCommand = revokeSponsorshipCommand;
_organizationRepository = organizationRepository;
_organizationSponsorshipRepository = organizationSponsorshipRepository;
_organizationUserRepository = organizationUserRepository;
_currentContext = currentContext;
}
var existingOrgSponsorship = await _organizationSponsorshipRepository
.GetBySponsoringOrganizationUserIdAsync(orgUser.Id);
[HttpPost("{sponsoringOrgId}/families-for-enterprise")]
public async Task CreateSponsorship(Guid sponsoringOrgId, [FromBody] OrganizationSponsorshipCreateRequestModel model)
{
await _offerSponsorshipCommand.CreateSponsorshipAsync(
await _organizationRepository.GetByIdAsync(sponsoringOrgId),
await _organizationUserRepository.GetByOrganizationAsync(sponsoringOrgId, _currentContext.UserId ?? default),
model.PlanSponsorshipType, model.SponsoredEmail, model.FriendlyName);
}
await _revokeSponsorshipCommand.RevokeSponsorshipAsync(existingOrgSponsorship);
[HttpDelete("{sponsoringOrgId}")]
[HttpPost("{sponsoringOrgId}/delete")]
public async Task RevokeSponsorship(Guid sponsoringOrgId)
{
var orgUser = await _organizationUserRepository.GetByOrganizationAsync(sponsoringOrgId, _currentContext.UserId ?? default);
if (orgUser == null)
{
throw new BadRequestException("Unknown Organization User");
}
var existingOrgSponsorship = await _organizationSponsorshipRepository
.GetBySponsoringOrganizationUserIdAsync(orgUser.Id);
await _revokeSponsorshipCommand.RevokeSponsorshipAsync(existingOrgSponsorship);
}
}
}

View File

@ -16,322 +16,323 @@ using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Route("sends")]
[Authorize("Application")]
public class SendsController : Controller
namespace Bit.Api.Controllers
{
private readonly ISendRepository _sendRepository;
private readonly IUserService _userService;
private readonly ISendService _sendService;
private readonly ISendFileStorageService _sendFileStorageService;
private readonly ILogger<SendsController> _logger;
private readonly GlobalSettings _globalSettings;
private readonly ICurrentContext _currentContext;
public SendsController(
ISendRepository sendRepository,
IUserService userService,
ISendService sendService,
ISendFileStorageService sendFileStorageService,
ILogger<SendsController> logger,
GlobalSettings globalSettings,
ICurrentContext currentContext)
[Route("sends")]
[Authorize("Application")]
public class SendsController : Controller
{
_sendRepository = sendRepository;
_userService = userService;
_sendService = sendService;
_sendFileStorageService = sendFileStorageService;
_logger = logger;
_globalSettings = globalSettings;
_currentContext = currentContext;
}
private readonly ISendRepository _sendRepository;
private readonly IUserService _userService;
private readonly ISendService _sendService;
private readonly ISendFileStorageService _sendFileStorageService;
private readonly ILogger<SendsController> _logger;
private readonly GlobalSettings _globalSettings;
private readonly ICurrentContext _currentContext;
[AllowAnonymous]
[HttpPost("access/{id}")]
public async Task<IActionResult> Access(string id, [FromBody] SendAccessRequestModel model)
{
// Uncomment whenever we want to require the `send-id` header
//if (!_currentContext.HttpContext.Request.Headers.ContainsKey("Send-Id") ||
// _currentContext.HttpContext.Request.Headers["Send-Id"] != id)
//{
// throw new BadRequestException("Invalid Send-Id header.");
//}
var guid = new Guid(CoreHelpers.Base64UrlDecode(id));
var (send, passwordRequired, passwordInvalid) =
await _sendService.AccessAsync(guid, model.Password);
if (passwordRequired)
public SendsController(
ISendRepository sendRepository,
IUserService userService,
ISendService sendService,
ISendFileStorageService sendFileStorageService,
ILogger<SendsController> logger,
GlobalSettings globalSettings,
ICurrentContext currentContext)
{
return new UnauthorizedResult();
}
if (passwordInvalid)
{
await Task.Delay(2000);
throw new BadRequestException("Invalid password.");
}
if (send == null)
{
throw new NotFoundException();
_sendRepository = sendRepository;
_userService = userService;
_sendService = sendService;
_sendFileStorageService = sendFileStorageService;
_logger = logger;
_globalSettings = globalSettings;
_currentContext = currentContext;
}
var sendResponse = new SendAccessResponseModel(send, _globalSettings);
if (send.UserId.HasValue && !send.HideEmail.GetValueOrDefault())
[AllowAnonymous]
[HttpPost("access/{id}")]
public async Task<IActionResult> Access(string id, [FromBody] SendAccessRequestModel model)
{
var creator = await _userService.GetUserByIdAsync(send.UserId.Value);
sendResponse.CreatorIdentifier = creator.Email;
}
return new ObjectResult(sendResponse);
}
// Uncomment whenever we want to require the `send-id` header
//if (!_currentContext.HttpContext.Request.Headers.ContainsKey("Send-Id") ||
// _currentContext.HttpContext.Request.Headers["Send-Id"] != id)
//{
// throw new BadRequestException("Invalid Send-Id header.");
//}
[AllowAnonymous]
[HttpPost("{encodedSendId}/access/file/{fileId}")]
public async Task<IActionResult> GetSendFileDownloadData(string encodedSendId,
string fileId, [FromBody] SendAccessRequestModel model)
{
// Uncomment whenever we want to require the `send-id` header
//if (!_currentContext.HttpContext.Request.Headers.ContainsKey("Send-Id") ||
// _currentContext.HttpContext.Request.Headers["Send-Id"] != encodedSendId)
//{
// throw new BadRequestException("Invalid Send-Id header.");
//}
var guid = new Guid(CoreHelpers.Base64UrlDecode(id));
var (send, passwordRequired, passwordInvalid) =
await _sendService.AccessAsync(guid, model.Password);
if (passwordRequired)
{
return new UnauthorizedResult();
}
if (passwordInvalid)
{
await Task.Delay(2000);
throw new BadRequestException("Invalid password.");
}
if (send == null)
{
throw new NotFoundException();
}
var sendId = new Guid(CoreHelpers.Base64UrlDecode(encodedSendId));
var send = await _sendRepository.GetByIdAsync(sendId);
if (send == null)
{
throw new BadRequestException("Could not locate send");
var sendResponse = new SendAccessResponseModel(send, _globalSettings);
if (send.UserId.HasValue && !send.HideEmail.GetValueOrDefault())
{
var creator = await _userService.GetUserByIdAsync(send.UserId.Value);
sendResponse.CreatorIdentifier = creator.Email;
}
return new ObjectResult(sendResponse);
}
var (url, passwordRequired, passwordInvalid) = await _sendService.GetSendFileDownloadUrlAsync(send, fileId,
model.Password);
[AllowAnonymous]
[HttpPost("{encodedSendId}/access/file/{fileId}")]
public async Task<IActionResult> GetSendFileDownloadData(string encodedSendId,
string fileId, [FromBody] SendAccessRequestModel model)
{
// Uncomment whenever we want to require the `send-id` header
//if (!_currentContext.HttpContext.Request.Headers.ContainsKey("Send-Id") ||
// _currentContext.HttpContext.Request.Headers["Send-Id"] != encodedSendId)
//{
// throw new BadRequestException("Invalid Send-Id header.");
//}
if (passwordRequired)
{
return new UnauthorizedResult();
}
if (passwordInvalid)
{
await Task.Delay(2000);
throw new BadRequestException("Invalid password.");
}
if (send == null)
{
throw new NotFoundException();
var sendId = new Guid(CoreHelpers.Base64UrlDecode(encodedSendId));
var send = await _sendRepository.GetByIdAsync(sendId);
if (send == null)
{
throw new BadRequestException("Could not locate send");
}
var (url, passwordRequired, passwordInvalid) = await _sendService.GetSendFileDownloadUrlAsync(send, fileId,
model.Password);
if (passwordRequired)
{
return new UnauthorizedResult();
}
if (passwordInvalid)
{
await Task.Delay(2000);
throw new BadRequestException("Invalid password.");
}
if (send == null)
{
throw new NotFoundException();
}
return new ObjectResult(new SendFileDownloadDataResponseModel()
{
Id = fileId,
Url = url,
});
}
return new ObjectResult(new SendFileDownloadDataResponseModel()
[HttpGet("{id}")]
public async Task<SendResponseModel> Get(string id)
{
Id = fileId,
Url = url,
});
}
var userId = _userService.GetProperUserId(User).Value;
var send = await _sendRepository.GetByIdAsync(new Guid(id));
if (send == null || send.UserId != userId)
{
throw new NotFoundException();
}
[HttpGet("{id}")]
public async Task<SendResponseModel> Get(string id)
{
var userId = _userService.GetProperUserId(User).Value;
var send = await _sendRepository.GetByIdAsync(new Guid(id));
if (send == null || send.UserId != userId)
{
throw new NotFoundException();
return new SendResponseModel(send, _globalSettings);
}
return new SendResponseModel(send, _globalSettings);
}
[HttpGet("")]
public async Task<ListResponseModel<SendResponseModel>> Get()
{
var userId = _userService.GetProperUserId(User).Value;
var sends = await _sendRepository.GetManyByUserIdAsync(userId);
var responses = sends.Select(s => new SendResponseModel(s, _globalSettings));
return new ListResponseModel<SendResponseModel>(responses);
}
[HttpPost("")]
public async Task<SendResponseModel> Post([FromBody] SendRequestModel model)
{
model.ValidateCreation();
var userId = _userService.GetProperUserId(User).Value;
var send = model.ToSend(userId, _sendService);
await _sendService.SaveSendAsync(send);
return new SendResponseModel(send, _globalSettings);
}
[HttpPost("file")]
[Obsolete("Deprecated File Send API", false)]
[RequestSizeLimit(Constants.FileSize101mb)]
[DisableFormValueModelBinding]
public async Task<SendResponseModel> PostFile()
{
if (!Request?.ContentType.Contains("multipart/") ?? true)
[HttpGet("")]
public async Task<ListResponseModel<SendResponseModel>> Get()
{
throw new BadRequestException("Invalid content.");
var userId = _userService.GetProperUserId(User).Value;
var sends = await _sendRepository.GetManyByUserIdAsync(userId);
var responses = sends.Select(s => new SendResponseModel(s, _globalSettings));
return new ListResponseModel<SendResponseModel>(responses);
}
Send send = null;
await Request.GetSendFileAsync(async (stream, fileName, model) =>
[HttpPost("")]
public async Task<SendResponseModel> Post([FromBody] SendRequestModel model)
{
model.ValidateCreation();
var userId = _userService.GetProperUserId(User).Value;
var (madeSend, madeData) = model.ToSend(userId, fileName, _sendService);
send = madeSend;
await _sendService.SaveFileSendAsync(send, madeData, model.FileLength.GetValueOrDefault(0));
await _sendService.UploadFileToExistingSendAsync(stream, send);
});
return new SendResponseModel(send, _globalSettings);
}
[HttpPost("file/v2")]
public async Task<SendFileUploadDataResponseModel> PostFile([FromBody] SendRequestModel model)
{
if (model.Type != SendType.File)
{
throw new BadRequestException("Invalid content.");
var send = model.ToSend(userId, _sendService);
await _sendService.SaveSendAsync(send);
return new SendResponseModel(send, _globalSettings);
}
if (!model.FileLength.HasValue)
{
throw new BadRequestException("Invalid content. File size hint is required.");
}
if (model.FileLength.Value > SendService.MAX_FILE_SIZE)
{
throw new BadRequestException($"Max file size is {SendService.MAX_FILE_SIZE_READABLE}.");
}
var userId = _userService.GetProperUserId(User).Value;
var (send, data) = model.ToSend(userId, model.File.FileName, _sendService);
var uploadUrl = await _sendService.SaveFileSendAsync(send, data, model.FileLength.Value);
return new SendFileUploadDataResponseModel
{
Url = uploadUrl,
FileUploadType = _sendFileStorageService.FileUploadType,
SendResponse = new SendResponseModel(send, _globalSettings)
};
}
[HttpGet("{id}/file/{fileId}")]
public async Task<SendFileUploadDataResponseModel> RenewFileUpload(string id, string fileId)
{
var userId = _userService.GetProperUserId(User).Value;
var sendId = new Guid(id);
var send = await _sendRepository.GetByIdAsync(sendId);
var fileData = JsonSerializer.Deserialize<SendFileData>(send?.Data);
if (send == null || send.Type != SendType.File || (send.UserId.HasValue && send.UserId.Value != userId) ||
!send.UserId.HasValue || fileData.Id != fileId || fileData.Validated)
{
// Not found if Send isn't found, user doesn't have access, request is faulty,
// or we've already validated the file. This last is to emulate create-only blob permissions for Azure
throw new NotFoundException();
}
return new SendFileUploadDataResponseModel
{
Url = await _sendFileStorageService.GetSendFileUploadUrlAsync(send, fileId),
FileUploadType = _sendFileStorageService.FileUploadType,
SendResponse = new SendResponseModel(send, _globalSettings),
};
}
[HttpPost("{id}/file/{fileId}")]
[SelfHosted(SelfHostedOnly = true)]
[RequestSizeLimit(Constants.FileSize501mb)]
[DisableFormValueModelBinding]
public async Task PostFileForExistingSend(string id, string fileId)
{
if (!Request?.ContentType.Contains("multipart/") ?? true)
{
throw new BadRequestException("Invalid content.");
}
var send = await _sendRepository.GetByIdAsync(new Guid(id));
await Request.GetFileAsync(async (stream) =>
{
await _sendService.UploadFileToExistingSendAsync(stream, send);
});
}
[AllowAnonymous]
[HttpPost("file/validate/azure")]
public async Task<ObjectResult> AzureValidateFile()
{
return await ApiHelpers.HandleAzureEvents(Request, new Dictionary<string, Func<EventGridEvent, Task>>
[HttpPost("file")]
[Obsolete("Deprecated File Send API", false)]
[RequestSizeLimit(Constants.FileSize101mb)]
[DisableFormValueModelBinding]
public async Task<SendResponseModel> PostFile()
{
if (!Request?.ContentType.Contains("multipart/") ?? true)
{
throw new BadRequestException("Invalid content.");
}
Send send = null;
await Request.GetSendFileAsync(async (stream, fileName, model) =>
{
model.ValidateCreation();
var userId = _userService.GetProperUserId(User).Value;
var (madeSend, madeData) = model.ToSend(userId, fileName, _sendService);
send = madeSend;
await _sendService.SaveFileSendAsync(send, madeData, model.FileLength.GetValueOrDefault(0));
await _sendService.UploadFileToExistingSendAsync(stream, send);
});
return new SendResponseModel(send, _globalSettings);
}
[HttpPost("file/v2")]
public async Task<SendFileUploadDataResponseModel> PostFile([FromBody] SendRequestModel model)
{
if (model.Type != SendType.File)
{
throw new BadRequestException("Invalid content.");
}
if (!model.FileLength.HasValue)
{
throw new BadRequestException("Invalid content. File size hint is required.");
}
if (model.FileLength.Value > SendService.MAX_FILE_SIZE)
{
throw new BadRequestException($"Max file size is {SendService.MAX_FILE_SIZE_READABLE}.");
}
var userId = _userService.GetProperUserId(User).Value;
var (send, data) = model.ToSend(userId, model.File.FileName, _sendService);
var uploadUrl = await _sendService.SaveFileSendAsync(send, data, model.FileLength.Value);
return new SendFileUploadDataResponseModel
{
Url = uploadUrl,
FileUploadType = _sendFileStorageService.FileUploadType,
SendResponse = new SendResponseModel(send, _globalSettings)
};
}
[HttpGet("{id}/file/{fileId}")]
public async Task<SendFileUploadDataResponseModel> RenewFileUpload(string id, string fileId)
{
var userId = _userService.GetProperUserId(User).Value;
var sendId = new Guid(id);
var send = await _sendRepository.GetByIdAsync(sendId);
var fileData = JsonSerializer.Deserialize<SendFileData>(send?.Data);
if (send == null || send.Type != SendType.File || (send.UserId.HasValue && send.UserId.Value != userId) ||
!send.UserId.HasValue || fileData.Id != fileId || fileData.Validated)
{
// Not found if Send isn't found, user doesn't have access, request is faulty,
// or we've already validated the file. This last is to emulate create-only blob permissions for Azure
throw new NotFoundException();
}
return new SendFileUploadDataResponseModel
{
Url = await _sendFileStorageService.GetSendFileUploadUrlAsync(send, fileId),
FileUploadType = _sendFileStorageService.FileUploadType,
SendResponse = new SendResponseModel(send, _globalSettings),
};
}
[HttpPost("{id}/file/{fileId}")]
[SelfHosted(SelfHostedOnly = true)]
[RequestSizeLimit(Constants.FileSize501mb)]
[DisableFormValueModelBinding]
public async Task PostFileForExistingSend(string id, string fileId)
{
if (!Request?.ContentType.Contains("multipart/") ?? true)
{
throw new BadRequestException("Invalid content.");
}
var send = await _sendRepository.GetByIdAsync(new Guid(id));
await Request.GetFileAsync(async (stream) =>
{
await _sendService.UploadFileToExistingSendAsync(stream, send);
});
}
[AllowAnonymous]
[HttpPost("file/validate/azure")]
public async Task<ObjectResult> AzureValidateFile()
{
return await ApiHelpers.HandleAzureEvents(Request, new Dictionary<string, Func<EventGridEvent, Task>>
{
"Microsoft.Storage.BlobCreated", async (eventGridEvent) =>
{
try
"Microsoft.Storage.BlobCreated", async (eventGridEvent) =>
{
var blobName = eventGridEvent.Subject.Split($"{AzureSendFileStorageService.FilesContainerName}/blobs/")[1];
var sendId = AzureSendFileStorageService.SendIdFromBlobName(blobName);
var send = await _sendRepository.GetByIdAsync(new Guid(sendId));
if (send == null)
try
{
if (_sendFileStorageService is AzureSendFileStorageService azureSendFileStorageService)
var blobName = eventGridEvent.Subject.Split($"{AzureSendFileStorageService.FilesContainerName}/blobs/")[1];
var sendId = AzureSendFileStorageService.SendIdFromBlobName(blobName);
var send = await _sendRepository.GetByIdAsync(new Guid(sendId));
if (send == null)
{
await azureSendFileStorageService.DeleteBlobAsync(blobName);
if (_sendFileStorageService is AzureSendFileStorageService azureSendFileStorageService)
{
await azureSendFileStorageService.DeleteBlobAsync(blobName);
}
return;
}
await _sendService.ValidateSendFile(send);
}
catch (Exception e)
{
_logger.LogError(e, $"Uncaught exception occurred while handling event grid event: {JsonSerializer.Serialize(eventGridEvent)}");
return;
}
await _sendService.ValidateSendFile(send);
}
catch (Exception e)
{
_logger.LogError(e, $"Uncaught exception occurred while handling event grid event: {JsonSerializer.Serialize(eventGridEvent)}");
return;
}
}
});
}
[HttpPut("{id}")]
public async Task<SendResponseModel> Put(string id, [FromBody] SendRequestModel model)
{
model.ValidateEdit();
var userId = _userService.GetProperUserId(User).Value;
var send = await _sendRepository.GetByIdAsync(new Guid(id));
if (send == null || send.UserId != userId)
{
throw new NotFoundException();
}
});
}
[HttpPut("{id}")]
public async Task<SendResponseModel> Put(string id, [FromBody] SendRequestModel model)
{
model.ValidateEdit();
var userId = _userService.GetProperUserId(User).Value;
var send = await _sendRepository.GetByIdAsync(new Guid(id));
if (send == null || send.UserId != userId)
{
throw new NotFoundException();
await _sendService.SaveSendAsync(model.ToSend(send, _sendService));
return new SendResponseModel(send, _globalSettings);
}
await _sendService.SaveSendAsync(model.ToSend(send, _sendService));
return new SendResponseModel(send, _globalSettings);
}
[HttpPut("{id}/remove-password")]
public async Task<SendResponseModel> PutRemovePassword(string id)
{
var userId = _userService.GetProperUserId(User).Value;
var send = await _sendRepository.GetByIdAsync(new Guid(id));
if (send == null || send.UserId != userId)
[HttpPut("{id}/remove-password")]
public async Task<SendResponseModel> PutRemovePassword(string id)
{
throw new NotFoundException();
var userId = _userService.GetProperUserId(User).Value;
var send = await _sendRepository.GetByIdAsync(new Guid(id));
if (send == null || send.UserId != userId)
{
throw new NotFoundException();
}
send.Password = null;
await _sendService.SaveSendAsync(send);
return new SendResponseModel(send, _globalSettings);
}
send.Password = null;
await _sendService.SaveSendAsync(send);
return new SendResponseModel(send, _globalSettings);
}
[HttpDelete("{id}")]
public async Task Delete(string id)
{
var userId = _userService.GetProperUserId(User).Value;
var send = await _sendRepository.GetByIdAsync(new Guid(id));
if (send == null || send.UserId != userId)
[HttpDelete("{id}")]
public async Task Delete(string id)
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User).Value;
var send = await _sendRepository.GetByIdAsync(new Guid(id));
if (send == null || send.UserId != userId)
{
throw new NotFoundException();
}
await _sendService.DeleteSendAsync(send);
await _sendService.DeleteSendAsync(send);
}
}
}

View File

@ -4,46 +4,47 @@ using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Route("settings")]
[Authorize("Application")]
public class SettingsController : Controller
namespace Bit.Api.Controllers
{
private readonly IUserService _userService;
public SettingsController(
IUserService userService)
[Route("settings")]
[Authorize("Application")]
public class SettingsController : Controller
{
_userService = userService;
}
private readonly IUserService _userService;
[HttpGet("domains")]
public async Task<DomainsResponseModel> GetDomains(bool excluded = true)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
public SettingsController(
IUserService userService)
{
throw new UnauthorizedAccessException();
_userService = userService;
}
var response = new DomainsResponseModel(user, excluded);
return response;
}
[HttpPut("domains")]
[HttpPost("domains")]
public async Task<DomainsResponseModel> PutDomains([FromBody] UpdateDomainsRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
[HttpGet("domains")]
public async Task<DomainsResponseModel> GetDomains(bool excluded = true)
{
throw new UnauthorizedAccessException();
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
var response = new DomainsResponseModel(user, excluded);
return response;
}
await _userService.SaveUserAsync(model.ToUser(user), true);
[HttpPut("domains")]
[HttpPost("domains")]
public async Task<DomainsResponseModel> PutDomains([FromBody] UpdateDomainsRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
var response = new DomainsResponseModel(user);
return response;
await _userService.SaveUserAsync(model.ToUser(user), true);
var response = new DomainsResponseModel(user);
return response;
}
}
}

View File

@ -10,84 +10,85 @@ using Bit.Core.Settings;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Route("sync")]
[Authorize("Application")]
public class SyncController : Controller
namespace Bit.Api.Controllers
{
private readonly IUserService _userService;
private readonly IFolderRepository _folderRepository;
private readonly ICipherRepository _cipherRepository;
private readonly ICollectionRepository _collectionRepository;
private readonly ICollectionCipherRepository _collectionCipherRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IProviderUserRepository _providerUserRepository;
private readonly IPolicyRepository _policyRepository;
private readonly ISendRepository _sendRepository;
private readonly GlobalSettings _globalSettings;
public SyncController(
IUserService userService,
IFolderRepository folderRepository,
ICipherRepository cipherRepository,
ICollectionRepository collectionRepository,
ICollectionCipherRepository collectionCipherRepository,
IOrganizationUserRepository organizationUserRepository,
IProviderUserRepository providerUserRepository,
IPolicyRepository policyRepository,
ISendRepository sendRepository,
GlobalSettings globalSettings)
[Route("sync")]
[Authorize("Application")]
public class SyncController : Controller
{
_userService = userService;
_folderRepository = folderRepository;
_cipherRepository = cipherRepository;
_collectionRepository = collectionRepository;
_collectionCipherRepository = collectionCipherRepository;
_organizationUserRepository = organizationUserRepository;
_providerUserRepository = providerUserRepository;
_policyRepository = policyRepository;
_sendRepository = sendRepository;
_globalSettings = globalSettings;
}
private readonly IUserService _userService;
private readonly IFolderRepository _folderRepository;
private readonly ICipherRepository _cipherRepository;
private readonly ICollectionRepository _collectionRepository;
private readonly ICollectionCipherRepository _collectionCipherRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IProviderUserRepository _providerUserRepository;
private readonly IPolicyRepository _policyRepository;
private readonly ISendRepository _sendRepository;
private readonly GlobalSettings _globalSettings;
[HttpGet("")]
public async Task<SyncResponseModel> Get([FromQuery] bool excludeDomains = false)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
public SyncController(
IUserService userService,
IFolderRepository folderRepository,
ICipherRepository cipherRepository,
ICollectionRepository collectionRepository,
ICollectionCipherRepository collectionCipherRepository,
IOrganizationUserRepository organizationUserRepository,
IProviderUserRepository providerUserRepository,
IPolicyRepository policyRepository,
ISendRepository sendRepository,
GlobalSettings globalSettings)
{
throw new BadRequestException("User not found.");
_userService = userService;
_folderRepository = folderRepository;
_cipherRepository = cipherRepository;
_collectionRepository = collectionRepository;
_collectionCipherRepository = collectionCipherRepository;
_organizationUserRepository = organizationUserRepository;
_providerUserRepository = providerUserRepository;
_policyRepository = policyRepository;
_sendRepository = sendRepository;
_globalSettings = globalSettings;
}
var organizationUserDetails = await _organizationUserRepository.GetManyDetailsByUserAsync(user.Id,
OrganizationUserStatusType.Confirmed);
var providerUserDetails = await _providerUserRepository.GetManyDetailsByUserAsync(user.Id,
ProviderUserStatusType.Confirmed);
var providerUserOrganizationDetails =
await _providerUserRepository.GetManyOrganizationDetailsByUserAsync(user.Id,
[HttpGet("")]
public async Task<SyncResponseModel> Get([FromQuery] bool excludeDomains = false)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new BadRequestException("User not found.");
}
var organizationUserDetails = await _organizationUserRepository.GetManyDetailsByUserAsync(user.Id,
OrganizationUserStatusType.Confirmed);
var providerUserDetails = await _providerUserRepository.GetManyDetailsByUserAsync(user.Id,
ProviderUserStatusType.Confirmed);
var hasEnabledOrgs = organizationUserDetails.Any(o => o.Enabled);
var folders = await _folderRepository.GetManyByUserIdAsync(user.Id);
var ciphers = await _cipherRepository.GetManyByUserIdAsync(user.Id, hasEnabledOrgs);
var sends = await _sendRepository.GetManyByUserIdAsync(user.Id);
var providerUserOrganizationDetails =
await _providerUserRepository.GetManyOrganizationDetailsByUserAsync(user.Id,
ProviderUserStatusType.Confirmed);
var hasEnabledOrgs = organizationUserDetails.Any(o => o.Enabled);
var folders = await _folderRepository.GetManyByUserIdAsync(user.Id);
var ciphers = await _cipherRepository.GetManyByUserIdAsync(user.Id, hasEnabledOrgs);
var sends = await _sendRepository.GetManyByUserIdAsync(user.Id);
IEnumerable<CollectionDetails> collections = null;
IDictionary<Guid, IGrouping<Guid, CollectionCipher>> collectionCiphersGroupDict = null;
IEnumerable<Policy> policies = null;
if (hasEnabledOrgs)
{
collections = await _collectionRepository.GetManyByUserIdAsync(user.Id);
var collectionCiphers = await _collectionCipherRepository.GetManyByUserIdAsync(user.Id);
collectionCiphersGroupDict = collectionCiphers.GroupBy(c => c.CipherId).ToDictionary(s => s.Key);
policies = await _policyRepository.GetManyByUserIdAsync(user.Id);
IEnumerable<CollectionDetails> collections = null;
IDictionary<Guid, IGrouping<Guid, CollectionCipher>> collectionCiphersGroupDict = null;
IEnumerable<Policy> policies = null;
if (hasEnabledOrgs)
{
collections = await _collectionRepository.GetManyByUserIdAsync(user.Id);
var collectionCiphers = await _collectionCipherRepository.GetManyByUserIdAsync(user.Id);
collectionCiphersGroupDict = collectionCiphers.GroupBy(c => c.CipherId).ToDictionary(s => s.Key);
policies = await _policyRepository.GetManyByUserIdAsync(user.Id);
}
var userTwoFactorEnabled = await _userService.TwoFactorIsEnabledAsync(user);
var userHasPremiumFromOrganization = await _userService.HasPremiumFromOrganization(user);
var response = new SyncResponseModel(_globalSettings, user, userTwoFactorEnabled, userHasPremiumFromOrganization, organizationUserDetails,
providerUserDetails, providerUserOrganizationDetails, folders, collections, ciphers,
collectionCiphersGroupDict, excludeDomains, policies, sends);
return response;
}
var userTwoFactorEnabled = await _userService.TwoFactorIsEnabledAsync(user);
var userHasPremiumFromOrganization = await _userService.HasPremiumFromOrganization(user);
var response = new SyncResponseModel(_globalSettings, user, userTwoFactorEnabled, userHasPremiumFromOrganization, organizationUserDetails,
providerUserDetails, providerUserOrganizationDetails, folders, collections, ciphers,
collectionCiphersGroupDict, excludeDomains, policies, sends);
return response;
}
}

View File

@ -16,442 +16,443 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Route("two-factor")]
[Authorize("Web")]
public class TwoFactorController : Controller
namespace Bit.Api.Controllers
{
private readonly IUserService _userService;
private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationService _organizationService;
private readonly GlobalSettings _globalSettings;
private readonly UserManager<User> _userManager;
private readonly ICurrentContext _currentContext;
public TwoFactorController(
IUserService userService,
IOrganizationRepository organizationRepository,
IOrganizationService organizationService,
GlobalSettings globalSettings,
UserManager<User> userManager,
ICurrentContext currentContext)
[Route("two-factor")]
[Authorize("Web")]
public class TwoFactorController : Controller
{
_userService = userService;
_organizationRepository = organizationRepository;
_organizationService = organizationService;
_globalSettings = globalSettings;
_userManager = userManager;
_currentContext = currentContext;
}
private readonly IUserService _userService;
private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationService _organizationService;
private readonly GlobalSettings _globalSettings;
private readonly UserManager<User> _userManager;
private readonly ICurrentContext _currentContext;
[HttpGet("")]
public async Task<ListResponseModel<TwoFactorProviderResponseModel>> Get()
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
public TwoFactorController(
IUserService userService,
IOrganizationRepository organizationRepository,
IOrganizationService organizationService,
GlobalSettings globalSettings,
UserManager<User> userManager,
ICurrentContext currentContext)
{
throw new UnauthorizedAccessException();
_userService = userService;
_organizationRepository = organizationRepository;
_organizationService = organizationService;
_globalSettings = globalSettings;
_userManager = userManager;
_currentContext = currentContext;
}
var providers = user.GetTwoFactorProviders()?.Select(
p => new TwoFactorProviderResponseModel(p.Key, p.Value));
return new ListResponseModel<TwoFactorProviderResponseModel>(providers);
}
[HttpGet("~/organizations/{id}/two-factor")]
public async Task<ListResponseModel<TwoFactorProviderResponseModel>> GetOrganization(string id)
{
var orgIdGuid = new Guid(id);
if (!await _currentContext.OrganizationAdmin(orgIdGuid))
[HttpGet("")]
public async Task<ListResponseModel<TwoFactorProviderResponseModel>> Get()
{
throw new NotFoundException();
}
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
if (organization == null)
{
throw new NotFoundException();
}
var providers = organization.GetTwoFactorProviders()?.Select(
p => new TwoFactorProviderResponseModel(p.Key, p.Value));
return new ListResponseModel<TwoFactorProviderResponseModel>(providers);
}
[HttpPost("get-authenticator")]
public async Task<TwoFactorAuthenticatorResponseModel> GetAuthenticator([FromBody] SecretVerificationRequestModel model)
{
var user = await CheckAsync(model, false);
var response = new TwoFactorAuthenticatorResponseModel(user);
return response;
}
[HttpPut("authenticator")]
[HttpPost("authenticator")]
public async Task<TwoFactorAuthenticatorResponseModel> PutAuthenticator(
[FromBody] UpdateTwoFactorAuthenticatorRequestModel model)
{
var user = await CheckAsync(model, false);
model.ToUser(user);
if (!await _userManager.VerifyTwoFactorTokenAsync(user,
CoreHelpers.CustomProviderName(TwoFactorProviderType.Authenticator), model.Token))
{
await Task.Delay(2000);
throw new BadRequestException("Token", "Invalid token.");
}
await _userService.UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.Authenticator);
var response = new TwoFactorAuthenticatorResponseModel(user);
return response;
}
[HttpPost("get-yubikey")]
public async Task<TwoFactorYubiKeyResponseModel> GetYubiKey([FromBody] SecretVerificationRequestModel model)
{
var user = await CheckAsync(model, true);
var response = new TwoFactorYubiKeyResponseModel(user);
return response;
}
[HttpPut("yubikey")]
[HttpPost("yubikey")]
public async Task<TwoFactorYubiKeyResponseModel> PutYubiKey([FromBody] UpdateTwoFactorYubicoOtpRequestModel model)
{
var user = await CheckAsync(model, true);
model.ToUser(user);
await ValidateYubiKeyAsync(user, nameof(model.Key1), model.Key1);
await ValidateYubiKeyAsync(user, nameof(model.Key2), model.Key2);
await ValidateYubiKeyAsync(user, nameof(model.Key3), model.Key3);
await ValidateYubiKeyAsync(user, nameof(model.Key4), model.Key4);
await ValidateYubiKeyAsync(user, nameof(model.Key5), model.Key5);
await _userService.UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.YubiKey);
var response = new TwoFactorYubiKeyResponseModel(user);
return response;
}
[HttpPost("get-duo")]
public async Task<TwoFactorDuoResponseModel> GetDuo([FromBody] SecretVerificationRequestModel model)
{
var user = await CheckAsync(model, true);
var response = new TwoFactorDuoResponseModel(user);
return response;
}
[HttpPut("duo")]
[HttpPost("duo")]
public async Task<TwoFactorDuoResponseModel> PutDuo([FromBody] UpdateTwoFactorDuoRequestModel model)
{
var user = await CheckAsync(model, true);
try
{
var duoApi = new DuoApi(model.IntegrationKey, model.SecretKey, model.Host);
duoApi.JSONApiCall<object>("GET", "/auth/v2/check");
}
catch (DuoException)
{
throw new BadRequestException("Duo configuration settings are not valid. Please re-check the Duo Admin panel.");
}
model.ToUser(user);
await _userService.UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.Duo);
var response = new TwoFactorDuoResponseModel(user);
return response;
}
[HttpPost("~/organizations/{id}/two-factor/get-duo")]
public async Task<TwoFactorDuoResponseModel> GetOrganizationDuo(string id,
[FromBody] SecretVerificationRequestModel model)
{
var user = await CheckAsync(model, false);
var orgIdGuid = new Guid(id);
if (!await _currentContext.ManagePolicies(orgIdGuid))
{
throw new NotFoundException();
}
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
if (organization == null)
{
throw new NotFoundException();
}
var response = new TwoFactorDuoResponseModel(organization);
return response;
}
[HttpPut("~/organizations/{id}/two-factor/duo")]
[HttpPost("~/organizations/{id}/two-factor/duo")]
public async Task<TwoFactorDuoResponseModel> PutOrganizationDuo(string id,
[FromBody] UpdateTwoFactorDuoRequestModel model)
{
var user = await CheckAsync(model, false);
var orgIdGuid = new Guid(id);
if (!await _currentContext.ManagePolicies(orgIdGuid))
{
throw new NotFoundException();
}
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
if (organization == null)
{
throw new NotFoundException();
}
try
{
var duoApi = new DuoApi(model.IntegrationKey, model.SecretKey, model.Host);
duoApi.JSONApiCall<object>("GET", "/auth/v2/check");
}
catch (DuoException)
{
throw new BadRequestException("Duo configuration settings are not valid. Please re-check the Duo Admin panel.");
}
model.ToOrganization(organization);
await _organizationService.UpdateTwoFactorProviderAsync(organization,
TwoFactorProviderType.OrganizationDuo);
var response = new TwoFactorDuoResponseModel(organization);
return response;
}
[HttpPost("get-webauthn")]
public async Task<TwoFactorWebAuthnResponseModel> GetWebAuthn([FromBody] SecretVerificationRequestModel model)
{
var user = await CheckAsync(model, true);
var response = new TwoFactorWebAuthnResponseModel(user);
return response;
}
[HttpPost("get-webauthn-challenge")]
public async Task<CredentialCreateOptions> GetWebAuthnChallenge([FromBody] SecretVerificationRequestModel model)
{
var user = await CheckAsync(model, true);
var reg = await _userService.StartWebAuthnRegistrationAsync(user);
return reg;
}
[HttpPut("webauthn")]
[HttpPost("webauthn")]
public async Task<TwoFactorWebAuthnResponseModel> PutWebAuthn([FromBody] TwoFactorWebAuthnRequestModel model)
{
var user = await CheckAsync(model, true);
var success = await _userService.CompleteWebAuthRegistrationAsync(
user, model.Id.Value, model.Name, model.DeviceResponse);
if (!success)
{
throw new BadRequestException("Unable to complete WebAuthn registration.");
}
var response = new TwoFactorWebAuthnResponseModel(user);
return response;
}
[HttpDelete("webauthn")]
public async Task<TwoFactorWebAuthnResponseModel> DeleteWebAuthn([FromBody] TwoFactorWebAuthnDeleteRequestModel model)
{
var user = await CheckAsync(model, true);
await _userService.DeleteWebAuthnKeyAsync(user, model.Id.Value);
var response = new TwoFactorWebAuthnResponseModel(user);
return response;
}
[HttpPost("get-email")]
public async Task<TwoFactorEmailResponseModel> GetEmail([FromBody] SecretVerificationRequestModel model)
{
var user = await CheckAsync(model, false);
var response = new TwoFactorEmailResponseModel(user);
return response;
}
[HttpPost("send-email")]
public async Task SendEmail([FromBody] TwoFactorEmailRequestModel model)
{
var user = await CheckAsync(model, false);
model.ToUser(user);
await _userService.SendTwoFactorEmailAsync(user);
}
[AllowAnonymous]
[HttpPost("send-email-login")]
public async Task SendEmailLogin([FromBody] TwoFactorEmailRequestModel model)
{
var user = await _userManager.FindByEmailAsync(model.Email.ToLowerInvariant());
if (user != null)
{
if (await _userService.VerifySecretAsync(user, model.Secret))
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
var isBecauseNewDeviceLogin = false;
if (user.GetTwoFactorProvider(TwoFactorProviderType.Email) is null
&&
await _userService.Needs2FABecauseNewDeviceAsync(user, model.DeviceIdentifier, null))
{
model.ToUser(user);
isBecauseNewDeviceLogin = true;
}
throw new UnauthorizedAccessException();
}
await _userService.SendTwoFactorEmailAsync(user, isBecauseNewDeviceLogin);
return;
var providers = user.GetTwoFactorProviders()?.Select(
p => new TwoFactorProviderResponseModel(p.Key, p.Value));
return new ListResponseModel<TwoFactorProviderResponseModel>(providers);
}
[HttpGet("~/organizations/{id}/two-factor")]
public async Task<ListResponseModel<TwoFactorProviderResponseModel>> GetOrganization(string id)
{
var orgIdGuid = new Guid(id);
if (!await _currentContext.OrganizationAdmin(orgIdGuid))
{
throw new NotFoundException();
}
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
if (organization == null)
{
throw new NotFoundException();
}
var providers = organization.GetTwoFactorProviders()?.Select(
p => new TwoFactorProviderResponseModel(p.Key, p.Value));
return new ListResponseModel<TwoFactorProviderResponseModel>(providers);
}
[HttpPost("get-authenticator")]
public async Task<TwoFactorAuthenticatorResponseModel> GetAuthenticator([FromBody] SecretVerificationRequestModel model)
{
var user = await CheckAsync(model, false);
var response = new TwoFactorAuthenticatorResponseModel(user);
return response;
}
[HttpPut("authenticator")]
[HttpPost("authenticator")]
public async Task<TwoFactorAuthenticatorResponseModel> PutAuthenticator(
[FromBody] UpdateTwoFactorAuthenticatorRequestModel model)
{
var user = await CheckAsync(model, false);
model.ToUser(user);
if (!await _userManager.VerifyTwoFactorTokenAsync(user,
CoreHelpers.CustomProviderName(TwoFactorProviderType.Authenticator), model.Token))
{
await Task.Delay(2000);
throw new BadRequestException("Token", "Invalid token.");
}
await _userService.UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.Authenticator);
var response = new TwoFactorAuthenticatorResponseModel(user);
return response;
}
[HttpPost("get-yubikey")]
public async Task<TwoFactorYubiKeyResponseModel> GetYubiKey([FromBody] SecretVerificationRequestModel model)
{
var user = await CheckAsync(model, true);
var response = new TwoFactorYubiKeyResponseModel(user);
return response;
}
[HttpPut("yubikey")]
[HttpPost("yubikey")]
public async Task<TwoFactorYubiKeyResponseModel> PutYubiKey([FromBody] UpdateTwoFactorYubicoOtpRequestModel model)
{
var user = await CheckAsync(model, true);
model.ToUser(user);
await ValidateYubiKeyAsync(user, nameof(model.Key1), model.Key1);
await ValidateYubiKeyAsync(user, nameof(model.Key2), model.Key2);
await ValidateYubiKeyAsync(user, nameof(model.Key3), model.Key3);
await ValidateYubiKeyAsync(user, nameof(model.Key4), model.Key4);
await ValidateYubiKeyAsync(user, nameof(model.Key5), model.Key5);
await _userService.UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.YubiKey);
var response = new TwoFactorYubiKeyResponseModel(user);
return response;
}
[HttpPost("get-duo")]
public async Task<TwoFactorDuoResponseModel> GetDuo([FromBody] SecretVerificationRequestModel model)
{
var user = await CheckAsync(model, true);
var response = new TwoFactorDuoResponseModel(user);
return response;
}
[HttpPut("duo")]
[HttpPost("duo")]
public async Task<TwoFactorDuoResponseModel> PutDuo([FromBody] UpdateTwoFactorDuoRequestModel model)
{
var user = await CheckAsync(model, true);
try
{
var duoApi = new DuoApi(model.IntegrationKey, model.SecretKey, model.Host);
duoApi.JSONApiCall<object>("GET", "/auth/v2/check");
}
catch (DuoException)
{
throw new BadRequestException("Duo configuration settings are not valid. Please re-check the Duo Admin panel.");
}
model.ToUser(user);
await _userService.UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.Duo);
var response = new TwoFactorDuoResponseModel(user);
return response;
}
[HttpPost("~/organizations/{id}/two-factor/get-duo")]
public async Task<TwoFactorDuoResponseModel> GetOrganizationDuo(string id,
[FromBody] SecretVerificationRequestModel model)
{
var user = await CheckAsync(model, false);
var orgIdGuid = new Guid(id);
if (!await _currentContext.ManagePolicies(orgIdGuid))
{
throw new NotFoundException();
}
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
if (organization == null)
{
throw new NotFoundException();
}
var response = new TwoFactorDuoResponseModel(organization);
return response;
}
[HttpPut("~/organizations/{id}/two-factor/duo")]
[HttpPost("~/organizations/{id}/two-factor/duo")]
public async Task<TwoFactorDuoResponseModel> PutOrganizationDuo(string id,
[FromBody] UpdateTwoFactorDuoRequestModel model)
{
var user = await CheckAsync(model, false);
var orgIdGuid = new Guid(id);
if (!await _currentContext.ManagePolicies(orgIdGuid))
{
throw new NotFoundException();
}
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
if (organization == null)
{
throw new NotFoundException();
}
try
{
var duoApi = new DuoApi(model.IntegrationKey, model.SecretKey, model.Host);
duoApi.JSONApiCall<object>("GET", "/auth/v2/check");
}
catch (DuoException)
{
throw new BadRequestException("Duo configuration settings are not valid. Please re-check the Duo Admin panel.");
}
model.ToOrganization(organization);
await _organizationService.UpdateTwoFactorProviderAsync(organization,
TwoFactorProviderType.OrganizationDuo);
var response = new TwoFactorDuoResponseModel(organization);
return response;
}
[HttpPost("get-webauthn")]
public async Task<TwoFactorWebAuthnResponseModel> GetWebAuthn([FromBody] SecretVerificationRequestModel model)
{
var user = await CheckAsync(model, true);
var response = new TwoFactorWebAuthnResponseModel(user);
return response;
}
[HttpPost("get-webauthn-challenge")]
public async Task<CredentialCreateOptions> GetWebAuthnChallenge([FromBody] SecretVerificationRequestModel model)
{
var user = await CheckAsync(model, true);
var reg = await _userService.StartWebAuthnRegistrationAsync(user);
return reg;
}
[HttpPut("webauthn")]
[HttpPost("webauthn")]
public async Task<TwoFactorWebAuthnResponseModel> PutWebAuthn([FromBody] TwoFactorWebAuthnRequestModel model)
{
var user = await CheckAsync(model, true);
var success = await _userService.CompleteWebAuthRegistrationAsync(
user, model.Id.Value, model.Name, model.DeviceResponse);
if (!success)
{
throw new BadRequestException("Unable to complete WebAuthn registration.");
}
var response = new TwoFactorWebAuthnResponseModel(user);
return response;
}
[HttpDelete("webauthn")]
public async Task<TwoFactorWebAuthnResponseModel> DeleteWebAuthn([FromBody] TwoFactorWebAuthnDeleteRequestModel model)
{
var user = await CheckAsync(model, true);
await _userService.DeleteWebAuthnKeyAsync(user, model.Id.Value);
var response = new TwoFactorWebAuthnResponseModel(user);
return response;
}
[HttpPost("get-email")]
public async Task<TwoFactorEmailResponseModel> GetEmail([FromBody] SecretVerificationRequestModel model)
{
var user = await CheckAsync(model, false);
var response = new TwoFactorEmailResponseModel(user);
return response;
}
[HttpPost("send-email")]
public async Task SendEmail([FromBody] TwoFactorEmailRequestModel model)
{
var user = await CheckAsync(model, false);
model.ToUser(user);
await _userService.SendTwoFactorEmailAsync(user);
}
[AllowAnonymous]
[HttpPost("send-email-login")]
public async Task SendEmailLogin([FromBody] TwoFactorEmailRequestModel model)
{
var user = await _userManager.FindByEmailAsync(model.Email.ToLowerInvariant());
if (user != null)
{
if (await _userService.VerifySecretAsync(user, model.Secret))
{
var isBecauseNewDeviceLogin = false;
if (user.GetTwoFactorProvider(TwoFactorProviderType.Email) is null
&&
await _userService.Needs2FABecauseNewDeviceAsync(user, model.DeviceIdentifier, null))
{
model.ToUser(user);
isBecauseNewDeviceLogin = true;
}
await _userService.SendTwoFactorEmailAsync(user, isBecauseNewDeviceLogin);
return;
}
}
await Task.Delay(2000);
throw new BadRequestException("Cannot send two-factor email.");
}
[HttpPut("email")]
[HttpPost("email")]
public async Task<TwoFactorEmailResponseModel> PutEmail([FromBody] UpdateTwoFactorEmailRequestModel model)
{
var user = await CheckAsync(model, false);
model.ToUser(user);
if (!await _userManager.VerifyTwoFactorTokenAsync(user,
CoreHelpers.CustomProviderName(TwoFactorProviderType.Email), model.Token))
{
await Task.Delay(2000);
throw new BadRequestException("Token", "Invalid token.");
}
await _userService.UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.Email);
var response = new TwoFactorEmailResponseModel(user);
return response;
}
[HttpPut("disable")]
[HttpPost("disable")]
public async Task<TwoFactorProviderResponseModel> PutDisable([FromBody] TwoFactorProviderRequestModel model)
{
var user = await CheckAsync(model, false);
await _userService.DisableTwoFactorProviderAsync(user, model.Type.Value, _organizationService);
var response = new TwoFactorProviderResponseModel(model.Type.Value, user);
return response;
}
[HttpPut("~/organizations/{id}/two-factor/disable")]
[HttpPost("~/organizations/{id}/two-factor/disable")]
public async Task<TwoFactorProviderResponseModel> PutOrganizationDisable(string id,
[FromBody] TwoFactorProviderRequestModel model)
{
var user = await CheckAsync(model, false);
var orgIdGuid = new Guid(id);
if (!await _currentContext.ManagePolicies(orgIdGuid))
{
throw new NotFoundException();
}
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
if (organization == null)
{
throw new NotFoundException();
}
await _organizationService.DisableTwoFactorProviderAsync(organization, model.Type.Value);
var response = new TwoFactorProviderResponseModel(model.Type.Value, organization);
return response;
}
[HttpPost("get-recover")]
public async Task<TwoFactorRecoverResponseModel> GetRecover([FromBody] SecretVerificationRequestModel model)
{
var user = await CheckAsync(model, false);
var response = new TwoFactorRecoverResponseModel(user);
return response;
}
[HttpPost("recover")]
[AllowAnonymous]
public async Task PostRecover([FromBody] TwoFactorRecoveryRequestModel model)
{
if (!await _userService.RecoverTwoFactorAsync(model.Email, model.MasterPasswordHash, model.RecoveryCode,
_organizationService))
{
await Task.Delay(2000);
throw new BadRequestException(string.Empty, "Invalid information. Try again.");
}
}
await Task.Delay(2000);
throw new BadRequestException("Cannot send two-factor email.");
}
[HttpPut("email")]
[HttpPost("email")]
public async Task<TwoFactorEmailResponseModel> PutEmail([FromBody] UpdateTwoFactorEmailRequestModel model)
{
var user = await CheckAsync(model, false);
model.ToUser(user);
if (!await _userManager.VerifyTwoFactorTokenAsync(user,
CoreHelpers.CustomProviderName(TwoFactorProviderType.Email), model.Token))
[HttpGet("get-device-verification-settings")]
public async Task<DeviceVerificationResponseModel> GetDeviceVerificationSettings()
{
await Task.Delay(2000);
throw new BadRequestException("Token", "Invalid token.");
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
if (User.Claims.HasSsoIdP())
{
return new DeviceVerificationResponseModel(false, false);
}
var canUserEditDeviceVerificationSettings = _userService.CanEditDeviceVerificationSettings(user);
return new DeviceVerificationResponseModel(canUserEditDeviceVerificationSettings, canUserEditDeviceVerificationSettings && user.UnknownDeviceVerificationEnabled);
}
await _userService.UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.Email);
var response = new TwoFactorEmailResponseModel(user);
return response;
}
[HttpPut("disable")]
[HttpPost("disable")]
public async Task<TwoFactorProviderResponseModel> PutDisable([FromBody] TwoFactorProviderRequestModel model)
{
var user = await CheckAsync(model, false);
await _userService.DisableTwoFactorProviderAsync(user, model.Type.Value, _organizationService);
var response = new TwoFactorProviderResponseModel(model.Type.Value, user);
return response;
}
[HttpPut("~/organizations/{id}/two-factor/disable")]
[HttpPost("~/organizations/{id}/two-factor/disable")]
public async Task<TwoFactorProviderResponseModel> PutOrganizationDisable(string id,
[FromBody] TwoFactorProviderRequestModel model)
{
var user = await CheckAsync(model, false);
var orgIdGuid = new Guid(id);
if (!await _currentContext.ManagePolicies(orgIdGuid))
[HttpPut("device-verification-settings")]
public async Task<DeviceVerificationResponseModel> PutDeviceVerificationSettings([FromBody] DeviceVerificationRequestModel model)
{
throw new NotFoundException();
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
if (!_userService.CanEditDeviceVerificationSettings(user)
|| User.Claims.HasSsoIdP())
{
throw new InvalidOperationException("Can't update device verification settings");
}
model.ToUser(user);
await _userService.SaveUserAsync(user);
return new DeviceVerificationResponseModel(true, user.UnknownDeviceVerificationEnabled);
}
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
if (organization == null)
private async Task<User> CheckAsync(SecretVerificationRequestModel model, bool premium)
{
throw new NotFoundException();
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
if (!await _userService.VerifySecretAsync(user, model.Secret))
{
await Task.Delay(2000);
throw new BadRequestException(string.Empty, "User verification failed.");
}
if (premium && !(await _userService.CanAccessPremium(user)))
{
throw new BadRequestException("Premium status is required.");
}
return user;
}
await _organizationService.DisableTwoFactorProviderAsync(organization, model.Type.Value);
var response = new TwoFactorProviderResponseModel(model.Type.Value, organization);
return response;
}
[HttpPost("get-recover")]
public async Task<TwoFactorRecoverResponseModel> GetRecover([FromBody] SecretVerificationRequestModel model)
{
var user = await CheckAsync(model, false);
var response = new TwoFactorRecoverResponseModel(user);
return response;
}
[HttpPost("recover")]
[AllowAnonymous]
public async Task PostRecover([FromBody] TwoFactorRecoveryRequestModel model)
{
if (!await _userService.RecoverTwoFactorAsync(model.Email, model.MasterPasswordHash, model.RecoveryCode,
_organizationService))
private async Task ValidateYubiKeyAsync(User user, string name, string value)
{
await Task.Delay(2000);
throw new BadRequestException(string.Empty, "Invalid information. Try again.");
}
}
if (string.IsNullOrWhiteSpace(value) || value.Length == 12)
{
return;
}
[HttpGet("get-device-verification-settings")]
public async Task<DeviceVerificationResponseModel> GetDeviceVerificationSettings()
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
if (User.Claims.HasSsoIdP())
{
return new DeviceVerificationResponseModel(false, false);
}
var canUserEditDeviceVerificationSettings = _userService.CanEditDeviceVerificationSettings(user);
return new DeviceVerificationResponseModel(canUserEditDeviceVerificationSettings, canUserEditDeviceVerificationSettings && user.UnknownDeviceVerificationEnabled);
}
[HttpPut("device-verification-settings")]
public async Task<DeviceVerificationResponseModel> PutDeviceVerificationSettings([FromBody] DeviceVerificationRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
if (!_userService.CanEditDeviceVerificationSettings(user)
|| User.Claims.HasSsoIdP())
{
throw new InvalidOperationException("Can't update device verification settings");
}
model.ToUser(user);
await _userService.SaveUserAsync(user);
return new DeviceVerificationResponseModel(true, user.UnknownDeviceVerificationEnabled);
}
private async Task<User> CheckAsync(SecretVerificationRequestModel model, bool premium)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
if (!await _userService.VerifySecretAsync(user, model.Secret))
{
await Task.Delay(2000);
throw new BadRequestException(string.Empty, "User verification failed.");
}
if (premium && !(await _userService.CanAccessPremium(user)))
{
throw new BadRequestException("Premium status is required.");
}
return user;
}
private async Task ValidateYubiKeyAsync(User user, string name, string value)
{
if (string.IsNullOrWhiteSpace(value) || value.Length == 12)
{
return;
}
if (!await _userManager.VerifyTwoFactorTokenAsync(user,
CoreHelpers.CustomProviderName(TwoFactorProviderType.YubiKey), value))
{
await Task.Delay(2000);
throw new BadRequestException(name, $"{name} is invalid.");
}
else
{
await Task.Delay(500);
if (!await _userManager.VerifyTwoFactorTokenAsync(user,
CoreHelpers.CustomProviderName(TwoFactorProviderType.YubiKey), value))
{
await Task.Delay(2000);
throw new BadRequestException(name, $"{name} is invalid.");
}
else
{
await Task.Delay(500);
}
}
}
}

View File

@ -4,30 +4,31 @@ using Bit.Core.Repositories;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Route("users")]
[Authorize("Application")]
public class UsersController : Controller
namespace Bit.Api.Controllers
{
private readonly IUserRepository _userRepository;
public UsersController(
IUserRepository userRepository)
[Route("users")]
[Authorize("Application")]
public class UsersController : Controller
{
_userRepository = userRepository;
}
private readonly IUserRepository _userRepository;
[HttpGet("{id}/public-key")]
public async Task<UserKeyResponseModel> Get(string id)
{
var guidId = new Guid(id);
var key = await _userRepository.GetPublicKeyAsync(guidId);
if (key == null)
public UsersController(
IUserRepository userRepository)
{
throw new NotFoundException();
_userRepository = userRepository;
}
return new UserKeyResponseModel(guidId, key);
[HttpGet("{id}/public-key")]
public async Task<UserKeyResponseModel> Get(string id)
{
var guidId = new Guid(id);
var key = await _userRepository.GetPublicKeyAsync(guidId);
if (key == null)
{
throw new NotFoundException();
}
return new UserKeyResponseModel(guidId, key);
}
}
}

View File

@ -2,16 +2,17 @@
using Bit.Core.Jobs;
using Quartz;
namespace Bit.Api.Jobs;
public class AliveJob : BaseJob
namespace Bit.Api.Jobs
{
public AliveJob(ILogger<AliveJob> logger)
: base(logger) { }
protected override Task ExecuteJobAsync(IJobExecutionContext context)
public class AliveJob : BaseJob
{
_logger.LogInformation(Constants.BypassFiltersEventId, null, "It's alive!");
return Task.FromResult(0);
public AliveJob(ILogger<AliveJob> logger)
: base(logger) { }
protected override Task ExecuteJobAsync(IJobExecutionContext context)
{
_logger.LogInformation(Constants.BypassFiltersEventId, null, "It's alive!");
return Task.FromResult(0);
}
}
}

View File

@ -2,22 +2,23 @@
using Bit.Core.Services;
using Quartz;
namespace Bit.Api.Jobs;
public class EmergencyAccessNotificationJob : BaseJob
namespace Bit.Api.Jobs
{
private readonly IServiceScopeFactory _serviceScopeFactory;
public EmergencyAccessNotificationJob(IServiceScopeFactory serviceScopeFactory, ILogger<EmergencyAccessNotificationJob> logger)
: base(logger)
public class EmergencyAccessNotificationJob : BaseJob
{
_serviceScopeFactory = serviceScopeFactory;
}
private readonly IServiceScopeFactory _serviceScopeFactory;
protected override async Task ExecuteJobAsync(IJobExecutionContext context)
{
using var scope = _serviceScopeFactory.CreateScope();
var emergencyAccessService = scope.ServiceProvider.GetService(typeof(IEmergencyAccessService)) as IEmergencyAccessService;
await emergencyAccessService.SendNotificationsAsync();
public EmergencyAccessNotificationJob(IServiceScopeFactory serviceScopeFactory, ILogger<EmergencyAccessNotificationJob> logger)
: base(logger)
{
_serviceScopeFactory = serviceScopeFactory;
}
protected override async Task ExecuteJobAsync(IJobExecutionContext context)
{
using var scope = _serviceScopeFactory.CreateScope();
var emergencyAccessService = scope.ServiceProvider.GetService(typeof(IEmergencyAccessService)) as IEmergencyAccessService;
await emergencyAccessService.SendNotificationsAsync();
}
}
}

View File

@ -2,22 +2,23 @@
using Bit.Core.Services;
using Quartz;
namespace Bit.Api.Jobs;
public class EmergencyAccessTimeoutJob : BaseJob
namespace Bit.Api.Jobs
{
private readonly IServiceScopeFactory _serviceScopeFactory;
public EmergencyAccessTimeoutJob(IServiceScopeFactory serviceScopeFactory, ILogger<EmergencyAccessNotificationJob> logger)
: base(logger)
public class EmergencyAccessTimeoutJob : BaseJob
{
_serviceScopeFactory = serviceScopeFactory;
}
private readonly IServiceScopeFactory _serviceScopeFactory;
protected override async Task ExecuteJobAsync(IJobExecutionContext context)
{
using var scope = _serviceScopeFactory.CreateScope();
var emergencyAccessService = scope.ServiceProvider.GetService(typeof(IEmergencyAccessService)) as IEmergencyAccessService;
await emergencyAccessService.HandleTimedOutRequestsAsync();
public EmergencyAccessTimeoutJob(IServiceScopeFactory serviceScopeFactory, ILogger<EmergencyAccessNotificationJob> logger)
: base(logger)
{
_serviceScopeFactory = serviceScopeFactory;
}
protected override async Task ExecuteJobAsync(IJobExecutionContext context)
{
using var scope = _serviceScopeFactory.CreateScope();
var emergencyAccessService = scope.ServiceProvider.GetService(typeof(IEmergencyAccessService)) as IEmergencyAccessService;
await emergencyAccessService.HandleTimedOutRequestsAsync();
}
}
}

View File

@ -2,81 +2,82 @@
using Bit.Core.Settings;
using Quartz;
namespace Bit.Api.Jobs;
public class JobsHostedService : BaseJobsHostedService
namespace Bit.Api.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 everyTopOfTheHourTrigger = TriggerBuilder.Create()
.WithIdentity("EveryTopOfTheHourTrigger")
.StartNow()
.WithCronSchedule("0 0 * * * ?")
.Build();
var emergencyAccessNotificationTrigger = TriggerBuilder.Create()
.WithIdentity("EmergencyAccessNotificationTrigger")
.StartNow()
.WithCronSchedule("0 0 * * * ?")
.Build();
var emergencyAccessTimeoutTrigger = TriggerBuilder.Create()
.WithIdentity("EmergencyAccessTimeoutTrigger")
.StartNow()
.WithCronSchedule("0 0 * * * ?")
.Build();
var everyTopOfTheSixthHourTrigger = TriggerBuilder.Create()
.WithIdentity("EveryTopOfTheSixthHourTrigger")
.StartNow()
.WithCronSchedule("0 0 */6 * * ?")
.Build();
var everyTwelfthHourAndThirtyMinutesTrigger = TriggerBuilder.Create()
.WithIdentity("EveryTwelfthHourAndThirtyMinutesTrigger")
.StartNow()
.WithCronSchedule("0 30 */12 * * ?")
.Build();
var randomDailySponsorshipSyncTrigger = TriggerBuilder.Create()
.WithIdentity("RandomDailySponsorshipSyncTrigger")
.StartAt(DateBuilder.FutureDate(new Random().Next(24), IntervalUnit.Hour))
.WithSimpleSchedule(x => x
.WithIntervalInHours(24)
.RepeatForever())
.Build();
public JobsHostedService(
GlobalSettings globalSettings,
IServiceProvider serviceProvider,
ILogger<JobsHostedService> logger,
ILogger<JobListener> listenerLogger)
: base(globalSettings, serviceProvider, logger, listenerLogger) { }
var jobs = new List<Tuple<Type, ITrigger>>
public override async Task StartAsync(CancellationToken cancellationToken)
{
new Tuple<Type, ITrigger>(typeof(AliveJob), everyTopOfTheHourTrigger),
new Tuple<Type, ITrigger>(typeof(EmergencyAccessNotificationJob), emergencyAccessNotificationTrigger),
new Tuple<Type, ITrigger>(typeof(EmergencyAccessTimeoutJob), emergencyAccessTimeoutTrigger),
new Tuple<Type, ITrigger>(typeof(ValidateUsersJob), everyTopOfTheSixthHourTrigger),
new Tuple<Type, ITrigger>(typeof(ValidateOrganizationsJob), everyTwelfthHourAndThirtyMinutesTrigger)
};
var everyTopOfTheHourTrigger = TriggerBuilder.Create()
.WithIdentity("EveryTopOfTheHourTrigger")
.StartNow()
.WithCronSchedule("0 0 * * * ?")
.Build();
var emergencyAccessNotificationTrigger = TriggerBuilder.Create()
.WithIdentity("EmergencyAccessNotificationTrigger")
.StartNow()
.WithCronSchedule("0 0 * * * ?")
.Build();
var emergencyAccessTimeoutTrigger = TriggerBuilder.Create()
.WithIdentity("EmergencyAccessTimeoutTrigger")
.StartNow()
.WithCronSchedule("0 0 * * * ?")
.Build();
var everyTopOfTheSixthHourTrigger = TriggerBuilder.Create()
.WithIdentity("EveryTopOfTheSixthHourTrigger")
.StartNow()
.WithCronSchedule("0 0 */6 * * ?")
.Build();
var everyTwelfthHourAndThirtyMinutesTrigger = TriggerBuilder.Create()
.WithIdentity("EveryTwelfthHourAndThirtyMinutesTrigger")
.StartNow()
.WithCronSchedule("0 30 */12 * * ?")
.Build();
var randomDailySponsorshipSyncTrigger = TriggerBuilder.Create()
.WithIdentity("RandomDailySponsorshipSyncTrigger")
.StartAt(DateBuilder.FutureDate(new Random().Next(24), IntervalUnit.Hour))
.WithSimpleSchedule(x => x
.WithIntervalInHours(24)
.RepeatForever())
.Build();
if (_globalSettings.SelfHosted && _globalSettings.EnableCloudCommunication)
{
jobs.Add(new Tuple<Type, ITrigger>(typeof(SelfHostedSponsorshipSyncJob), randomDailySponsorshipSyncTrigger));
var jobs = new List<Tuple<Type, ITrigger>>
{
new Tuple<Type, ITrigger>(typeof(AliveJob), everyTopOfTheHourTrigger),
new Tuple<Type, ITrigger>(typeof(EmergencyAccessNotificationJob), emergencyAccessNotificationTrigger),
new Tuple<Type, ITrigger>(typeof(EmergencyAccessTimeoutJob), emergencyAccessTimeoutTrigger),
new Tuple<Type, ITrigger>(typeof(ValidateUsersJob), everyTopOfTheSixthHourTrigger),
new Tuple<Type, ITrigger>(typeof(ValidateOrganizationsJob), everyTwelfthHourAndThirtyMinutesTrigger)
};
if (_globalSettings.SelfHosted && _globalSettings.EnableCloudCommunication)
{
jobs.Add(new Tuple<Type, ITrigger>(typeof(SelfHostedSponsorshipSyncJob), randomDailySponsorshipSyncTrigger));
}
Jobs = jobs;
await base.StartAsync(cancellationToken);
}
Jobs = jobs;
await base.StartAsync(cancellationToken);
}
public static void AddJobsServices(IServiceCollection services, bool selfHosted)
{
if (selfHosted)
public static void AddJobsServices(IServiceCollection services, bool selfHosted)
{
services.AddTransient<SelfHostedSponsorshipSyncJob>();
if (selfHosted)
{
services.AddTransient<SelfHostedSponsorshipSyncJob>();
}
services.AddTransient<AliveJob>();
services.AddTransient<EmergencyAccessNotificationJob>();
services.AddTransient<EmergencyAccessTimeoutJob>();
services.AddTransient<ValidateUsersJob>();
services.AddTransient<ValidateOrganizationsJob>();
}
services.AddTransient<AliveJob>();
services.AddTransient<EmergencyAccessNotificationJob>();
services.AddTransient<EmergencyAccessTimeoutJob>();
services.AddTransient<ValidateUsersJob>();
services.AddTransient<ValidateOrganizationsJob>();
}
}

View File

@ -7,58 +7,59 @@ using Bit.Core.Services;
using Bit.Core.Settings;
using Quartz;
namespace Bit.Api.Jobs;
public class SelfHostedSponsorshipSyncJob : BaseJob
namespace Bit.Api.Jobs
{
private readonly IServiceProvider _serviceProvider;
private IOrganizationRepository _organizationRepository;
private IOrganizationConnectionRepository _organizationConnectionRepository;
private readonly ILicensingService _licensingService;
private GlobalSettings _globalSettings;
public SelfHostedSponsorshipSyncJob(
IServiceProvider serviceProvider,
IOrganizationRepository organizationRepository,
IOrganizationConnectionRepository organizationConnectionRepository,
ILicensingService licensingService,
ILogger<SelfHostedSponsorshipSyncJob> logger,
GlobalSettings globalSettings)
: base(logger)
public class SelfHostedSponsorshipSyncJob : BaseJob
{
_serviceProvider = serviceProvider;
_organizationRepository = organizationRepository;
_organizationConnectionRepository = organizationConnectionRepository;
_licensingService = licensingService;
_globalSettings = globalSettings;
}
private readonly IServiceProvider _serviceProvider;
private IOrganizationRepository _organizationRepository;
private IOrganizationConnectionRepository _organizationConnectionRepository;
private readonly ILicensingService _licensingService;
private GlobalSettings _globalSettings;
protected override async Task ExecuteJobAsync(IJobExecutionContext context)
{
if (!_globalSettings.EnableCloudCommunication)
public SelfHostedSponsorshipSyncJob(
IServiceProvider serviceProvider,
IOrganizationRepository organizationRepository,
IOrganizationConnectionRepository organizationConnectionRepository,
ILicensingService licensingService,
ILogger<SelfHostedSponsorshipSyncJob> logger,
GlobalSettings globalSettings)
: base(logger)
{
_logger.LogInformation("Skipping Organization sync with cloud - Cloud communication is disabled in global settings");
return;
_serviceProvider = serviceProvider;
_organizationRepository = organizationRepository;
_organizationConnectionRepository = organizationConnectionRepository;
_licensingService = licensingService;
_globalSettings = globalSettings;
}
var organizations = await _organizationRepository.GetManyByEnabledAsync();
using (var scope = _serviceProvider.CreateScope())
protected override async Task ExecuteJobAsync(IJobExecutionContext context)
{
var syncCommand = scope.ServiceProvider.GetRequiredService<ISelfHostedSyncSponsorshipsCommand>();
foreach (var org in organizations)
if (!_globalSettings.EnableCloudCommunication)
{
var connection = (await _organizationConnectionRepository.GetEnabledByOrganizationIdTypeAsync(org.Id, OrganizationConnectionType.CloudBillingSync)).FirstOrDefault();
if (connection != null)
_logger.LogInformation("Skipping Organization sync with cloud - Cloud communication is disabled in global settings");
return;
}
var organizations = await _organizationRepository.GetManyByEnabledAsync();
using (var scope = _serviceProvider.CreateScope())
{
var syncCommand = scope.ServiceProvider.GetRequiredService<ISelfHostedSyncSponsorshipsCommand>();
foreach (var org in organizations)
{
try
var connection = (await _organizationConnectionRepository.GetEnabledByOrganizationIdTypeAsync(org.Id, OrganizationConnectionType.CloudBillingSync)).FirstOrDefault();
if (connection != null)
{
var config = connection.GetConfig<BillingSyncConfig>();
await syncCommand.SyncOrganization(org.Id, config.CloudOrganizationId, connection);
}
catch (Exception ex)
{
_logger.LogError(ex, $"Sponsorship sync for organization {org.Name} Failed");
try
{
var config = connection.GetConfig<BillingSyncConfig>();
await syncCommand.SyncOrganization(org.Id, config.CloudOrganizationId, connection);
}
catch (Exception ex)
{
_logger.LogError(ex, $"Sponsorship sync for organization {org.Name} Failed");
}
}
}
}

View File

@ -2,22 +2,23 @@
using Bit.Core.Services;
using Quartz;
namespace Bit.Api.Jobs;
public class ValidateOrganizationsJob : BaseJob
namespace Bit.Api.Jobs
{
private readonly ILicensingService _licensingService;
public ValidateOrganizationsJob(
ILicensingService licensingService,
ILogger<ValidateOrganizationsJob> logger)
: base(logger)
public class ValidateOrganizationsJob : BaseJob
{
_licensingService = licensingService;
}
private readonly ILicensingService _licensingService;
protected async override Task ExecuteJobAsync(IJobExecutionContext context)
{
await _licensingService.ValidateOrganizationsAsync();
public ValidateOrganizationsJob(
ILicensingService licensingService,
ILogger<ValidateOrganizationsJob> logger)
: base(logger)
{
_licensingService = licensingService;
}
protected async override Task ExecuteJobAsync(IJobExecutionContext context)
{
await _licensingService.ValidateOrganizationsAsync();
}
}
}

View File

@ -2,22 +2,23 @@
using Bit.Core.Services;
using Quartz;
namespace Bit.Api.Jobs;
public class ValidateUsersJob : BaseJob
namespace Bit.Api.Jobs
{
private readonly ILicensingService _licensingService;
public ValidateUsersJob(
ILicensingService licensingService,
ILogger<ValidateUsersJob> logger)
: base(logger)
public class ValidateUsersJob : BaseJob
{
_licensingService = licensingService;
}
private readonly ILicensingService _licensingService;
protected async override Task ExecuteJobAsync(IJobExecutionContext context)
{
await _licensingService.ValidateUsersAsync();
public ValidateUsersJob(
ILicensingService licensingService,
ILogger<ValidateUsersJob> logger)
: base(logger)
{
_licensingService = licensingService;
}
protected async override Task ExecuteJobAsync(IJobExecutionContext context)
{
await _licensingService.ValidateUsersAsync();
}
}
}

View File

@ -1,20 +1,21 @@
using Bit.Core.Models.Data;
using Bit.Core.Utilities;
namespace Bit.Api.Models;
public class CipherAttachmentModel
namespace Bit.Api.Models
{
public CipherAttachmentModel() { }
public CipherAttachmentModel(CipherAttachment.MetaData data)
public class CipherAttachmentModel
{
FileName = data.FileName;
Key = data.Key;
}
public CipherAttachmentModel() { }
[EncryptedStringLength(1000)]
public string FileName { get; set; }
[EncryptedStringLength(1000)]
public string Key { get; set; }
public CipherAttachmentModel(CipherAttachment.MetaData data)
{
FileName = data.FileName;
Key = data.Key;
}
[EncryptedStringLength(1000)]
public string FileName { get; set; }
[EncryptedStringLength(1000)]
public string Key { get; set; }
}
}

View File

@ -2,38 +2,39 @@
using Bit.Core.Models.Data;
using Bit.Core.Utilities;
namespace Bit.Api.Models;
public class CipherCardModel
namespace Bit.Api.Models
{
public CipherCardModel() { }
public CipherCardModel(CipherCardData data)
public class CipherCardModel
{
CardholderName = data.CardholderName;
Brand = data.Brand;
Number = data.Number;
ExpMonth = data.ExpMonth;
ExpYear = data.ExpYear;
Code = data.Code;
}
public CipherCardModel() { }
[EncryptedString]
[EncryptedStringLength(1000)]
public string CardholderName { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string Brand { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string Number { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string ExpMonth { get; set; }
[EncryptedString]
[StringLength(1000)]
public string ExpYear { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string Code { get; set; }
public CipherCardModel(CipherCardData data)
{
CardholderName = data.CardholderName;
Brand = data.Brand;
Number = data.Number;
ExpMonth = data.ExpMonth;
ExpYear = data.ExpYear;
Code = data.Code;
}
[EncryptedString]
[EncryptedStringLength(1000)]
public string CardholderName { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string Brand { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string Number { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string ExpMonth { get; set; }
[EncryptedString]
[StringLength(1000)]
public string ExpYear { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string Code { get; set; }
}
}

View File

@ -2,35 +2,36 @@
using Bit.Core.Models.Data;
using Bit.Core.Utilities;
namespace Bit.Api.Models;
public class CipherFieldModel
namespace Bit.Api.Models
{
public CipherFieldModel() { }
public CipherFieldModel(CipherFieldData data)
public class CipherFieldModel
{
Type = data.Type;
Name = data.Name;
Value = data.Value;
LinkedId = data.LinkedId ?? null;
}
public CipherFieldModel() { }
public FieldType Type { get; set; }
[EncryptedStringLength(1000)]
public string Name { get; set; }
[EncryptedStringLength(5000)]
public string Value { get; set; }
public int? LinkedId { get; set; }
public CipherFieldData ToCipherFieldData()
{
return new CipherFieldData
public CipherFieldModel(CipherFieldData data)
{
Type = Type,
Name = Name,
Value = Value,
LinkedId = LinkedId ?? null,
};
Type = data.Type;
Name = data.Name;
Value = data.Value;
LinkedId = data.LinkedId ?? null;
}
public FieldType Type { get; set; }
[EncryptedStringLength(1000)]
public string Name { get; set; }
[EncryptedStringLength(5000)]
public string Value { get; set; }
public int? LinkedId { get; set; }
public CipherFieldData ToCipherFieldData()
{
return new CipherFieldData
{
Type = Type,
Name = Name,
Value = Value,
LinkedId = LinkedId ?? null,
};
}
}
}

View File

@ -2,86 +2,87 @@
using Bit.Core.Models.Data;
using Bit.Core.Utilities;
namespace Bit.Api.Models;
public class CipherIdentityModel
namespace Bit.Api.Models
{
public CipherIdentityModel() { }
public CipherIdentityModel(CipherIdentityData data)
public class CipherIdentityModel
{
Title = data.Title;
FirstName = data.FirstName;
MiddleName = data.MiddleName;
LastName = data.LastName;
Address1 = data.Address1;
Address2 = data.Address2;
Address3 = data.Address3;
City = data.City;
State = data.State;
PostalCode = data.PostalCode;
Country = data.Country;
Company = data.Company;
Email = data.Email;
Phone = data.Phone;
SSN = data.SSN;
Username = data.Username;
PassportNumber = data.PassportNumber;
LicenseNumber = data.LicenseNumber;
}
public CipherIdentityModel() { }
[EncryptedString]
[EncryptedStringLength(1000)]
public string Title { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string FirstName { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string MiddleName { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string LastName { get; set; }
[EncryptedString]
[StringLength(1000)]
public string Address1 { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string Address2 { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string Address3 { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string City { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string State { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string PostalCode { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string Country { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string Company { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string Email { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string Phone { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string SSN { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string Username { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string PassportNumber { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string LicenseNumber { get; set; }
public CipherIdentityModel(CipherIdentityData data)
{
Title = data.Title;
FirstName = data.FirstName;
MiddleName = data.MiddleName;
LastName = data.LastName;
Address1 = data.Address1;
Address2 = data.Address2;
Address3 = data.Address3;
City = data.City;
State = data.State;
PostalCode = data.PostalCode;
Country = data.Country;
Company = data.Company;
Email = data.Email;
Phone = data.Phone;
SSN = data.SSN;
Username = data.Username;
PassportNumber = data.PassportNumber;
LicenseNumber = data.LicenseNumber;
}
[EncryptedString]
[EncryptedStringLength(1000)]
public string Title { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string FirstName { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string MiddleName { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string LastName { get; set; }
[EncryptedString]
[StringLength(1000)]
public string Address1 { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string Address2 { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string Address3 { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string City { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string State { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string PostalCode { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string Country { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string Company { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string Email { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string Phone { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string SSN { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string Username { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string PassportNumber { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string LicenseNumber { get; set; }
}
}

View File

@ -2,83 +2,84 @@
using Bit.Core.Models.Data;
using Bit.Core.Utilities;
namespace Bit.Api.Models;
public class CipherLoginModel
namespace Bit.Api.Models
{
public CipherLoginModel() { }
public CipherLoginModel(CipherLoginData data)
public class CipherLoginModel
{
Uris = data.Uris?.Select(u => new CipherLoginUriModel(u))?.ToList();
if (!Uris?.Any() ?? true)
{
Uri = data.Uri;
}
public CipherLoginModel() { }
Username = data.Username;
Password = data.Password;
PasswordRevisionDate = data.PasswordRevisionDate;
Totp = data.Totp;
AutofillOnPageLoad = data.AutofillOnPageLoad;
}
[EncryptedString]
[EncryptedStringLength(10000)]
public string Uri
{
get => Uris?.FirstOrDefault()?.Uri;
set
public CipherLoginModel(CipherLoginData data)
{
if (string.IsNullOrWhiteSpace(value))
Uris = data.Uris?.Select(u => new CipherLoginUriModel(u))?.ToList();
if (!Uris?.Any() ?? true)
{
return;
Uri = data.Uri;
}
if (Uris == null)
{
Uris = new List<CipherLoginUriModel>();
}
Uris.Add(new CipherLoginUriModel(value));
}
}
public List<CipherLoginUriModel> Uris { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string Username { get; set; }
[EncryptedString]
[EncryptedStringLength(5000)]
public string Password { get; set; }
public DateTime? PasswordRevisionDate { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string Totp { get; set; }
public bool? AutofillOnPageLoad { get; set; }
public class CipherLoginUriModel
{
public CipherLoginUriModel() { }
public CipherLoginUriModel(string uri)
{
Uri = uri;
}
public CipherLoginUriModel(CipherLoginData.CipherLoginUriData uri)
{
Uri = uri.Uri;
Match = uri.Match;
Username = data.Username;
Password = data.Password;
PasswordRevisionDate = data.PasswordRevisionDate;
Totp = data.Totp;
AutofillOnPageLoad = data.AutofillOnPageLoad;
}
[EncryptedString]
[EncryptedStringLength(10000)]
public string Uri { get; set; }
public UriMatchType? Match { get; set; } = null;
public CipherLoginData.CipherLoginUriData ToCipherLoginUriData()
public string Uri
{
return new CipherLoginData.CipherLoginUriData { Uri = Uri, Match = Match, };
get => Uris?.FirstOrDefault()?.Uri;
set
{
if (string.IsNullOrWhiteSpace(value))
{
return;
}
if (Uris == null)
{
Uris = new List<CipherLoginUriModel>();
}
Uris.Add(new CipherLoginUriModel(value));
}
}
public List<CipherLoginUriModel> Uris { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string Username { get; set; }
[EncryptedString]
[EncryptedStringLength(5000)]
public string Password { get; set; }
public DateTime? PasswordRevisionDate { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string Totp { get; set; }
public bool? AutofillOnPageLoad { get; set; }
public class CipherLoginUriModel
{
public CipherLoginUriModel() { }
public CipherLoginUriModel(string uri)
{
Uri = uri;
}
public CipherLoginUriModel(CipherLoginData.CipherLoginUriData uri)
{
Uri = uri.Uri;
Match = uri.Match;
}
[EncryptedString]
[EncryptedStringLength(10000)]
public string Uri { get; set; }
public UriMatchType? Match { get; set; } = null;
public CipherLoginData.CipherLoginUriData ToCipherLoginUriData()
{
return new CipherLoginData.CipherLoginUriData { Uri = Uri, Match = Match, };
}
}
}
}

View File

@ -2,27 +2,28 @@
using Bit.Core.Models.Data;
using Bit.Core.Utilities;
namespace Bit.Api.Models;
public class CipherPasswordHistoryModel
namespace Bit.Api.Models
{
public CipherPasswordHistoryModel() { }
public CipherPasswordHistoryModel(CipherPasswordHistoryData data)
public class CipherPasswordHistoryModel
{
Password = data.Password;
LastUsedDate = data.LastUsedDate;
}
public CipherPasswordHistoryModel() { }
[EncryptedString]
[EncryptedStringLength(5000)]
[Required]
public string Password { get; set; }
[Required]
public DateTime? LastUsedDate { get; set; }
public CipherPasswordHistoryModel(CipherPasswordHistoryData data)
{
Password = data.Password;
LastUsedDate = data.LastUsedDate;
}
public CipherPasswordHistoryData ToCipherPasswordHistoryData()
{
return new CipherPasswordHistoryData { Password = Password, LastUsedDate = LastUsedDate.Value, };
[EncryptedString]
[EncryptedStringLength(5000)]
[Required]
public string Password { get; set; }
[Required]
public DateTime? LastUsedDate { get; set; }
public CipherPasswordHistoryData ToCipherPasswordHistoryData()
{
return new CipherPasswordHistoryData { Password = Password, LastUsedDate = LastUsedDate.Value, };
}
}
}

View File

@ -1,16 +1,17 @@
using Bit.Core.Enums;
using Bit.Core.Models.Data;
namespace Bit.Api.Models;
public class CipherSecureNoteModel
namespace Bit.Api.Models
{
public CipherSecureNoteModel() { }
public CipherSecureNoteModel(CipherSecureNoteData data)
public class CipherSecureNoteModel
{
Type = data.Type;
}
public CipherSecureNoteModel() { }
public SecureNoteType Type { get; set; }
public CipherSecureNoteModel(CipherSecureNoteData data)
{
Type = data.Type;
}
public SecureNoteType Type { get; set; }
}
}

View File

@ -1,18 +1,19 @@
using System.ComponentModel.DataAnnotations;
namespace Bit.Api.Models.Public;
public abstract class AssociationWithPermissionsBaseModel
namespace Bit.Api.Models.Public
{
/// <summary>
/// The associated object's unique identifier.
/// </summary>
/// <example>bfbc8338-e329-4dc0-b0c9-317c2ebf1a09</example>
[Required]
public Guid? Id { get; set; }
/// <summary>
/// When true, the read only permission will not allow the user or group to make changes to items.
/// </summary>
[Required]
public bool? ReadOnly { get; set; }
public abstract class AssociationWithPermissionsBaseModel
{
/// <summary>
/// The associated object's unique identifier.
/// </summary>
/// <example>bfbc8338-e329-4dc0-b0c9-317c2ebf1a09</example>
[Required]
public Guid? Id { get; set; }
/// <summary>
/// When true, the read only permission will not allow the user or group to make changes to items.
/// </summary>
[Required]
public bool? ReadOnly { get; set; }
}
}

View File

@ -1,13 +1,14 @@
using System.ComponentModel.DataAnnotations;
namespace Bit.Api.Models.Public;
public abstract class CollectionBaseModel
namespace Bit.Api.Models.Public
{
/// <summary>
/// External identifier for reference or linking this collection to another system.
/// </summary>
/// <example>external_id_123456</example>
[StringLength(300)]
public string ExternalId { get; set; }
public abstract class CollectionBaseModel
{
/// <summary>
/// External identifier for reference or linking this collection to another system.
/// </summary>
/// <example>external_id_123456</example>
[StringLength(300)]
public string ExternalId { get; set; }
}
}

View File

@ -1,26 +1,27 @@
using System.ComponentModel.DataAnnotations;
namespace Bit.Api.Models.Public;
public abstract class GroupBaseModel
namespace Bit.Api.Models.Public
{
/// <summary>
/// The name of the group.
/// </summary>
/// <example>Development Team</example>
[Required]
[StringLength(100)]
public string Name { get; set; }
/// <summary>
/// Determines if this group can access all collections within the organization, or only the associated
/// collections. If set to <c>true</c>, this option overrides any collection assignments.
/// </summary>
[Required]
public bool? AccessAll { get; set; }
/// <summary>
/// External identifier for reference or linking this group to another system, such as a user directory.
/// </summary>
/// <example>external_id_123456</example>
[StringLength(300)]
public string ExternalId { get; set; }
public abstract class GroupBaseModel
{
/// <summary>
/// The name of the group.
/// </summary>
/// <example>Development Team</example>
[Required]
[StringLength(100)]
public string Name { get; set; }
/// <summary>
/// Determines if this group can access all collections within the organization, or only the associated
/// collections. If set to <c>true</c>, this option overrides any collection assignments.
/// </summary>
[Required]
public bool? AccessAll { get; set; }
/// <summary>
/// External identifier for reference or linking this group to another system, such as a user directory.
/// </summary>
/// <example>external_id_123456</example>
[StringLength(300)]
public string ExternalId { get; set; }
}
}

Some files were not shown because too many files have changed in this diff Show More