mirror of
https://github.com/bitwarden/server
synced 2025-12-28 22:23:30 +00:00
* Add PolicyValidatorsRefactor constant to FeatureFlagKeys in Constants.cs * Add Metadata property and ToSavePolicyModel method to PolicyUpdateRequestModel * Refactor PoliciesController to utilize IVNextSavePolicyCommand based on feature flag - Added IFeatureService and IVNextSavePolicyCommand dependencies to PoliciesController. - Updated PutVNext method to conditionally use VNextSavePolicyCommand or SavePolicyCommand based on the PolicyValidatorsRefactor feature flag. - Enhanced unit tests to verify behavior for both enabled and disabled states of the feature flag. * Update public PoliciesController to to utilize IVNextSavePolicyCommand based on feature flag - Introduced IFeatureService and IVNextSavePolicyCommand to manage policy saving based on the PolicyValidatorsRefactor feature flag. - Updated the Put method to conditionally use the new VNextSavePolicyCommand or the legacy SavePolicyCommand. - Added unit tests to validate the behavior of the Put method for both enabled and disabled states of the feature flag. * Refactor VerifyOrganizationDomainCommand to utilize IVNextSavePolicyCommand based on feature flag - Added IFeatureService and IVNextSavePolicyCommand dependencies to VerifyOrganizationDomainCommand. - Updated EnableSingleOrganizationPolicyAsync method to conditionally use VNextSavePolicyCommand or SavePolicyCommand based on the PolicyValidatorsRefactor feature flag. - Enhanced unit tests to validate the behavior when the feature flag is enabled. * Enhance SsoConfigService to utilize IVNextSavePolicyCommand based on feature flag - Added IFeatureService and IVNextSavePolicyCommand dependencies to SsoConfigService. - Updated SaveAsync method to conditionally use VNextSavePolicyCommand or SavePolicyCommand based on the PolicyValidatorsRefactor feature flag. - Added unit tests to validate the behavior when the feature flag is enabled. * Refactor SavePolicyModel to simplify constructor usage by removing EmptyMetadataModel parameter. Update related usages across the codebase to reflect the new constructor overloads. * Update PolicyUpdateRequestModel to make Metadata property nullable for improved null safety
173 lines
6.7 KiB
C#
173 lines
6.7 KiB
C#
// FIXME: Update this file to be null safe and then delete the line below
|
|
#nullable disable
|
|
|
|
using Bit.Core.AdminConsole.Enums;
|
|
using Bit.Core.AdminConsole.Models.Data;
|
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
|
|
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
|
|
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
|
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces;
|
|
using Bit.Core.Context;
|
|
using Bit.Core.Entities;
|
|
using Bit.Core.Enums;
|
|
using Bit.Core.Exceptions;
|
|
using Bit.Core.Models.Data.Organizations;
|
|
using Bit.Core.Repositories;
|
|
using Bit.Core.Services;
|
|
using Bit.Core.Settings;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains;
|
|
|
|
public class VerifyOrganizationDomainCommand(
|
|
IOrganizationDomainRepository organizationDomainRepository,
|
|
IDnsResolverService dnsResolverService,
|
|
IEventService eventService,
|
|
IGlobalSettings globalSettings,
|
|
ICurrentContext currentContext,
|
|
IFeatureService featureService,
|
|
ISavePolicyCommand savePolicyCommand,
|
|
IVNextSavePolicyCommand vNextSavePolicyCommand,
|
|
IMailService mailService,
|
|
IOrganizationUserRepository organizationUserRepository,
|
|
IOrganizationRepository organizationRepository,
|
|
ILogger<VerifyOrganizationDomainCommand> logger)
|
|
: IVerifyOrganizationDomainCommand
|
|
{
|
|
public async Task<OrganizationDomain> UserVerifyOrganizationDomainAsync(OrganizationDomain organizationDomain)
|
|
{
|
|
if (currentContext.UserId is null)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"{nameof(UserVerifyOrganizationDomainAsync)} can only be called by a user. " +
|
|
$"Please call {nameof(SystemVerifyOrganizationDomainAsync)} for system users.");
|
|
}
|
|
|
|
var actingUser = new StandardUser(currentContext.UserId.Value, await currentContext.OrganizationOwner(organizationDomain.OrganizationId));
|
|
|
|
var domainVerificationResult = await VerifyOrganizationDomainAsync(organizationDomain, actingUser);
|
|
|
|
await eventService.LogOrganizationDomainEventAsync(domainVerificationResult,
|
|
domainVerificationResult.VerifiedDate != null
|
|
? EventType.OrganizationDomain_Verified
|
|
: EventType.OrganizationDomain_NotVerified);
|
|
|
|
await organizationDomainRepository.ReplaceAsync(domainVerificationResult);
|
|
|
|
return domainVerificationResult;
|
|
}
|
|
|
|
public async Task<OrganizationDomain> SystemVerifyOrganizationDomainAsync(OrganizationDomain organizationDomain)
|
|
{
|
|
var actingUser = new SystemUser(EventSystemUser.DomainVerification);
|
|
|
|
organizationDomain.SetJobRunCount();
|
|
|
|
var domainVerificationResult = await VerifyOrganizationDomainAsync(organizationDomain, actingUser);
|
|
|
|
if (domainVerificationResult.VerifiedDate is not null)
|
|
{
|
|
logger.LogInformation(Constants.BypassFiltersEventId, "Successfully validated domain");
|
|
|
|
await eventService.LogOrganizationDomainEventAsync(domainVerificationResult,
|
|
EventType.OrganizationDomain_Verified,
|
|
EventSystemUser.DomainVerification);
|
|
}
|
|
else
|
|
{
|
|
domainVerificationResult.SetNextRunDate(globalSettings.DomainVerification.VerificationInterval);
|
|
|
|
await eventService.LogOrganizationDomainEventAsync(domainVerificationResult,
|
|
EventType.OrganizationDomain_NotVerified,
|
|
EventSystemUser.DomainVerification);
|
|
|
|
logger.LogInformation(Constants.BypassFiltersEventId,
|
|
"Verification for organization {OrgId} with domain {Domain} failed",
|
|
domainVerificationResult.OrganizationId, domainVerificationResult.DomainName);
|
|
}
|
|
|
|
await organizationDomainRepository.ReplaceAsync(domainVerificationResult);
|
|
|
|
return domainVerificationResult;
|
|
}
|
|
|
|
private async Task<OrganizationDomain> VerifyOrganizationDomainAsync(OrganizationDomain domain, IActingUser actingUser)
|
|
{
|
|
domain.SetLastCheckedDate();
|
|
|
|
if (domain.VerifiedDate is not null)
|
|
{
|
|
await organizationDomainRepository.ReplaceAsync(domain);
|
|
throw new ConflictException("Domain has already been verified.");
|
|
}
|
|
|
|
var claimedDomain =
|
|
await organizationDomainRepository.GetClaimedDomainsByDomainNameAsync(domain.DomainName);
|
|
|
|
if (claimedDomain.Count > 0)
|
|
{
|
|
await organizationDomainRepository.ReplaceAsync(domain);
|
|
throw new ConflictException("The domain is not available to be claimed.");
|
|
}
|
|
|
|
try
|
|
{
|
|
if (await dnsResolverService.ResolveAsync(domain.DomainName, domain.Txt))
|
|
{
|
|
domain.SetVerifiedDate();
|
|
|
|
await DomainVerificationSideEffectsAsync(domain, actingUser);
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
logger.LogError("Error verifying Organization domain: {domain}. {errorMessage}",
|
|
domain.DomainName, e.Message);
|
|
}
|
|
|
|
return domain;
|
|
}
|
|
|
|
private async Task DomainVerificationSideEffectsAsync(OrganizationDomain domain, IActingUser actingUser)
|
|
{
|
|
await EnableSingleOrganizationPolicyAsync(domain.OrganizationId, actingUser);
|
|
await SendVerifiedDomainUserEmailAsync(domain);
|
|
}
|
|
|
|
private async Task EnableSingleOrganizationPolicyAsync(Guid organizationId, IActingUser actingUser)
|
|
{
|
|
var policyUpdate = new PolicyUpdate
|
|
{
|
|
OrganizationId = organizationId,
|
|
Type = PolicyType.SingleOrg,
|
|
Enabled = true,
|
|
PerformedBy = actingUser
|
|
};
|
|
|
|
if (featureService.IsEnabled(FeatureFlagKeys.PolicyValidatorsRefactor))
|
|
{
|
|
var savePolicyModel = new SavePolicyModel(policyUpdate, actingUser);
|
|
await vNextSavePolicyCommand.SaveAsync(savePolicyModel);
|
|
}
|
|
else
|
|
{
|
|
await savePolicyCommand.SaveAsync(policyUpdate);
|
|
}
|
|
}
|
|
|
|
private async Task SendVerifiedDomainUserEmailAsync(OrganizationDomain domain)
|
|
{
|
|
var orgUserUsers = await organizationUserRepository.GetManyDetailsByOrganizationAsync(domain.OrganizationId);
|
|
|
|
var domainUserEmails = orgUserUsers
|
|
.Where(ou => ou.Email.ToLower().EndsWith($"@{domain.DomainName.ToLower()}") &&
|
|
ou.Status != OrganizationUserStatusType.Revoked &&
|
|
ou.Status != OrganizationUserStatusType.Invited)
|
|
.Select(ou => ou.Email);
|
|
|
|
var organization = await organizationRepository.GetByIdAsync(domain.OrganizationId);
|
|
|
|
await mailService.SendClaimedDomainUserEmailAsync(new ClaimedUserDomainClaimedEmails(domainUserEmails, organization));
|
|
}
|
|
}
|