Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RBAC to Bitwarden Portal #2853

Merged
merged 18 commits into from
May 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Admin/Controllers/LogsController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Bit.Admin.Models;
using Bit.Admin.Utilities;
using Bit.Core.Settings;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
Expand All @@ -11,6 +12,7 @@ namespace Bit.Admin.Controllers;

[Authorize]
[SelfHosted(NotSelfHostedOnly = true)]
[RequirePermission(Enums.Permission.Logs_View)]
public class LogsController : Controller
{
private const string Database = "Diagnostics";
Expand Down
69 changes: 65 additions & 4 deletions src/Admin/Controllers/OrganizationsController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using Bit.Admin.Models;
using Bit.Admin.Enums;
using Bit.Admin.Models;
using Bit.Admin.Services;
using Bit.Admin.Utilities;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.OrganizationConnectionConfigs;
Expand Down Expand Up @@ -36,6 +39,7 @@ public class OrganizationsController : Controller
private readonly IUserService _userService;
private readonly IProviderRepository _providerRepository;
private readonly ILogger<OrganizationsController> _logger;
private readonly IAccessControlService _accessControlService;

public OrganizationsController(
IOrganizationService organizationService,
Expand All @@ -54,7 +58,8 @@ public OrganizationsController(
IReferenceEventService referenceEventService,
IUserService userService,
IProviderRepository providerRepository,
ILogger<OrganizationsController> logger)
ILogger<OrganizationsController> logger,
IAccessControlService accessControlService)
{
_organizationService = organizationService;
_organizationRepository = organizationRepository;
Expand All @@ -73,8 +78,10 @@ public OrganizationsController(
_userService = userService;
_providerRepository = providerRepository;
_logger = logger;
_accessControlService = accessControlService;
}

[RequirePermission(Permission.Org_List_View)]
public async Task<IActionResult> Index(string name = null, string userEmail = null, bool? paid = null,
int page = 1, int count = 25)
{
Expand Down Expand Up @@ -163,8 +170,8 @@ public async Task<IActionResult> Edit(Guid id)
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<IActionResult> Edit(Guid id, OrganizationEditModel model)
{
var organization = await _organizationRepository.GetByIdAsync(id);
model.ToOrganization(organization);
var organization = await GetOrganization(id, model);

await _organizationRepository.ReplaceAsync(organization);
await _applicationCacheService.UpsertOrganizationAbilityAsync(organization);
await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.OrganizationEditedByAdmin, organization)
Expand All @@ -177,6 +184,7 @@ await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventTy

[HttpPost]
[ValidateAntiForgeryToken]
[RequirePermission(Permission.Org_Delete)]
public async Task<IActionResult> Delete(Guid id)
{
var organization = await _organizationRepository.GetByIdAsync(id);
Expand Down Expand Up @@ -241,4 +249,57 @@ public async Task<IActionResult> ResendOwnerInvite(Guid id)

return Json(null);
}
private async Task<Organization> GetOrganization(Guid id, OrganizationEditModel model)
{
var organization = await _organizationRepository.GetByIdAsync(id);

if (_accessControlService.UserHasPermission(Permission.Org_CheckEnabledBox))
{
organization.Enabled = model.Enabled;
}

if (_accessControlService.UserHasPermission(Permission.Org_Plan_Edit))
{
organization.PlanType = model.PlanType.Value;
organization.Plan = model.Plan;
organization.Seats = model.Seats;
organization.MaxAutoscaleSeats = model.MaxAutoscaleSeats;
organization.MaxCollections = model.MaxCollections;
organization.MaxStorageGb = model.MaxStorageGb;

//features
organization.SelfHost = model.SelfHost;
organization.Use2fa = model.Use2fa;
organization.UseApi = model.UseApi;
organization.UseGroups = model.UseGroups;
organization.UsePolicies = model.UsePolicies;
organization.UseSso = model.UseSso;
organization.UseKeyConnector = model.UseKeyConnector;
organization.UseScim = model.UseScim;
organization.UseDirectory = model.UseDirectory;
organization.UseEvents = model.UseEvents;
organization.UseResetPassword = model.UseResetPassword;
organization.UseCustomPermissions = model.UseCustomPermissions;
organization.UseTotp = model.UseTotp;
organization.UsersGetPremium = model.UsersGetPremium;
organization.UseSecretsManager = model.UseSecretsManager;
}

if (_accessControlService.UserHasPermission(Permission.Org_Licensing_Edit))
{
organization.LicenseKey = model.LicenseKey;
organization.ExpirationDate = model.ExpirationDate;
}

if (_accessControlService.UserHasPermission(Permission.Org_Billing_Edit))
{
organization.BillingEmail = model.BillingEmail?.ToLowerInvariant()?.Trim();
organization.Gateway = model.Gateway;
organization.GatewayCustomerId = model.GatewayCustomerId;
organization.GatewaySubscriptionId = model.GatewaySubscriptionId;
}

return organization;
}

}
9 changes: 8 additions & 1 deletion src/Admin/Controllers/ProvidersController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Bit.Admin.Models;
using Bit.Admin.Enums;
using Bit.Admin.Models;
using Bit.Admin.Utilities;
using Bit.Core.Entities.Provider;
using Bit.Core.Enums.Provider;
using Bit.Core.Providers.Interfaces;
Expand Down Expand Up @@ -54,6 +56,7 @@ public ProvidersController(
_createProviderCommand = createProviderCommand;
}

[RequirePermission(Permission.Provider_List_View)]
public async Task<IActionResult> Index(string name = null, string userEmail = null, int page = 1, int count = 25)
{
if (page < 1)
Expand Down Expand Up @@ -90,6 +93,7 @@ public IActionResult Create(string ownerEmail = null)

[HttpPost]
[ValidateAntiForgeryToken]
[RequirePermission(Permission.Provider_Create)]
public async Task<IActionResult> Create(CreateProviderModel model)
{
if (!ModelState.IsValid)
Expand All @@ -111,6 +115,7 @@ public async Task<IActionResult> Create(CreateProviderModel model)
return RedirectToAction("Edit", new { id = provider.Id });
}

[RequirePermission(Permission.Provider_View)]
public async Task<IActionResult> View(Guid id)
{
var provider = await _providerRepository.GetByIdAsync(id);
Expand Down Expand Up @@ -141,6 +146,7 @@ public async Task<IActionResult> Edit(Guid id)
[HttpPost]
[ValidateAntiForgeryToken]
[SelfHosted(NotSelfHostedOnly = true)]
[RequirePermission(Permission.Provider_Edit)]
public async Task<IActionResult> Edit(Guid id, ProviderEditModel model)
{
var provider = await _providerRepository.GetByIdAsync(id);
Expand All @@ -155,6 +161,7 @@ public async Task<IActionResult> Edit(Guid id, ProviderEditModel model)
return RedirectToAction("Edit", new { id });
}

[RequirePermission(Permission.Provider_ResendEmailInvite)]
public async Task<IActionResult> ResendInvite(Guid ownerId, Guid providerId)
{
await _providerService.ResendProviderSetupInviteEmailAsync(providerId, ownerId);
Expand Down
19 changes: 19 additions & 0 deletions src/Admin/Controllers/ToolsController.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Text;
using System.Text.Json;
using Bit.Admin.Enums;
using Bit.Admin.Models;
using Bit.Admin.Utilities;
using Bit.Core.Entities;
using Bit.Core.Models.BitStripe;
using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
Expand Down Expand Up @@ -55,13 +57,15 @@ public ToolsController(
_environment = environment;
}

[RequirePermission(Permission.Tools_ChargeBrainTreeCustomer)]
public IActionResult ChargeBraintree()
{
return View(new ChargeBraintreeModel());
}

[HttpPost]
[ValidateAntiForgeryToken]
[RequirePermission(Permission.Tools_ChargeBrainTreeCustomer)]
public async Task<IActionResult> ChargeBraintree(ChargeBraintreeModel model)
{
if (!ModelState.IsValid)
Expand Down Expand Up @@ -113,6 +117,7 @@ public async Task<IActionResult> ChargeBraintree(ChargeBraintreeModel model)
return View(model);
}

[RequirePermission(Permission.Tools_CreateEditTransaction)]
public IActionResult CreateTransaction(Guid? organizationId = null, Guid? userId = null)
{
return View("CreateUpdateTransaction", new CreateUpdateTransactionModel
Expand All @@ -124,6 +129,7 @@ public IActionResult CreateTransaction(Guid? organizationId = null, Guid? userId

[HttpPost]
[ValidateAntiForgeryToken]
[RequirePermission(Permission.Tools_CreateEditTransaction)]
public async Task<IActionResult> CreateTransaction(CreateUpdateTransactionModel model)
{
if (!ModelState.IsValid)
Expand All @@ -142,6 +148,7 @@ public async Task<IActionResult> CreateTransaction(CreateUpdateTransactionModel
}
}

[RequirePermission(Permission.Tools_CreateEditTransaction)]
public async Task<IActionResult> EditTransaction(Guid id)
{
var transaction = await _transactionRepository.GetByIdAsync(id);
Expand All @@ -154,6 +161,7 @@ public async Task<IActionResult> EditTransaction(Guid id)

[HttpPost]
[ValidateAntiForgeryToken]
[RequirePermission(Permission.Tools_CreateEditTransaction)]
public async Task<IActionResult> EditTransaction(Guid id, CreateUpdateTransactionModel model)
{
if (!ModelState.IsValid)
Expand All @@ -171,13 +179,15 @@ public async Task<IActionResult> EditTransaction(Guid id, CreateUpdateTransactio
}
}

[RequirePermission(Permission.Tools_PromoteAdmin)]
public IActionResult PromoteAdmin()
{
return View();
}

[HttpPost]
[ValidateAntiForgeryToken]
[RequirePermission(Permission.Tools_PromoteAdmin)]
public async Task<IActionResult> PromoteAdmin(PromoteAdminModel model)
{
if (!ModelState.IsValid)
Expand Down Expand Up @@ -207,13 +217,15 @@ public async Task<IActionResult> PromoteAdmin(PromoteAdminModel model)
return RedirectToAction("Edit", "Organizations", new { id = model.OrganizationId.Value });
}

[RequirePermission(Permission.Tools_GenerateLicenseFile)]
public IActionResult GenerateLicense()
{
return View();
}

[HttpPost]
[ValidateAntiForgeryToken]
[RequirePermission(Permission.Tools_GenerateLicenseFile)]
public async Task<IActionResult> GenerateLicense(LicenseModel model)
{
if (!ModelState.IsValid)
Expand Down Expand Up @@ -285,6 +297,7 @@ public async Task<IActionResult> GenerateLicense(LicenseModel model)
}
}

[RequirePermission(Permission.Tools_ManageTaxRates)]
public async Task<IActionResult> TaxRate(int page = 1, int count = 25)
{
if (page < 1)
Expand All @@ -307,6 +320,7 @@ public async Task<IActionResult> TaxRate(int page = 1, int count = 25)
});
}

[RequirePermission(Permission.Tools_ManageTaxRates)]
public async Task<IActionResult> TaxRateAddEdit(string stripeTaxRateId = null)
{
if (string.IsNullOrWhiteSpace(stripeTaxRateId))
Expand All @@ -328,6 +342,7 @@ public async Task<IActionResult> TaxRateAddEdit(string stripeTaxRateId = null)
}

[ValidateAntiForgeryToken]
[RequirePermission(Permission.Tools_ManageTaxRates)]
public async Task<IActionResult> TaxRateUpload(IFormFile file)
{
if (file == null || file.Length == 0)
Expand Down Expand Up @@ -395,6 +410,7 @@ public async Task<IActionResult> TaxRateUpload(IFormFile file)

[HttpPost]
[ValidateAntiForgeryToken]
[RequirePermission(Permission.Tools_ManageTaxRates)]
public async Task<IActionResult> TaxRateAddEdit(TaxRateAddEditModel model)
{
var existingRateCheck = await _taxRateRepository.GetByLocationAsync(new TaxRate() { Country = model.Country, PostalCode = model.PostalCode });
Expand Down Expand Up @@ -429,6 +445,7 @@ public async Task<IActionResult> TaxRateAddEdit(TaxRateAddEditModel model)
return RedirectToAction("TaxRate");
}

[RequirePermission(Permission.Tools_ManageTaxRates)]
public async Task<IActionResult> TaxRateArchive(string stripeTaxRateId)
{
if (!string.IsNullOrWhiteSpace(stripeTaxRateId))
Expand All @@ -439,6 +456,7 @@ public async Task<IActionResult> TaxRateArchive(string stripeTaxRateId)
return RedirectToAction("TaxRate");
}

[RequirePermission(Permission.Tools_ManageStripeSubscriptions)]
public async Task<IActionResult> StripeSubscriptions(StripeSubscriptionListOptions options)
{
options = options ?? new StripeSubscriptionListOptions();
Expand All @@ -465,6 +483,7 @@ public async Task<IActionResult> StripeSubscriptions(StripeSubscriptionListOptio
}

[HttpPost]
[RequirePermission(Permission.Tools_ManageStripeSubscriptions)]
public async Task<IActionResult> StripeSubscriptions([FromForm] StripeSubscriptionsModel model)
{
if (!ModelState.IsValid)
Expand Down
36 changes: 33 additions & 3 deletions src/Admin/Controllers/UsersController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using Bit.Admin.Models;
using Bit.Admin.Enums;
using Bit.Admin.Models;
using Bit.Admin.Services;
using Bit.Admin.Utilities;
using Bit.Core.Entities;
using Bit.Core.Repositories;
using Bit.Core.Services;
Expand All @@ -17,19 +20,23 @@ public class UsersController : Controller
private readonly ICipherRepository _cipherRepository;
private readonly IPaymentService _paymentService;
private readonly GlobalSettings _globalSettings;
private readonly IAccessControlService _accessControlService;

public UsersController(
IUserRepository userRepository,
ICipherRepository cipherRepository,
IPaymentService paymentService,
GlobalSettings globalSettings)
GlobalSettings globalSettings,
IAccessControlService accessControlService)
{
_userRepository = userRepository;
_cipherRepository = cipherRepository;
_paymentService = paymentService;
_globalSettings = globalSettings;
_accessControlService = accessControlService;
}

[RequirePermission(Permission.User_List_View)]
public async Task<IActionResult> Index(string email, int page = 1, int count = 25)
{
if (page < 1)
Expand Down Expand Up @@ -91,13 +98,36 @@ public async Task<IActionResult> Edit(Guid id, UserEditModel model)
return RedirectToAction("Index");
}

model.ToUser(user);
var canUpgradePremium = _accessControlService.UserHasPermission(Permission.User_UpgradePremium);

if (_accessControlService.UserHasPermission(Permission.User_Premium_Edit) ||
canUpgradePremium)
{
user.MaxStorageGb = model.MaxStorageGb;
user.Premium = model.Premium;
}

if (_accessControlService.UserHasPermission(Permission.User_Billing_Edit))
{
user.Gateway = model.Gateway;
user.GatewayCustomerId = model.GatewayCustomerId;
user.GatewaySubscriptionId = model.GatewaySubscriptionId;
}

if (_accessControlService.UserHasPermission(Permission.User_Licensing_Edit) ||
canUpgradePremium)
{
user.LicenseKey = model.LicenseKey;
user.PremiumExpirationDate = model.PremiumExpirationDate;
}

await _userRepository.ReplaceAsync(user);
return RedirectToAction("Edit", new { id });
}

[HttpPost]
[ValidateAntiForgeryToken]
[RequirePermission(Permission.User_Delete)]
public async Task<IActionResult> Delete(Guid id)
{
var user = await _userRepository.GetByIdAsync(id);
Expand Down
Loading