mirror of
https://github.com/bitwarden/server.git
synced 2025-04-17 19:18:16 -05:00
Merge branch 'main' into add-docker-arm64-builds
This commit is contained in:
commit
08543c60df
20
.github/workflows/test-database.yml
vendored
20
.github/workflows/test-database.yml
vendored
@ -32,28 +32,10 @@ on:
|
|||||||
- "src/**/Entities/**/*.cs" # Database entity definitions
|
- "src/**/Entities/**/*.cs" # Database entity definitions
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check-test-secrets:
|
|
||||||
name: Check for test secrets
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
outputs:
|
|
||||||
available: ${{ steps.check-test-secrets.outputs.available }}
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Check
|
|
||||||
id: check-test-secrets
|
|
||||||
run: |
|
|
||||||
if [ "${{ secrets.CODECOV_TOKEN }}" != '' ]; then
|
|
||||||
echo "available=true" >> $GITHUB_OUTPUT;
|
|
||||||
else
|
|
||||||
echo "available=false" >> $GITHUB_OUTPUT;
|
|
||||||
fi
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
name: Run tests
|
name: Run tests
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
needs: check-test-secrets
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out repo
|
- name: Check out repo
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
@ -167,7 +149,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Report test results
|
- name: Report test results
|
||||||
uses: dorny/test-reporter@31a54ee7ebcacc03a09ea97a7e5465a47b84aea5 # v1.9.1
|
uses: dorny/test-reporter@31a54ee7ebcacc03a09ea97a7e5465a47b84aea5 # v1.9.1
|
||||||
if: ${{ needs.check-test-secrets.outputs.available == 'true' && !cancelled() }}
|
if: ${{ github.event.pull_request.head.repo.full_name == github.repository && && !cancelled() }}
|
||||||
with:
|
with:
|
||||||
name: Test Results
|
name: Test Results
|
||||||
path: "**/*-test-results.trx"
|
path: "**/*-test-results.trx"
|
||||||
|
20
.github/workflows/test.yml
vendored
20
.github/workflows/test.yml
vendored
@ -13,29 +13,11 @@ env:
|
|||||||
_AZ_REGISTRY: "bitwardenprod.azurecr.io"
|
_AZ_REGISTRY: "bitwardenprod.azurecr.io"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check-test-secrets:
|
|
||||||
name: Check for test secrets
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
outputs:
|
|
||||||
available: ${{ steps.check-test-secrets.outputs.available }}
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Check
|
|
||||||
id: check-test-secrets
|
|
||||||
run: |
|
|
||||||
if [ "${{ secrets.CODECOV_TOKEN }}" != '' ]; then
|
|
||||||
echo "available=true" >> $GITHUB_OUTPUT;
|
|
||||||
else
|
|
||||||
echo "available=false" >> $GITHUB_OUTPUT;
|
|
||||||
fi
|
|
||||||
|
|
||||||
testing:
|
testing:
|
||||||
name: Run tests
|
name: Run tests
|
||||||
if: ${{ startsWith(github.head_ref, 'version_bump_') == false }}
|
if: ${{ startsWith(github.head_ref, 'version_bump_') == false }}
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
needs: check-test-secrets
|
|
||||||
permissions:
|
permissions:
|
||||||
checks: write
|
checks: write
|
||||||
contents: read
|
contents: read
|
||||||
@ -69,7 +51,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Report test results
|
- name: Report test results
|
||||||
uses: dorny/test-reporter@31a54ee7ebcacc03a09ea97a7e5465a47b84aea5 # v1.9.1
|
uses: dorny/test-reporter@31a54ee7ebcacc03a09ea97a7e5465a47b84aea5 # v1.9.1
|
||||||
if: ${{ needs.check-test-secrets.outputs.available == 'true' && !cancelled() }}
|
if: ${{ github.event.pull_request.head.repo.full_name == github.repository && !cancelled() }}
|
||||||
with:
|
with:
|
||||||
name: Test Results
|
name: Test Results
|
||||||
path: "**/*-test-results.trx"
|
path: "**/*-test-results.trx"
|
||||||
|
@ -4,11 +4,9 @@ using Bit.Api.Auth.Models.Request;
|
|||||||
using Bit.Api.Auth.Models.Request.Accounts;
|
using Bit.Api.Auth.Models.Request.Accounts;
|
||||||
using Bit.Api.Auth.Models.Request.WebAuthn;
|
using Bit.Api.Auth.Models.Request.WebAuthn;
|
||||||
using Bit.Api.KeyManagement.Validators;
|
using Bit.Api.KeyManagement.Validators;
|
||||||
using Bit.Api.Models.Request;
|
|
||||||
using Bit.Api.Models.Request.Accounts;
|
using Bit.Api.Models.Request.Accounts;
|
||||||
using Bit.Api.Models.Response;
|
using Bit.Api.Models.Response;
|
||||||
using Bit.Api.Tools.Models.Request;
|
using Bit.Api.Tools.Models.Request;
|
||||||
using Bit.Api.Utilities;
|
|
||||||
using Bit.Api.Vault.Models.Request;
|
using Bit.Api.Vault.Models.Request;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Core.AdminConsole.Enums.Provider;
|
using Bit.Core.AdminConsole.Enums.Provider;
|
||||||
@ -19,23 +17,15 @@ using Bit.Core.Auth.Models.Api.Request.Accounts;
|
|||||||
using Bit.Core.Auth.Models.Data;
|
using Bit.Core.Auth.Models.Data;
|
||||||
using Bit.Core.Auth.UserFeatures.TdeOffboardingPassword.Interfaces;
|
using Bit.Core.Auth.UserFeatures.TdeOffboardingPassword.Interfaces;
|
||||||
using Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces;
|
using Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces;
|
||||||
using Bit.Core.Billing.Models;
|
|
||||||
using Bit.Core.Billing.Services;
|
|
||||||
using Bit.Core.Context;
|
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.KeyManagement.Models.Data;
|
using Bit.Core.KeyManagement.Models.Data;
|
||||||
using Bit.Core.KeyManagement.UserKey;
|
using Bit.Core.KeyManagement.UserKey;
|
||||||
using Bit.Core.Models.Api.Response;
|
using Bit.Core.Models.Api.Response;
|
||||||
using Bit.Core.Models.Business;
|
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Settings;
|
|
||||||
using Bit.Core.Tools.Entities;
|
using Bit.Core.Tools.Entities;
|
||||||
using Bit.Core.Tools.Enums;
|
|
||||||
using Bit.Core.Tools.Models.Business;
|
|
||||||
using Bit.Core.Tools.Services;
|
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Bit.Core.Vault.Entities;
|
using Bit.Core.Vault.Entities;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
@ -47,20 +37,15 @@ namespace Bit.Api.Auth.Controllers;
|
|||||||
[Authorize("Application")]
|
[Authorize("Application")]
|
||||||
public class AccountsController : Controller
|
public class AccountsController : Controller
|
||||||
{
|
{
|
||||||
private readonly GlobalSettings _globalSettings;
|
|
||||||
private readonly IOrganizationService _organizationService;
|
private readonly IOrganizationService _organizationService;
|
||||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||||
private readonly IProviderUserRepository _providerUserRepository;
|
private readonly IProviderUserRepository _providerUserRepository;
|
||||||
private readonly IPaymentService _paymentService;
|
|
||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
private readonly IPolicyService _policyService;
|
private readonly IPolicyService _policyService;
|
||||||
private readonly ISetInitialMasterPasswordCommand _setInitialMasterPasswordCommand;
|
private readonly ISetInitialMasterPasswordCommand _setInitialMasterPasswordCommand;
|
||||||
private readonly ITdeOffboardingPasswordCommand _tdeOffboardingPasswordCommand;
|
private readonly ITdeOffboardingPasswordCommand _tdeOffboardingPasswordCommand;
|
||||||
private readonly IRotateUserKeyCommand _rotateUserKeyCommand;
|
private readonly IRotateUserKeyCommand _rotateUserKeyCommand;
|
||||||
private readonly IFeatureService _featureService;
|
private readonly IFeatureService _featureService;
|
||||||
private readonly ISubscriberService _subscriberService;
|
|
||||||
private readonly IReferenceEventService _referenceEventService;
|
|
||||||
private readonly ICurrentContext _currentContext;
|
|
||||||
|
|
||||||
private readonly IRotationValidator<IEnumerable<CipherWithIdRequestModel>, IEnumerable<Cipher>> _cipherValidator;
|
private readonly IRotationValidator<IEnumerable<CipherWithIdRequestModel>, IEnumerable<Cipher>> _cipherValidator;
|
||||||
private readonly IRotationValidator<IEnumerable<FolderWithIdRequestModel>, IEnumerable<Folder>> _folderValidator;
|
private readonly IRotationValidator<IEnumerable<FolderWithIdRequestModel>, IEnumerable<Folder>> _folderValidator;
|
||||||
@ -75,20 +60,15 @@ public class AccountsController : Controller
|
|||||||
|
|
||||||
|
|
||||||
public AccountsController(
|
public AccountsController(
|
||||||
GlobalSettings globalSettings,
|
|
||||||
IOrganizationService organizationService,
|
IOrganizationService organizationService,
|
||||||
IOrganizationUserRepository organizationUserRepository,
|
IOrganizationUserRepository organizationUserRepository,
|
||||||
IProviderUserRepository providerUserRepository,
|
IProviderUserRepository providerUserRepository,
|
||||||
IPaymentService paymentService,
|
|
||||||
IUserService userService,
|
IUserService userService,
|
||||||
IPolicyService policyService,
|
IPolicyService policyService,
|
||||||
ISetInitialMasterPasswordCommand setInitialMasterPasswordCommand,
|
ISetInitialMasterPasswordCommand setInitialMasterPasswordCommand,
|
||||||
ITdeOffboardingPasswordCommand tdeOffboardingPasswordCommand,
|
ITdeOffboardingPasswordCommand tdeOffboardingPasswordCommand,
|
||||||
IRotateUserKeyCommand rotateUserKeyCommand,
|
IRotateUserKeyCommand rotateUserKeyCommand,
|
||||||
IFeatureService featureService,
|
IFeatureService featureService,
|
||||||
ISubscriberService subscriberService,
|
|
||||||
IReferenceEventService referenceEventService,
|
|
||||||
ICurrentContext currentContext,
|
|
||||||
IRotationValidator<IEnumerable<CipherWithIdRequestModel>, IEnumerable<Cipher>> cipherValidator,
|
IRotationValidator<IEnumerable<CipherWithIdRequestModel>, IEnumerable<Cipher>> cipherValidator,
|
||||||
IRotationValidator<IEnumerable<FolderWithIdRequestModel>, IEnumerable<Folder>> folderValidator,
|
IRotationValidator<IEnumerable<FolderWithIdRequestModel>, IEnumerable<Folder>> folderValidator,
|
||||||
IRotationValidator<IEnumerable<SendWithIdRequestModel>, IReadOnlyList<Send>> sendValidator,
|
IRotationValidator<IEnumerable<SendWithIdRequestModel>, IReadOnlyList<Send>> sendValidator,
|
||||||
@ -99,20 +79,15 @@ public class AccountsController : Controller
|
|||||||
IRotationValidator<IEnumerable<WebAuthnLoginRotateKeyRequestModel>, IEnumerable<WebAuthnLoginRotateKeyData>> webAuthnKeyValidator
|
IRotationValidator<IEnumerable<WebAuthnLoginRotateKeyRequestModel>, IEnumerable<WebAuthnLoginRotateKeyData>> webAuthnKeyValidator
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_globalSettings = globalSettings;
|
|
||||||
_organizationService = organizationService;
|
_organizationService = organizationService;
|
||||||
_organizationUserRepository = organizationUserRepository;
|
_organizationUserRepository = organizationUserRepository;
|
||||||
_providerUserRepository = providerUserRepository;
|
_providerUserRepository = providerUserRepository;
|
||||||
_paymentService = paymentService;
|
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
_policyService = policyService;
|
_policyService = policyService;
|
||||||
_setInitialMasterPasswordCommand = setInitialMasterPasswordCommand;
|
_setInitialMasterPasswordCommand = setInitialMasterPasswordCommand;
|
||||||
_tdeOffboardingPasswordCommand = tdeOffboardingPasswordCommand;
|
_tdeOffboardingPasswordCommand = tdeOffboardingPasswordCommand;
|
||||||
_rotateUserKeyCommand = rotateUserKeyCommand;
|
_rotateUserKeyCommand = rotateUserKeyCommand;
|
||||||
_featureService = featureService;
|
_featureService = featureService;
|
||||||
_subscriberService = subscriberService;
|
|
||||||
_referenceEventService = referenceEventService;
|
|
||||||
_currentContext = currentContext;
|
|
||||||
_cipherValidator = cipherValidator;
|
_cipherValidator = cipherValidator;
|
||||||
_folderValidator = folderValidator;
|
_folderValidator = folderValidator;
|
||||||
_sendValidator = sendValidator;
|
_sendValidator = sendValidator;
|
||||||
@ -638,212 +613,6 @@ public class AccountsController : Controller
|
|||||||
throw new BadRequestException(ModelState);
|
throw new BadRequestException(ModelState);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("premium")]
|
|
||||||
public async Task<PaymentResponseModel> PostPremium(PremiumRequestModel model)
|
|
||||||
{
|
|
||||||
var user = await _userService.GetUserByPrincipalAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
throw new UnauthorizedAccessException();
|
|
||||||
}
|
|
||||||
|
|
||||||
var valid = model.Validate(_globalSettings);
|
|
||||||
UserLicense license = null;
|
|
||||||
if (valid && _globalSettings.SelfHosted)
|
|
||||||
{
|
|
||||||
license = await ApiHelpers.ReadJsonFileFromBody<UserLicense>(HttpContext, model.License);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!valid && !_globalSettings.SelfHosted && string.IsNullOrWhiteSpace(model.Country))
|
|
||||||
{
|
|
||||||
throw new BadRequestException("Country is required.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!valid || (_globalSettings.SelfHosted && license == null))
|
|
||||||
{
|
|
||||||
throw new BadRequestException("Invalid license.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = await _userService.SignUpPremiumAsync(user, model.PaymentToken,
|
|
||||||
model.PaymentMethodType.Value, model.AdditionalStorageGb.GetValueOrDefault(0), license,
|
|
||||||
new TaxInfo
|
|
||||||
{
|
|
||||||
BillingAddressCountry = model.Country,
|
|
||||||
BillingAddressPostalCode = model.PostalCode
|
|
||||||
});
|
|
||||||
|
|
||||||
var userTwoFactorEnabled = await _userService.TwoFactorIsEnabledAsync(user);
|
|
||||||
var userHasPremiumFromOrganization = await _userService.HasPremiumFromOrganization(user);
|
|
||||||
var organizationIdsManagingActiveUser = await GetOrganizationIdsManagingUserAsync(user.Id);
|
|
||||||
|
|
||||||
var profile = new ProfileResponseModel(user, null, null, null, userTwoFactorEnabled, userHasPremiumFromOrganization, organizationIdsManagingActiveUser);
|
|
||||||
return new PaymentResponseModel
|
|
||||||
{
|
|
||||||
UserProfile = profile,
|
|
||||||
PaymentIntentClientSecret = result.Item2,
|
|
||||||
Success = result.Item1
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("subscription")]
|
|
||||||
public async Task<SubscriptionResponseModel> GetSubscription()
|
|
||||||
{
|
|
||||||
var user = await _userService.GetUserByPrincipalAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
throw new UnauthorizedAccessException();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_globalSettings.SelfHosted && user.Gateway != null)
|
|
||||||
{
|
|
||||||
var subscriptionInfo = await _paymentService.GetSubscriptionAsync(user);
|
|
||||||
var license = await _userService.GenerateLicenseAsync(user, subscriptionInfo);
|
|
||||||
return new SubscriptionResponseModel(user, subscriptionInfo, license);
|
|
||||||
}
|
|
||||||
else if (!_globalSettings.SelfHosted)
|
|
||||||
{
|
|
||||||
var license = await _userService.GenerateLicenseAsync(user);
|
|
||||||
return new SubscriptionResponseModel(user, license);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return new SubscriptionResponseModel(user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("payment")]
|
|
||||||
[SelfHosted(NotSelfHostedOnly = true)]
|
|
||||||
public async Task PostPayment([FromBody] PaymentRequestModel model)
|
|
||||||
{
|
|
||||||
var user = await _userService.GetUserByPrincipalAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
throw new UnauthorizedAccessException();
|
|
||||||
}
|
|
||||||
|
|
||||||
await _userService.ReplacePaymentMethodAsync(user, model.PaymentToken, model.PaymentMethodType.Value,
|
|
||||||
new TaxInfo
|
|
||||||
{
|
|
||||||
BillingAddressLine1 = model.Line1,
|
|
||||||
BillingAddressLine2 = model.Line2,
|
|
||||||
BillingAddressCity = model.City,
|
|
||||||
BillingAddressState = model.State,
|
|
||||||
BillingAddressCountry = model.Country,
|
|
||||||
BillingAddressPostalCode = model.PostalCode,
|
|
||||||
TaxIdNumber = model.TaxId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("storage")]
|
|
||||||
[SelfHosted(NotSelfHostedOnly = true)]
|
|
||||||
public async Task<PaymentResponseModel> PostStorage([FromBody] StorageRequestModel model)
|
|
||||||
{
|
|
||||||
var user = await _userService.GetUserByPrincipalAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
throw new UnauthorizedAccessException();
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = await _userService.AdjustStorageAsync(user, model.StorageGbAdjustment.Value);
|
|
||||||
return new PaymentResponseModel
|
|
||||||
{
|
|
||||||
Success = true,
|
|
||||||
PaymentIntentClientSecret = result
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("license")]
|
|
||||||
[SelfHosted(SelfHostedOnly = true)]
|
|
||||||
public async Task PostLicense(LicenseRequestModel model)
|
|
||||||
{
|
|
||||||
var user = await _userService.GetUserByPrincipalAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
throw new UnauthorizedAccessException();
|
|
||||||
}
|
|
||||||
|
|
||||||
var license = await ApiHelpers.ReadJsonFileFromBody<UserLicense>(HttpContext, model.License);
|
|
||||||
if (license == null)
|
|
||||||
{
|
|
||||||
throw new BadRequestException("Invalid license");
|
|
||||||
}
|
|
||||||
|
|
||||||
await _userService.UpdateLicenseAsync(user, license);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("cancel")]
|
|
||||||
public async Task PostCancel([FromBody] SubscriptionCancellationRequestModel request)
|
|
||||||
{
|
|
||||||
var user = await _userService.GetUserByPrincipalAsync(User);
|
|
||||||
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
throw new UnauthorizedAccessException();
|
|
||||||
}
|
|
||||||
|
|
||||||
await _subscriberService.CancelSubscription(user,
|
|
||||||
new OffboardingSurveyResponse
|
|
||||||
{
|
|
||||||
UserId = user.Id,
|
|
||||||
Reason = request.Reason,
|
|
||||||
Feedback = request.Feedback
|
|
||||||
},
|
|
||||||
user.IsExpired());
|
|
||||||
|
|
||||||
await _referenceEventService.RaiseEventAsync(new ReferenceEvent(
|
|
||||||
ReferenceEventType.CancelSubscription,
|
|
||||||
user,
|
|
||||||
_currentContext)
|
|
||||||
{
|
|
||||||
EndOfPeriod = user.IsExpired()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("reinstate-premium")]
|
|
||||||
[SelfHosted(NotSelfHostedOnly = true)]
|
|
||||||
public async Task PostReinstate()
|
|
||||||
{
|
|
||||||
var user = await _userService.GetUserByPrincipalAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
throw new UnauthorizedAccessException();
|
|
||||||
}
|
|
||||||
|
|
||||||
await _userService.ReinstatePremiumAsync(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("tax")]
|
|
||||||
[SelfHosted(NotSelfHostedOnly = true)]
|
|
||||||
public async Task<TaxInfoResponseModel> GetTaxInfo()
|
|
||||||
{
|
|
||||||
var user = await _userService.GetUserByPrincipalAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
throw new UnauthorizedAccessException();
|
|
||||||
}
|
|
||||||
|
|
||||||
var taxInfo = await _paymentService.GetTaxInfoAsync(user);
|
|
||||||
return new TaxInfoResponseModel(taxInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPut("tax")]
|
|
||||||
[SelfHosted(NotSelfHostedOnly = true)]
|
|
||||||
public async Task PutTaxInfo([FromBody] TaxInfoUpdateRequestModel model)
|
|
||||||
{
|
|
||||||
var user = await _userService.GetUserByPrincipalAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
throw new UnauthorizedAccessException();
|
|
||||||
}
|
|
||||||
|
|
||||||
var taxInfo = new TaxInfo
|
|
||||||
{
|
|
||||||
BillingAddressPostalCode = model.PostalCode,
|
|
||||||
BillingAddressCountry = model.Country,
|
|
||||||
};
|
|
||||||
await _paymentService.SaveTaxInfoAsync(user, taxInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpDelete("sso/{organizationId}")]
|
[HttpDelete("sso/{organizationId}")]
|
||||||
public async Task DeleteSsoUser(string organizationId)
|
public async Task DeleteSsoUser(string organizationId)
|
||||||
{
|
{
|
||||||
|
237
src/Api/Billing/Controllers/AccountsController.cs
Normal file
237
src/Api/Billing/Controllers/AccountsController.cs
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
#nullable enable
|
||||||
|
using Bit.Api.Models.Request;
|
||||||
|
using Bit.Api.Models.Request.Accounts;
|
||||||
|
using Bit.Api.Models.Response;
|
||||||
|
using Bit.Api.Utilities;
|
||||||
|
using Bit.Core.Billing.Models;
|
||||||
|
using Bit.Core.Billing.Services;
|
||||||
|
using Bit.Core.Context;
|
||||||
|
using Bit.Core.Exceptions;
|
||||||
|
using Bit.Core.Models.Business;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
using Bit.Core.Settings;
|
||||||
|
using Bit.Core.Tools.Enums;
|
||||||
|
using Bit.Core.Tools.Models.Business;
|
||||||
|
using Bit.Core.Tools.Services;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Bit.Api.Billing.Controllers;
|
||||||
|
|
||||||
|
[Route("accounts")]
|
||||||
|
[Authorize("Application")]
|
||||||
|
public class AccountsController(
|
||||||
|
IUserService userService) : Controller
|
||||||
|
{
|
||||||
|
[HttpPost("premium")]
|
||||||
|
public async Task<PaymentResponseModel> PostPremiumAsync(
|
||||||
|
PremiumRequestModel model,
|
||||||
|
[FromServices] GlobalSettings globalSettings)
|
||||||
|
{
|
||||||
|
var user = await userService.GetUserByPrincipalAsync(User);
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException();
|
||||||
|
}
|
||||||
|
|
||||||
|
var valid = model.Validate(globalSettings);
|
||||||
|
UserLicense? license = null;
|
||||||
|
if (valid && globalSettings.SelfHosted)
|
||||||
|
{
|
||||||
|
license = await ApiHelpers.ReadJsonFileFromBody<UserLicense>(HttpContext, model.License);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!valid && !globalSettings.SelfHosted && string.IsNullOrWhiteSpace(model.Country))
|
||||||
|
{
|
||||||
|
throw new BadRequestException("Country is required.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!valid || (globalSettings.SelfHosted && license == null))
|
||||||
|
{
|
||||||
|
throw new BadRequestException("Invalid license.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await userService.SignUpPremiumAsync(user, model.PaymentToken,
|
||||||
|
model.PaymentMethodType!.Value, model.AdditionalStorageGb.GetValueOrDefault(0), license,
|
||||||
|
new TaxInfo { BillingAddressCountry = model.Country, BillingAddressPostalCode = model.PostalCode });
|
||||||
|
|
||||||
|
var userTwoFactorEnabled = await userService.TwoFactorIsEnabledAsync(user);
|
||||||
|
var userHasPremiumFromOrganization = await userService.HasPremiumFromOrganization(user);
|
||||||
|
var organizationIdsManagingActiveUser = await GetOrganizationIdsManagingUserAsync(user.Id);
|
||||||
|
|
||||||
|
var profile = new ProfileResponseModel(user, null, null, null, userTwoFactorEnabled,
|
||||||
|
userHasPremiumFromOrganization, organizationIdsManagingActiveUser);
|
||||||
|
return new PaymentResponseModel
|
||||||
|
{
|
||||||
|
UserProfile = profile,
|
||||||
|
PaymentIntentClientSecret = result.Item2,
|
||||||
|
Success = result.Item1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("subscription")]
|
||||||
|
public async Task<SubscriptionResponseModel> GetSubscriptionAsync(
|
||||||
|
[FromServices] GlobalSettings globalSettings,
|
||||||
|
[FromServices] IPaymentService paymentService)
|
||||||
|
{
|
||||||
|
var user = await userService.GetUserByPrincipalAsync(User);
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!globalSettings.SelfHosted && user.Gateway != null)
|
||||||
|
{
|
||||||
|
var subscriptionInfo = await paymentService.GetSubscriptionAsync(user);
|
||||||
|
var license = await userService.GenerateLicenseAsync(user, subscriptionInfo);
|
||||||
|
return new SubscriptionResponseModel(user, subscriptionInfo, license);
|
||||||
|
}
|
||||||
|
else if (!globalSettings.SelfHosted)
|
||||||
|
{
|
||||||
|
var license = await userService.GenerateLicenseAsync(user);
|
||||||
|
return new SubscriptionResponseModel(user, license);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new SubscriptionResponseModel(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("payment")]
|
||||||
|
[SelfHosted(NotSelfHostedOnly = true)]
|
||||||
|
public async Task PostPaymentAsync([FromBody] PaymentRequestModel model)
|
||||||
|
{
|
||||||
|
var user = await userService.GetUserByPrincipalAsync(User);
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException();
|
||||||
|
}
|
||||||
|
|
||||||
|
await userService.ReplacePaymentMethodAsync(user, model.PaymentToken, model.PaymentMethodType!.Value,
|
||||||
|
new TaxInfo
|
||||||
|
{
|
||||||
|
BillingAddressLine1 = model.Line1,
|
||||||
|
BillingAddressLine2 = model.Line2,
|
||||||
|
BillingAddressCity = model.City,
|
||||||
|
BillingAddressState = model.State,
|
||||||
|
BillingAddressCountry = model.Country,
|
||||||
|
BillingAddressPostalCode = model.PostalCode,
|
||||||
|
TaxIdNumber = model.TaxId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("storage")]
|
||||||
|
[SelfHosted(NotSelfHostedOnly = true)]
|
||||||
|
public async Task<PaymentResponseModel> PostStorageAsync([FromBody] StorageRequestModel model)
|
||||||
|
{
|
||||||
|
var user = await userService.GetUserByPrincipalAsync(User);
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException();
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await userService.AdjustStorageAsync(user, model.StorageGbAdjustment!.Value);
|
||||||
|
return new PaymentResponseModel { Success = true, PaymentIntentClientSecret = result };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[HttpPost("license")]
|
||||||
|
[SelfHosted(SelfHostedOnly = true)]
|
||||||
|
public async Task PostLicenseAsync(LicenseRequestModel model)
|
||||||
|
{
|
||||||
|
var user = await userService.GetUserByPrincipalAsync(User);
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException();
|
||||||
|
}
|
||||||
|
|
||||||
|
var license = await ApiHelpers.ReadJsonFileFromBody<UserLicense>(HttpContext, model.License);
|
||||||
|
if (license == null)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("Invalid license");
|
||||||
|
}
|
||||||
|
|
||||||
|
await userService.UpdateLicenseAsync(user, license);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("cancel")]
|
||||||
|
public async Task PostCancelAsync(
|
||||||
|
[FromBody] SubscriptionCancellationRequestModel request,
|
||||||
|
[FromServices] ICurrentContext currentContext,
|
||||||
|
[FromServices] IReferenceEventService referenceEventService,
|
||||||
|
[FromServices] ISubscriberService subscriberService)
|
||||||
|
{
|
||||||
|
var user = await userService.GetUserByPrincipalAsync(User);
|
||||||
|
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException();
|
||||||
|
}
|
||||||
|
|
||||||
|
await subscriberService.CancelSubscription(user,
|
||||||
|
new OffboardingSurveyResponse { UserId = user.Id, Reason = request.Reason, Feedback = request.Feedback },
|
||||||
|
user.IsExpired());
|
||||||
|
|
||||||
|
await referenceEventService.RaiseEventAsync(new ReferenceEvent(
|
||||||
|
ReferenceEventType.CancelSubscription,
|
||||||
|
user,
|
||||||
|
currentContext)
|
||||||
|
{ EndOfPeriod = user.IsExpired() });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("reinstate-premium")]
|
||||||
|
[SelfHosted(NotSelfHostedOnly = true)]
|
||||||
|
public async Task PostReinstateAsync()
|
||||||
|
{
|
||||||
|
var user = await userService.GetUserByPrincipalAsync(User);
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException();
|
||||||
|
}
|
||||||
|
|
||||||
|
await userService.ReinstatePremiumAsync(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("tax")]
|
||||||
|
[SelfHosted(NotSelfHostedOnly = true)]
|
||||||
|
public async Task<TaxInfoResponseModel> GetTaxInfoAsync(
|
||||||
|
[FromServices] IPaymentService paymentService)
|
||||||
|
{
|
||||||
|
var user = await userService.GetUserByPrincipalAsync(User);
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException();
|
||||||
|
}
|
||||||
|
|
||||||
|
var taxInfo = await paymentService.GetTaxInfoAsync(user);
|
||||||
|
return new TaxInfoResponseModel(taxInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("tax")]
|
||||||
|
[SelfHosted(NotSelfHostedOnly = true)]
|
||||||
|
public async Task PutTaxInfoAsync(
|
||||||
|
[FromBody] TaxInfoUpdateRequestModel model,
|
||||||
|
[FromServices] IPaymentService paymentService)
|
||||||
|
{
|
||||||
|
var user = await userService.GetUserByPrincipalAsync(User);
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException();
|
||||||
|
}
|
||||||
|
|
||||||
|
var taxInfo = new TaxInfo
|
||||||
|
{
|
||||||
|
BillingAddressPostalCode = model.PostalCode,
|
||||||
|
BillingAddressCountry = model.Country,
|
||||||
|
};
|
||||||
|
await paymentService.SaveTaxInfoAsync(user, taxInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IEnumerable<Guid>> GetOrganizationIdsManagingUserAsync(Guid userId)
|
||||||
|
{
|
||||||
|
var organizationManagingUser = await userService.GetOrganizationsManagingUserAsync(userId);
|
||||||
|
return organizationManagingUser.Select(o => o.Id);
|
||||||
|
}
|
||||||
|
}
|
@ -15,16 +15,12 @@ using Bit.Core.Auth.Models.Api.Request.Accounts;
|
|||||||
using Bit.Core.Auth.Models.Data;
|
using Bit.Core.Auth.Models.Data;
|
||||||
using Bit.Core.Auth.UserFeatures.TdeOffboardingPassword.Interfaces;
|
using Bit.Core.Auth.UserFeatures.TdeOffboardingPassword.Interfaces;
|
||||||
using Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces;
|
using Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces;
|
||||||
using Bit.Core.Billing.Services;
|
|
||||||
using Bit.Core.Context;
|
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.KeyManagement.UserKey;
|
using Bit.Core.KeyManagement.UserKey;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Settings;
|
|
||||||
using Bit.Core.Tools.Entities;
|
using Bit.Core.Tools.Entities;
|
||||||
using Bit.Core.Tools.Services;
|
|
||||||
using Bit.Core.Vault.Entities;
|
using Bit.Core.Vault.Entities;
|
||||||
using Bit.Test.Common.AutoFixture.Attributes;
|
using Bit.Test.Common.AutoFixture.Attributes;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
@ -37,10 +33,8 @@ public class AccountsControllerTests : IDisposable
|
|||||||
{
|
{
|
||||||
|
|
||||||
private readonly AccountsController _sut;
|
private readonly AccountsController _sut;
|
||||||
private readonly GlobalSettings _globalSettings;
|
|
||||||
private readonly IOrganizationService _organizationService;
|
private readonly IOrganizationService _organizationService;
|
||||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||||
private readonly IPaymentService _paymentService;
|
|
||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
private readonly IProviderUserRepository _providerUserRepository;
|
private readonly IProviderUserRepository _providerUserRepository;
|
||||||
private readonly IPolicyService _policyService;
|
private readonly IPolicyService _policyService;
|
||||||
@ -48,9 +42,6 @@ public class AccountsControllerTests : IDisposable
|
|||||||
private readonly IRotateUserKeyCommand _rotateUserKeyCommand;
|
private readonly IRotateUserKeyCommand _rotateUserKeyCommand;
|
||||||
private readonly ITdeOffboardingPasswordCommand _tdeOffboardingPasswordCommand;
|
private readonly ITdeOffboardingPasswordCommand _tdeOffboardingPasswordCommand;
|
||||||
private readonly IFeatureService _featureService;
|
private readonly IFeatureService _featureService;
|
||||||
private readonly ISubscriberService _subscriberService;
|
|
||||||
private readonly IReferenceEventService _referenceEventService;
|
|
||||||
private readonly ICurrentContext _currentContext;
|
|
||||||
|
|
||||||
private readonly IRotationValidator<IEnumerable<CipherWithIdRequestModel>, IEnumerable<Cipher>> _cipherValidator;
|
private readonly IRotationValidator<IEnumerable<CipherWithIdRequestModel>, IEnumerable<Cipher>> _cipherValidator;
|
||||||
private readonly IRotationValidator<IEnumerable<FolderWithIdRequestModel>, IEnumerable<Folder>> _folderValidator;
|
private readonly IRotationValidator<IEnumerable<FolderWithIdRequestModel>, IEnumerable<Folder>> _folderValidator;
|
||||||
@ -70,16 +61,11 @@ public class AccountsControllerTests : IDisposable
|
|||||||
_organizationService = Substitute.For<IOrganizationService>();
|
_organizationService = Substitute.For<IOrganizationService>();
|
||||||
_organizationUserRepository = Substitute.For<IOrganizationUserRepository>();
|
_organizationUserRepository = Substitute.For<IOrganizationUserRepository>();
|
||||||
_providerUserRepository = Substitute.For<IProviderUserRepository>();
|
_providerUserRepository = Substitute.For<IProviderUserRepository>();
|
||||||
_paymentService = Substitute.For<IPaymentService>();
|
|
||||||
_globalSettings = new GlobalSettings();
|
|
||||||
_policyService = Substitute.For<IPolicyService>();
|
_policyService = Substitute.For<IPolicyService>();
|
||||||
_setInitialMasterPasswordCommand = Substitute.For<ISetInitialMasterPasswordCommand>();
|
_setInitialMasterPasswordCommand = Substitute.For<ISetInitialMasterPasswordCommand>();
|
||||||
_rotateUserKeyCommand = Substitute.For<IRotateUserKeyCommand>();
|
_rotateUserKeyCommand = Substitute.For<IRotateUserKeyCommand>();
|
||||||
_tdeOffboardingPasswordCommand = Substitute.For<ITdeOffboardingPasswordCommand>();
|
_tdeOffboardingPasswordCommand = Substitute.For<ITdeOffboardingPasswordCommand>();
|
||||||
_featureService = Substitute.For<IFeatureService>();
|
_featureService = Substitute.For<IFeatureService>();
|
||||||
_subscriberService = Substitute.For<ISubscriberService>();
|
|
||||||
_referenceEventService = Substitute.For<IReferenceEventService>();
|
|
||||||
_currentContext = Substitute.For<ICurrentContext>();
|
|
||||||
_cipherValidator =
|
_cipherValidator =
|
||||||
Substitute.For<IRotationValidator<IEnumerable<CipherWithIdRequestModel>, IEnumerable<Cipher>>>();
|
Substitute.For<IRotationValidator<IEnumerable<CipherWithIdRequestModel>, IEnumerable<Cipher>>>();
|
||||||
_folderValidator =
|
_folderValidator =
|
||||||
@ -93,20 +79,15 @@ public class AccountsControllerTests : IDisposable
|
|||||||
IReadOnlyList<OrganizationUser>>>();
|
IReadOnlyList<OrganizationUser>>>();
|
||||||
|
|
||||||
_sut = new AccountsController(
|
_sut = new AccountsController(
|
||||||
_globalSettings,
|
|
||||||
_organizationService,
|
_organizationService,
|
||||||
_organizationUserRepository,
|
_organizationUserRepository,
|
||||||
_providerUserRepository,
|
_providerUserRepository,
|
||||||
_paymentService,
|
|
||||||
_userService,
|
_userService,
|
||||||
_policyService,
|
_policyService,
|
||||||
_setInitialMasterPasswordCommand,
|
_setInitialMasterPasswordCommand,
|
||||||
_tdeOffboardingPasswordCommand,
|
_tdeOffboardingPasswordCommand,
|
||||||
_rotateUserKeyCommand,
|
_rotateUserKeyCommand,
|
||||||
_featureService,
|
_featureService,
|
||||||
_subscriberService,
|
|
||||||
_referenceEventService,
|
|
||||||
_currentContext,
|
|
||||||
_cipherValidator,
|
_cipherValidator,
|
||||||
_folderValidator,
|
_folderValidator,
|
||||||
_sendValidator,
|
_sendValidator,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user