using System; using System.Threading.Tasks; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Table; using Bit.Core.Repositories; using Microsoft.AspNetCore.DataProtection; namespace Bit.Core.Services { public class OrganizationSponsorshipService : IOrganizationSponsorshipService { private const string FamiliesForEnterpriseTokenName = "FamiliesForEnterpriseToken"; private const string TokenClearTextPrefix = "BWOrganizationSponsorship_"; private readonly IOrganizationSponsorshipRepository _organizationSponsorshipRepository; private readonly IOrganizationRepository _organizationRepository; private readonly IPaymentService _paymentService; private readonly IMailService _mailService; private readonly IDataProtector _dataProtector; public OrganizationSponsorshipService(IOrganizationSponsorshipRepository organizationSponsorshipRepository, IOrganizationRepository organizationRepository, IPaymentService paymentService, IMailService mailService, IDataProtectionProvider dataProtectionProvider) { _organizationSponsorshipRepository = organizationSponsorshipRepository; _organizationRepository = organizationRepository; _paymentService = paymentService; _mailService = mailService; _dataProtector = dataProtectionProvider.CreateProtector("OrganizationSponsorshipServiceDataProtector"); } public async Task ValidateRedemptionTokenAsync(string encryptedToken) { if (!encryptedToken.StartsWith(TokenClearTextPrefix)) { return false; } var decryptedToken = _dataProtector.Unprotect(encryptedToken[TokenClearTextPrefix.Length..]); var dataParts = decryptedToken.Split(' '); if (dataParts.Length != 3) { return false; } if (dataParts[0].Equals(FamiliesForEnterpriseTokenName)) { if (!Guid.TryParse(dataParts[1], out Guid sponsorshipId) || !Enum.TryParse(dataParts[2], true, out var sponsorshipType)) { return false; } var sponsorship = await _organizationSponsorshipRepository.GetByIdAsync(sponsorshipId); if (sponsorship == null || sponsorship.PlanSponsorshipType != sponsorshipType) { return false; } return true; } return false; } private string RedemptionToken(Guid sponsorshipId, PlanSponsorshipType sponsorshipType) => string.Concat( TokenClearTextPrefix, _dataProtector.Protect($"{FamiliesForEnterpriseTokenName} {sponsorshipId} {sponsorshipType}") ); public async Task OfferSponsorshipAsync(Organization sponsoringOrg, OrganizationUser sponsoringOrgUser, PlanSponsorshipType sponsorshipType, string sponsoredEmail, string friendlyName) { var sponsorship = new OrganizationSponsorship { SponsoringOrganizationId = sponsoringOrg.Id, SponsoringOrganizationUserId = sponsoringOrgUser.Id, FriendlyName = friendlyName, OfferedToEmail = sponsoredEmail, PlanSponsorshipType = sponsorshipType, CloudSponsor = true, }; try { sponsorship = await _organizationSponsorshipRepository.CreateAsync(sponsorship); await SendSponsorshipOfferAsync(sponsoringOrg, sponsorship); } catch { if (sponsorship.Id != default) { await _organizationSponsorshipRepository.DeleteAsync(sponsorship); } throw; } } public async Task SendSponsorshipOfferAsync(Organization sponsoringOrg, OrganizationSponsorship sponsorship) { await _mailService.SendFamiliesForEnterpriseOfferEmailAsync(sponsorship.OfferedToEmail, sponsoringOrg.Name, RedemptionToken(sponsorship.Id, sponsorship.PlanSponsorshipType.Value)); } public async Task SetUpSponsorshipAsync(OrganizationSponsorship sponsorship, Organization sponsoredOrganization) { if (sponsorship.PlanSponsorshipType == null) { throw new BadRequestException("Cannot set up sponsorship without a known sponsorship type."); } // TODO: rollback? await _paymentService.SponsorOrganizationAsync(sponsoredOrganization, sponsorship); await _organizationRepository.UpsertAsync(sponsoredOrganization); sponsorship.SponsoredOrganizationId = sponsoredOrganization.Id; sponsorship.OfferedToEmail = null; await _organizationSponsorshipRepository.UpsertAsync(sponsorship); } public async Task ValidateSponsorshipAsync(Guid sponsoredOrganizationId) { var sponsoredOrganization = await _organizationRepository.GetByIdAsync(sponsoredOrganizationId); if (sponsoredOrganization == null) { return false; } var existingSponsorship = await _organizationSponsorshipRepository .GetBySponsoredOrganizationIdAsync(sponsoredOrganizationId); if (existingSponsorship == null) { await RemoveSponsorshipAsync(sponsoredOrganization, null); return false; } if (existingSponsorship.SponsoringOrganizationId == null || existingSponsorship.SponsoringOrganizationUserId == null || existingSponsorship.PlanSponsorshipType == null) { await RemoveSponsorshipAsync(sponsoredOrganization, existingSponsorship); return false; } var sponsoredPlan = Utilities.StaticStore.GetSponsoredPlan(existingSponsorship.PlanSponsorshipType.Value); var sponsoringOrganization = await _organizationRepository .GetByIdAsync(existingSponsorship.SponsoringOrganizationId.Value); if (sponsoringOrganization == null) { await RemoveSponsorshipAsync(sponsoredOrganization, existingSponsorship); return false; } var sponsoringOrgPlan = Utilities.StaticStore.GetPlan(sponsoringOrganization.PlanType); if (!sponsoringOrganization.Enabled || sponsoredPlan.SponsoringProductType != sponsoringOrgPlan.Product) { await RemoveSponsorshipAsync(sponsoredOrganization, existingSponsorship); return false; } return true; } public async Task RemoveSponsorshipAsync(Organization sponsoredOrganization, OrganizationSponsorship sponsorship = null) { if (sponsoredOrganization != null) { await _paymentService.RemoveOrganizationSponsorshipAsync(sponsoredOrganization, sponsorship); await _organizationRepository.UpsertAsync(sponsoredOrganization); await _mailService.SendFamiliesForEnterpriseSponsorshipRevertingEmailAsync( sponsoredOrganization.BillingEmailAddress(), sponsoredOrganization.Name); } if (sponsorship == null) { return; } // Initialize the record as available sponsorship.SponsoredOrganizationId = null; sponsorship.FriendlyName = null; sponsorship.OfferedToEmail = null; sponsorship.PlanSponsorshipType = null; sponsorship.TimesRenewedWithoutValidation = 0; sponsorship.SponsorshipLapsedDate = null; if (sponsorship.CloudSponsor || sponsorship.SponsorshipLapsedDate.HasValue) { await _organizationSponsorshipRepository.DeleteAsync(sponsorship); } else { await _organizationSponsorshipRepository.UpsertAsync(sponsorship); } } } }