2019-02-15 16:18:34 -05:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
2020-02-14 20:13:25 -05:00
|
|
|
|
using System.Linq;
|
2020-08-18 17:00:21 -04:00
|
|
|
|
using System.Text;
|
2019-02-15 16:18:34 -05:00
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
using Bit.Admin.Models;
|
|
|
|
|
|
using Bit.Core;
|
2020-08-18 17:00:21 -04:00
|
|
|
|
using Bit.Core.Models.Table;
|
2019-02-25 12:43:20 -05:00
|
|
|
|
using Bit.Core.Repositories;
|
2020-08-18 17:00:21 -04:00
|
|
|
|
using Bit.Core.Services;
|
2019-02-15 16:18:34 -05:00
|
|
|
|
using Bit.Core.Utilities;
|
|
|
|
|
|
using Microsoft.AspNetCore.Authorization;
|
|
|
|
|
|
using Microsoft.AspNetCore.Mvc;
|
2020-08-18 17:00:21 -04:00
|
|
|
|
using Newtonsoft.Json;
|
2019-02-15 16:18:34 -05:00
|
|
|
|
|
|
|
|
|
|
namespace Bit.Admin.Controllers
|
|
|
|
|
|
{
|
|
|
|
|
|
[Authorize]
|
|
|
|
|
|
[SelfHosted(NotSelfHostedOnly = true)]
|
|
|
|
|
|
public class ToolsController : Controller
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly GlobalSettings _globalSettings;
|
2020-08-18 17:00:21 -04:00
|
|
|
|
private readonly IOrganizationRepository _organizationRepository;
|
|
|
|
|
|
private readonly IOrganizationService _organizationService;
|
|
|
|
|
|
private readonly IUserService _userService;
|
2019-02-25 12:43:20 -05:00
|
|
|
|
private readonly ITransactionRepository _transactionRepository;
|
2020-08-18 17:00:21 -04:00
|
|
|
|
private readonly IInstallationRepository _installationRepository;
|
2020-02-14 20:13:25 -05:00
|
|
|
|
private readonly IOrganizationUserRepository _organizationUserRepository;
|
2019-02-15 16:18:34 -05:00
|
|
|
|
|
2019-02-25 12:43:20 -05:00
|
|
|
|
public ToolsController(
|
|
|
|
|
|
GlobalSettings globalSettings,
|
2020-08-18 17:00:21 -04:00
|
|
|
|
IOrganizationRepository organizationRepository,
|
|
|
|
|
|
IOrganizationService organizationService,
|
|
|
|
|
|
IUserService userService,
|
2020-02-14 20:13:25 -05:00
|
|
|
|
ITransactionRepository transactionRepository,
|
2020-08-18 17:00:21 -04:00
|
|
|
|
IInstallationRepository installationRepository,
|
2020-02-14 20:13:25 -05:00
|
|
|
|
IOrganizationUserRepository organizationUserRepository)
|
2019-02-15 16:18:34 -05:00
|
|
|
|
{
|
|
|
|
|
|
_globalSettings = globalSettings;
|
2020-08-18 17:00:21 -04:00
|
|
|
|
_organizationRepository = organizationRepository;
|
|
|
|
|
|
_organizationService = organizationService;
|
|
|
|
|
|
_userService = userService;
|
2019-02-25 12:43:20 -05:00
|
|
|
|
_transactionRepository = transactionRepository;
|
2020-08-18 17:00:21 -04:00
|
|
|
|
_installationRepository = installationRepository;
|
2020-02-14 20:13:25 -05:00
|
|
|
|
_organizationUserRepository = organizationUserRepository;
|
2019-02-15 16:18:34 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-02-20 23:49:54 -05:00
|
|
|
|
public IActionResult ChargeBraintree()
|
2019-02-15 16:18:34 -05:00
|
|
|
|
{
|
|
|
|
|
|
return View(new ChargeBraintreeModel());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[HttpPost]
|
|
|
|
|
|
public async Task<IActionResult> ChargeBraintree(ChargeBraintreeModel model)
|
|
|
|
|
|
{
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (!ModelState.IsValid)
|
2019-02-15 16:18:34 -05:00
|
|
|
|
{
|
|
|
|
|
|
return View(model);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var btGateway = new Braintree.BraintreeGateway
|
|
|
|
|
|
{
|
|
|
|
|
|
Environment = _globalSettings.Braintree.Production ?
|
|
|
|
|
|
Braintree.Environment.PRODUCTION : Braintree.Environment.SANDBOX,
|
|
|
|
|
|
MerchantId = _globalSettings.Braintree.MerchantId,
|
|
|
|
|
|
PublicKey = _globalSettings.Braintree.PublicKey,
|
|
|
|
|
|
PrivateKey = _globalSettings.Braintree.PrivateKey
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var btObjIdField = model.Id[0] == 'o' ? "organization_id" : "user_id";
|
|
|
|
|
|
var btObjId = new Guid(model.Id.Substring(1, 32));
|
|
|
|
|
|
|
|
|
|
|
|
var transactionResult = await btGateway.Transaction.SaleAsync(
|
|
|
|
|
|
new Braintree.TransactionRequest
|
|
|
|
|
|
{
|
|
|
|
|
|
Amount = model.Amount.Value,
|
|
|
|
|
|
CustomerId = model.Id,
|
|
|
|
|
|
Options = new Braintree.TransactionOptionsRequest
|
|
|
|
|
|
{
|
|
|
|
|
|
SubmitForSettlement = true,
|
|
|
|
|
|
PayPal = new Braintree.TransactionOptionsPayPalRequest
|
|
|
|
|
|
{
|
|
|
|
|
|
CustomField = $"{btObjIdField}:{btObjId}"
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
CustomFields = new Dictionary<string, string>
|
|
|
|
|
|
{
|
|
|
|
|
|
[btObjIdField] = btObjId.ToString()
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (!transactionResult.IsSuccess())
|
2019-02-15 16:18:34 -05:00
|
|
|
|
{
|
2019-02-19 21:27:07 -05:00
|
|
|
|
ModelState.AddModelError(string.Empty, "Charge failed. " +
|
|
|
|
|
|
"Refer to Braintree admin portal for more information.");
|
2019-02-15 16:18:34 -05:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
model.TransactionId = transactionResult.Target.Id;
|
|
|
|
|
|
model.PayPalTransactionId = transactionResult.Target?.PayPalDetails?.CaptureId;
|
|
|
|
|
|
}
|
|
|
|
|
|
return View(model);
|
|
|
|
|
|
}
|
2019-02-25 12:43:20 -05:00
|
|
|
|
|
|
|
|
|
|
public IActionResult CreateTransaction(Guid? organizationId = null, Guid? userId = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return View("CreateUpdateTransaction", new CreateUpdateTransactionModel
|
|
|
|
|
|
{
|
|
|
|
|
|
OrganizationId = organizationId,
|
|
|
|
|
|
UserId = userId
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[HttpPost]
|
|
|
|
|
|
public async Task<IActionResult> CreateTransaction(CreateUpdateTransactionModel model)
|
|
|
|
|
|
{
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (!ModelState.IsValid)
|
2019-02-25 12:43:20 -05:00
|
|
|
|
{
|
|
|
|
|
|
return View("CreateUpdateTransaction", model);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await _transactionRepository.CreateAsync(model.ToTransaction());
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (model.UserId.HasValue)
|
2019-02-25 12:43:20 -05:00
|
|
|
|
{
|
|
|
|
|
|
return RedirectToAction("Edit", "Users", new { id = model.UserId });
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
return RedirectToAction("Edit", "Organizations", new { id = model.OrganizationId });
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<IActionResult> EditTransaction(Guid id)
|
|
|
|
|
|
{
|
|
|
|
|
|
var transaction = await _transactionRepository.GetByIdAsync(id);
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (transaction == null)
|
2019-02-25 12:43:20 -05:00
|
|
|
|
{
|
|
|
|
|
|
return RedirectToAction("Index", "Home");
|
|
|
|
|
|
}
|
|
|
|
|
|
return View("CreateUpdateTransaction", new CreateUpdateTransactionModel(transaction));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[HttpPost]
|
|
|
|
|
|
public async Task<IActionResult> EditTransaction(Guid id, CreateUpdateTransactionModel model)
|
|
|
|
|
|
{
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (!ModelState.IsValid)
|
2019-02-25 12:43:20 -05:00
|
|
|
|
{
|
|
|
|
|
|
return View("CreateUpdateTransaction", model);
|
|
|
|
|
|
}
|
|
|
|
|
|
await _transactionRepository.ReplaceAsync(model.ToTransaction(id));
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (model.UserId.HasValue)
|
2019-02-25 12:43:20 -05:00
|
|
|
|
{
|
|
|
|
|
|
return RedirectToAction("Edit", "Users", new { id = model.UserId });
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
return RedirectToAction("Edit", "Organizations", new { id = model.OrganizationId });
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2020-02-14 20:13:25 -05:00
|
|
|
|
|
|
|
|
|
|
public IActionResult PromoteAdmin()
|
|
|
|
|
|
{
|
2020-08-18 17:00:21 -04:00
|
|
|
|
return View();
|
2020-02-14 20:13:25 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[HttpPost]
|
|
|
|
|
|
public async Task<IActionResult> PromoteAdmin(PromoteAdminModel model)
|
|
|
|
|
|
{
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (!ModelState.IsValid)
|
2020-02-14 20:13:25 -05:00
|
|
|
|
{
|
2020-08-18 17:00:21 -04:00
|
|
|
|
return View(model);
|
2020-02-14 20:13:25 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var orgUsers = await _organizationUserRepository.GetManyByOrganizationAsync(
|
|
|
|
|
|
model.OrganizationId.Value, null);
|
|
|
|
|
|
var user = orgUsers.FirstOrDefault(u => u.UserId == model.UserId.Value);
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (user == null)
|
2020-02-14 20:13:25 -05:00
|
|
|
|
{
|
|
|
|
|
|
ModelState.AddModelError(nameof(model.UserId), "User Id not found in this organization.");
|
|
|
|
|
|
}
|
2020-03-27 14:36:37 -04:00
|
|
|
|
else if (user.Type != Core.Enums.OrganizationUserType.Admin)
|
2020-02-14 20:13:25 -05:00
|
|
|
|
{
|
|
|
|
|
|
ModelState.AddModelError(nameof(model.UserId), "User is not an admin of this organization.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-03-27 14:36:37 -04:00
|
|
|
|
if (!ModelState.IsValid)
|
2020-02-14 20:13:25 -05:00
|
|
|
|
{
|
2020-08-18 17:00:21 -04:00
|
|
|
|
return View(model);
|
2020-02-14 20:13:25 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
user.Type = Core.Enums.OrganizationUserType.Owner;
|
|
|
|
|
|
await _organizationUserRepository.ReplaceAsync(user);
|
|
|
|
|
|
return RedirectToAction("Edit", "Organizations", new { id = model.OrganizationId.Value });
|
|
|
|
|
|
}
|
2020-08-18 17:00:21 -04:00
|
|
|
|
|
|
|
|
|
|
public IActionResult GenerateLicense()
|
|
|
|
|
|
{
|
|
|
|
|
|
return View();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[HttpPost]
|
|
|
|
|
|
public async Task<IActionResult> GenerateLicense(LicenseModel model)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!ModelState.IsValid)
|
|
|
|
|
|
{
|
|
|
|
|
|
return View(model);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
User user = null;
|
|
|
|
|
|
Organization organization = null;
|
|
|
|
|
|
if (model.UserId.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
user = await _userService.GetUserByIdAsync(model.UserId.Value);
|
|
|
|
|
|
if (user == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
ModelState.AddModelError(nameof(model.UserId), "User Id not found.");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (model.OrganizationId.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
organization = await _organizationRepository.GetByIdAsync(model.OrganizationId.Value);
|
|
|
|
|
|
if (organization == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
ModelState.AddModelError(nameof(model.OrganizationId), "Organization not found.");
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (!organization.Enabled)
|
|
|
|
|
|
{
|
|
|
|
|
|
ModelState.AddModelError(nameof(model.OrganizationId), "Organization is disabled.");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (model.InstallationId.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
var installation = await _installationRepository.GetByIdAsync(model.InstallationId.Value);
|
|
|
|
|
|
if (installation == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
ModelState.AddModelError(nameof(model.InstallationId), "Installation not found.");
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (!installation.Enabled)
|
|
|
|
|
|
{
|
|
|
|
|
|
ModelState.AddModelError(nameof(model.OrganizationId), "Installation is disabled.");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!ModelState.IsValid)
|
|
|
|
|
|
{
|
|
|
|
|
|
return View(model);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (organization != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var license = await _organizationService.GenerateLicenseAsync(organization,
|
|
|
|
|
|
model.InstallationId.Value, model.Version);
|
|
|
|
|
|
return File(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(license, Formatting.Indented)),
|
|
|
|
|
|
"text/plain", "bitwarden_organization_license.json");
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (user != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var license = await _userService.GenerateLicenseAsync(user, null, model.Version);
|
|
|
|
|
|
return File(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(license, Formatting.Indented)),
|
|
|
|
|
|
"text/plain", "bitwarden_premium_license.json");
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new Exception("No license to generate.");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2019-02-15 16:18:34 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|