1
0
mirror of https://github.com/bitwarden/server.git synced 2025-02-01 23:31:41 +01:00

Fix/f4e multiple sponsorships (#1838)

* Use sponosorship from validate to redeem

* Update tests

* Format
This commit is contained in:
Matt Gibson 2022-02-02 13:59:47 -05:00 committed by GitHub
parent 452677e441
commit 8ce4d56a91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 41 additions and 34 deletions

View File

@ -68,14 +68,16 @@ namespace Bit.Api.Controllers
[SelfHosted(NotSelfHostedOnly = true)] [SelfHosted(NotSelfHostedOnly = true)]
public async Task<bool> PreValidateSponsorshipToken([FromQuery] string sponsorshipToken) public async Task<bool> PreValidateSponsorshipToken([FromQuery] string sponsorshipToken)
{ {
return await _organizationsSponsorshipService.ValidateRedemptionTokenAsync(sponsorshipToken, (await CurrentUser).Email); return (await _organizationsSponsorshipService.ValidateRedemptionTokenAsync(sponsorshipToken, (await CurrentUser).Email)).valid;
} }
[HttpPost("redeem")] [HttpPost("redeem")]
[SelfHosted(NotSelfHostedOnly = true)] [SelfHosted(NotSelfHostedOnly = true)]
public async Task RedeemSponsorship([FromQuery] string sponsorshipToken, [FromBody] OrganizationSponsorshipRedeemRequestModel model) public async Task RedeemSponsorship([FromQuery] string sponsorshipToken, [FromBody] OrganizationSponsorshipRedeemRequestModel model)
{ {
if (!await _organizationsSponsorshipService.ValidateRedemptionTokenAsync(sponsorshipToken, (await CurrentUser).Email)) var (valid, sponsorship) = await _organizationsSponsorshipService.ValidateRedemptionTokenAsync(sponsorshipToken, (await CurrentUser).Email);
if (!valid)
{ {
throw new BadRequestException("Failed to parse sponsorship token."); throw new BadRequestException("Failed to parse sponsorship token.");
} }
@ -86,8 +88,7 @@ namespace Bit.Api.Controllers
} }
await _organizationsSponsorshipService.SetUpSponsorshipAsync( await _organizationsSponsorshipService.SetUpSponsorshipAsync(
await _organizationSponsorshipRepository sponsorship,
.GetByOfferedToEmailAsync((await CurrentUser).Email),
// Check org to sponsor's product type // Check org to sponsor's product type
await _organizationRepository.GetByIdAsync(model.SponsoredOrganizationId)); await _organizationRepository.GetByIdAsync(model.SponsoredOrganizationId));
} }

View File

@ -10,6 +10,5 @@ namespace Bit.Core.Repositories
{ {
Task<OrganizationSponsorship> GetBySponsoringOrganizationUserIdAsync(Guid sponsoringOrganizationUserId); Task<OrganizationSponsorship> GetBySponsoringOrganizationUserIdAsync(Guid sponsoringOrganizationUserId);
Task<OrganizationSponsorship> GetBySponsoredOrganizationIdAsync(Guid sponsoredOrganizationId); Task<OrganizationSponsorship> GetBySponsoredOrganizationIdAsync(Guid sponsoredOrganizationId);
Task<OrganizationSponsorship> GetByOfferedToEmailAsync(string email);
} }
} }

View File

@ -7,7 +7,7 @@ namespace Bit.Core.Services
{ {
public interface IOrganizationSponsorshipService public interface IOrganizationSponsorshipService
{ {
Task<bool> ValidateRedemptionTokenAsync(string encryptedToken, string currentUserEmail); Task<(bool valid, OrganizationSponsorship sponsorship)> ValidateRedemptionTokenAsync(string encryptedToken, string currentUserEmail);
Task OfferSponsorshipAsync(Organization sponsoringOrg, OrganizationUser sponsoringOrgUser, Task OfferSponsorshipAsync(Organization sponsoringOrg, OrganizationUser sponsoringOrgUser,
PlanSponsorshipType sponsorshipType, string sponsoredEmail, string friendlyName, string sponsoringUserEmail); PlanSponsorshipType sponsorshipType, string sponsoredEmail, string friendlyName, string sponsoringUserEmail);
Task ResendSponsorshipOfferAsync(Organization sponsoringOrg, OrganizationUser sponsoringOrgUser, Task ResendSponsorshipOfferAsync(Organization sponsoringOrg, OrganizationUser sponsoringOrgUser,

View File

@ -37,11 +37,11 @@ namespace Bit.Core.Services
_dataProtector = dataProtectionProvider.CreateProtector("OrganizationSponsorshipServiceDataProtector"); _dataProtector = dataProtectionProvider.CreateProtector("OrganizationSponsorshipServiceDataProtector");
} }
public async Task<bool> ValidateRedemptionTokenAsync(string encryptedToken, string sponsoredUserEmail) public async Task<(bool valid, OrganizationSponsorship sponsorship)> ValidateRedemptionTokenAsync(string encryptedToken, string sponsoredUserEmail)
{ {
if (!encryptedToken.StartsWith(TokenClearTextPrefix) || sponsoredUserEmail == null) if (!encryptedToken.StartsWith(TokenClearTextPrefix) || sponsoredUserEmail == null)
{ {
return false; return (false, null);
} }
var decryptedToken = _dataProtector.Unprotect(encryptedToken[TokenClearTextPrefix.Length..]); var decryptedToken = _dataProtector.Unprotect(encryptedToken[TokenClearTextPrefix.Length..]);
@ -49,7 +49,7 @@ namespace Bit.Core.Services
if (dataParts.Length != 3) if (dataParts.Length != 3)
{ {
return false; return (false, null);
} }
if (dataParts[0].Equals(FamiliesForEnterpriseTokenName)) if (dataParts[0].Equals(FamiliesForEnterpriseTokenName))
@ -57,7 +57,7 @@ namespace Bit.Core.Services
if (!Guid.TryParse(dataParts[1], out Guid sponsorshipId) || if (!Guid.TryParse(dataParts[1], out Guid sponsorshipId) ||
!Enum.TryParse<PlanSponsorshipType>(dataParts[2], true, out var sponsorshipType)) !Enum.TryParse<PlanSponsorshipType>(dataParts[2], true, out var sponsorshipType))
{ {
return false; return (false, null);
} }
var sponsorship = await _organizationSponsorshipRepository.GetByIdAsync(sponsorshipId); var sponsorship = await _organizationSponsorshipRepository.GetByIdAsync(sponsorshipId);
@ -65,13 +65,13 @@ namespace Bit.Core.Services
sponsorship.PlanSponsorshipType != sponsorshipType || sponsorship.PlanSponsorshipType != sponsorshipType ||
sponsorship.OfferedToEmail != sponsoredUserEmail) sponsorship.OfferedToEmail != sponsoredUserEmail)
{ {
return false; return (false, sponsorship);
} }
return true; return (true, sponsorship);
} }
return false; return (false, null);
} }
private string RedemptionToken(Guid sponsorshipId, PlanSponsorshipType sponsorshipType) => private string RedemptionToken(Guid sponsorshipId, PlanSponsorshipType sponsorshipType) =>

View File

@ -48,21 +48,5 @@ namespace Bit.Infrastructure.Dapper.Repositories
return results.SingleOrDefault(); return results.SingleOrDefault();
} }
} }
public async Task<OrganizationSponsorship> GetByOfferedToEmailAsync(string offeredToEmail)
{
using (var connection = new SqlConnection(ConnectionString))
{
var results = await connection.QueryAsync<OrganizationSponsorship>(
"[dbo].[OrganizationSponsorship_ReadByOfferedToEmail]",
new
{
OfferedToEmail = offeredToEmail
},
commandType: CommandType.StoredProcedure);
return results.SingleOrDefault();
}
}
} }
} }

View File

@ -45,7 +45,7 @@ namespace Bit.Api.Test.Controllers
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(user.Id) sutProvider.GetDependency<IUserService>().GetUserByIdAsync(user.Id)
.Returns(user); .Returns(user);
sutProvider.GetDependency<IOrganizationSponsorshipService>().ValidateRedemptionTokenAsync(sponsorshipToken, sutProvider.GetDependency<IOrganizationSponsorshipService>().ValidateRedemptionTokenAsync(sponsorshipToken,
user.Email).Returns(false); user.Email).Returns((false, null));
var exception = await Assert.ThrowsAsync<BadRequestException>(() => var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.RedeemSponsorship(sponsorshipToken, model)); sutProvider.Sut.RedeemSponsorship(sponsorshipToken, model));
@ -59,13 +59,14 @@ namespace Bit.Api.Test.Controllers
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async Task RedeemSponsorship_NotSponsoredOrgOwner_ThrowsBadRequest(string sponsorshipToken, User user, public async Task RedeemSponsorship_NotSponsoredOrgOwner_ThrowsBadRequest(string sponsorshipToken, User user,
OrganizationSponsorshipRedeemRequestModel model, SutProvider<OrganizationSponsorshipsController> sutProvider) OrganizationSponsorship sponsorship, OrganizationSponsorshipRedeemRequestModel model,
SutProvider<OrganizationSponsorshipsController> sutProvider)
{ {
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(user.Id); sutProvider.GetDependency<ICurrentContext>().UserId.Returns(user.Id);
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(user.Id) sutProvider.GetDependency<IUserService>().GetUserByIdAsync(user.Id)
.Returns(user); .Returns(user);
sutProvider.GetDependency<IOrganizationSponsorshipService>().ValidateRedemptionTokenAsync(sponsorshipToken, sutProvider.GetDependency<IOrganizationSponsorshipService>().ValidateRedemptionTokenAsync(sponsorshipToken,
user.Email).Returns(true); user.Email).Returns((true, sponsorship));
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(model.SponsoredOrganizationId).Returns(false); sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(model.SponsoredOrganizationId).Returns(false);
var exception = await Assert.ThrowsAsync<BadRequestException>(() => var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
@ -79,12 +80,34 @@ namespace Bit.Api.Test.Controllers
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async Task PreValidateSponsorshipToken_ValidatesToken_Success(string sponsorshipToken, User user, public async Task RedeemSponsorship_NotSponsoredOrgOwner_Success(string sponsorshipToken, User user,
SutProvider<OrganizationSponsorshipsController> sutProvider) OrganizationSponsorship sponsorship, Organization sponsoringOrganization,
OrganizationSponsorshipRedeemRequestModel model, SutProvider<OrganizationSponsorshipsController> sutProvider)
{ {
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(user.Id); sutProvider.GetDependency<ICurrentContext>().UserId.Returns(user.Id);
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(user.Id) sutProvider.GetDependency<IUserService>().GetUserByIdAsync(user.Id)
.Returns(user); .Returns(user);
sutProvider.GetDependency<IOrganizationSponsorshipService>().ValidateRedemptionTokenAsync(sponsorshipToken,
user.Email).Returns((true, sponsorship));
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(model.SponsoredOrganizationId).Returns(true);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(model.SponsoredOrganizationId).Returns(sponsoringOrganization);
await sutProvider.Sut.RedeemSponsorship(sponsorshipToken, model);
await sutProvider.GetDependency<IOrganizationSponsorshipService>().Received(1)
.SetUpSponsorshipAsync(sponsorship, sponsoringOrganization);
}
[Theory]
[BitAutoData]
public async Task PreValidateSponsorshipToken_ValidatesToken_Success(string sponsorshipToken, User user,
OrganizationSponsorship sponsorship, SutProvider<OrganizationSponsorshipsController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(user.Id);
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(user.Id)
.Returns(user);
sutProvider.GetDependency<IOrganizationSponsorshipService>()
.ValidateRedemptionTokenAsync(sponsorshipToken, user.Email).Returns((true, sponsorship));
await sutProvider.Sut.PreValidateSponsorshipToken(sponsorshipToken); await sutProvider.Sut.PreValidateSponsorshipToken(sponsorshipToken);