1
0
mirror of https://github.com/bitwarden/server synced 2026-02-04 18:53:28 +00:00

Merge branch 'main' into auth/pm-27084/register-accepts-new-data-types

This commit is contained in:
Patrick-Pimentel-Bitwarden
2025-12-31 17:09:38 -05:00
committed by GitHub
251 changed files with 24359 additions and 1119 deletions

View File

@@ -57,8 +57,7 @@ public class ProviderClientsController(
Owner = user,
BillingEmail = provider.BillingEmail,
OwnerKey = requestBody.Key,
PublicKey = requestBody.KeyPair.PublicKey,
PrivateKey = requestBody.KeyPair.EncryptedPrivateKey,
Keys = requestBody.KeyPair.ToPublicKeyEncryptionKeyPairData(),
CollectionName = requestBody.CollectionName,
IsFromProvider = true
};

View File

@@ -113,11 +113,10 @@ public class OrganizationCreateRequestModel : IValidatableObject
BillingAddressCountry = BillingAddressCountry,
},
InitiationPath = InitiationPath,
SkipTrial = SkipTrial
SkipTrial = SkipTrial,
Keys = Keys?.ToPublicKeyEncryptionKeyPairData()
};
Keys?.ToOrganizationSignup(orgSignup);
return orgSignup;
}

View File

@@ -2,8 +2,7 @@
#nullable disable
using System.ComponentModel.DataAnnotations;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Models.Business;
using Bit.Core.KeyManagement.Models.Data;
namespace Bit.Api.AdminConsole.Models.Request.Organizations;
@@ -14,48 +13,10 @@ public class OrganizationKeysRequestModel
[Required]
public string EncryptedPrivateKey { get; set; }
public OrganizationSignup ToOrganizationSignup(OrganizationSignup existingSignup)
public PublicKeyEncryptionKeyPairData ToPublicKeyEncryptionKeyPairData()
{
if (string.IsNullOrWhiteSpace(existingSignup.PublicKey))
{
existingSignup.PublicKey = PublicKey;
}
if (string.IsNullOrWhiteSpace(existingSignup.PrivateKey))
{
existingSignup.PrivateKey = EncryptedPrivateKey;
}
return existingSignup;
}
public OrganizationUpgrade ToOrganizationUpgrade(OrganizationUpgrade existingUpgrade)
{
if (string.IsNullOrWhiteSpace(existingUpgrade.PublicKey))
{
existingUpgrade.PublicKey = PublicKey;
}
if (string.IsNullOrWhiteSpace(existingUpgrade.PrivateKey))
{
existingUpgrade.PrivateKey = EncryptedPrivateKey;
}
return existingUpgrade;
}
public Organization ToOrganization(Organization existingOrg)
{
if (string.IsNullOrWhiteSpace(existingOrg.PublicKey))
{
existingOrg.PublicKey = PublicKey;
}
if (string.IsNullOrWhiteSpace(existingOrg.PrivateKey))
{
existingOrg.PrivateKey = EncryptedPrivateKey;
}
return existingOrg;
return new PublicKeyEncryptionKeyPairData(
wrappedPrivateKey: EncryptedPrivateKey,
publicKey: PublicKey);
}
}

View File

@@ -110,10 +110,9 @@ public class OrganizationNoPaymentCreateRequest
BillingAddressCountry = BillingAddressCountry,
},
InitiationPath = InitiationPath,
Keys = Keys?.ToPublicKeyEncryptionKeyPairData()
};
Keys?.ToOrganizationSignup(orgSignup);
return orgSignup;
}
}

View File

@@ -22,7 +22,6 @@ public class OrganizationUpdateRequestModel
OrganizationId = organizationId,
Name = Name,
BillingEmail = BillingEmail,
PublicKey = Keys?.PublicKey,
EncryptedPrivateKey = Keys?.EncryptedPrivateKey
Keys = Keys?.ToPublicKeyEncryptionKeyPairData()
};
}

View File

@@ -43,11 +43,10 @@ public class OrganizationUpgradeRequestModel
{
BillingAddressCountry = BillingAddressCountry,
BillingAddressPostalCode = BillingAddressPostalCode
}
},
Keys = Keys?.ToPublicKeyEncryptionKeyPairData()
};
Keys?.ToOrganizationUpgrade(orgUpgrade);
return orgUpgrade;
}
}

View File

@@ -33,7 +33,7 @@
<ItemGroup>
<PackageReference Include="AspNetCore.HealthChecks.SqlServer" Version="8.0.2" />
<PackageReference Include="AspNetCore.HealthChecks.Uris" Version="8.0.1" />
<PackageReference Include="Azure.Messaging.EventGrid" Version="4.31.0" />
<PackageReference Include="Azure.Messaging.EventGrid" Version="5.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.4" />
</ItemGroup>

View File

@@ -21,7 +21,6 @@ using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Auth.Controllers;
[Route("webauthn")]
[Authorize(Policies.Web)]
public class WebAuthnController : Controller
{
private readonly IUserService _userService;
@@ -62,6 +61,7 @@ public class WebAuthnController : Controller
_featureService = featureService;
}
[Authorize(Policies.Web)]
[HttpGet("")]
public async Task<ListResponseModel<WebAuthnCredentialResponseModel>> Get()
{
@@ -71,6 +71,7 @@ public class WebAuthnController : Controller
return new ListResponseModel<WebAuthnCredentialResponseModel>(credentials.Select(c => new WebAuthnCredentialResponseModel(c)));
}
[Authorize(Policies.Application)]
[HttpPost("attestation-options")]
public async Task<WebAuthnCredentialCreateOptionsResponseModel> AttestationOptions([FromBody] SecretVerificationRequestModel model)
{
@@ -88,6 +89,7 @@ public class WebAuthnController : Controller
};
}
[Authorize(Policies.Web)]
[HttpPost("assertion-options")]
public async Task<WebAuthnLoginAssertionOptionsResponseModel> AssertionOptions([FromBody] SecretVerificationRequestModel model)
{
@@ -104,6 +106,7 @@ public class WebAuthnController : Controller
};
}
[Authorize(Policies.Application)]
[HttpPost("")]
public async Task Post([FromBody] WebAuthnLoginCredentialCreateRequestModel model)
{
@@ -149,6 +152,7 @@ public class WebAuthnController : Controller
}
}
[Authorize(Policies.Application)]
[HttpPut()]
public async Task UpdateCredential([FromBody] WebAuthnLoginCredentialUpdateRequestModel model)
{
@@ -172,6 +176,7 @@ public class WebAuthnController : Controller
await _credentialRepository.UpdateAsync(credential);
}
[Authorize(Policies.Web)]
[HttpPost("{id}/delete")]
public async Task Delete(Guid id, [FromBody] SecretVerificationRequestModel model)
{

View File

@@ -273,7 +273,7 @@ public class TwoFactorWebAuthnDeleteRequestModel : SecretVerificationRequestMode
yield return validationResult;
}
if (!Id.HasValue || Id < 0 || Id > 5)
if (!Id.HasValue)
{
yield return new ValidationResult("Invalid Key Id", new string[] { nameof(Id) });
}

View File

@@ -2,6 +2,7 @@
using Bit.Api.Billing.Models.Requests.Payment;
using Bit.Api.Billing.Models.Requests.Premium;
using Bit.Core;
using Bit.Core.Billing.Licenses.Queries;
using Bit.Core.Billing.Payment.Commands;
using Bit.Core.Billing.Payment.Queries;
using Bit.Core.Billing.Premium.Commands;
@@ -21,6 +22,7 @@ public class AccountBillingVNextController(
ICreatePremiumCloudHostedSubscriptionCommand createPremiumCloudHostedSubscriptionCommand,
IGetCreditQuery getCreditQuery,
IGetPaymentMethodQuery getPaymentMethodQuery,
IGetUserLicenseQuery getUserLicenseQuery,
IUpdatePaymentMethodCommand updatePaymentMethodCommand) : BaseBillingController
{
[HttpGet("credit")]
@@ -77,4 +79,13 @@ public class AccountBillingVNextController(
user, paymentMethod, billingAddress, additionalStorageGb);
return Handle(result);
}
[HttpGet("license")]
[InjectUser]
public async Task<IResult> GetLicenseAsync(
[BindNever] User user)
{
var response = await getUserLicenseQuery.Run(user);
return TypedResults.Ok(response);
}
}

View File

@@ -2,6 +2,7 @@
#nullable disable
using System.ComponentModel.DataAnnotations;
using Bit.Core.KeyManagement.Models.Data;
namespace Bit.Api.Billing.Models.Requests;
@@ -12,4 +13,11 @@ public class KeyPairRequestBody
public string PublicKey { get; set; }
[Required(ErrorMessage = "'encryptedPrivateKey' must be provided")]
public string EncryptedPrivateKey { get; set; }
public PublicKeyEncryptionKeyPairData ToPublicKeyEncryptionKeyPairData()
{
return new PublicKeyEncryptionKeyPairData(
wrappedPrivateKey: EncryptedPrivateKey,
publicKey: PublicKey);
}
}

View File

@@ -1,12 +1,12 @@
using Bit.Api.AdminConsole.Models.Request.Organizations;
using Bit.Api.AdminConsole.Models.Response.Organizations;
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
using Bit.Api.Dirt.Models.Request;
using Bit.Api.Dirt.Models.Response;
using Bit.Core.Context;
using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
using Bit.Core.Exceptions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.AdminConsole.Controllers;
namespace Bit.Api.Dirt.Controllers;
[Route("organizations/{organizationId:guid}/integrations/{integrationId:guid}/configurations")]
[Authorize("Application")]

View File

@@ -1,12 +1,12 @@
using Bit.Api.AdminConsole.Models.Request.Organizations;
using Bit.Api.AdminConsole.Models.Response.Organizations;
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces;
using Bit.Api.Dirt.Models.Request;
using Bit.Api.Dirt.Models.Response;
using Bit.Core.Context;
using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations.Interfaces;
using Bit.Core.Exceptions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.AdminConsole.Controllers;
namespace Bit.Api.Dirt.Controllers;
[Route("organizations/{organizationId:guid}/integrations")]
[Authorize("Application")]

View File

@@ -1,16 +1,16 @@
using System.Text.Json;
using Bit.Api.AdminConsole.Models.Response.Organizations;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
using Bit.Api.Dirt.Models.Response;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.Enums;
using Bit.Core.Dirt.Models.Data.EventIntegrations;
using Bit.Core.Dirt.Repositories;
using Bit.Core.Dirt.Services;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.AdminConsole.Controllers;
namespace Bit.Api.Dirt.Controllers;
[Route("organizations")]
[Authorize("Application")]

View File

@@ -1,18 +1,18 @@
using System.Text.Json;
using Bit.Api.AdminConsole.Models.Response.Organizations;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
using Bit.Api.Dirt.Models.Response;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.Enums;
using Bit.Core.Dirt.Models.Data.EventIntegrations;
using Bit.Core.Dirt.Repositories;
using Bit.Core.Dirt.Services;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Integration.AspNet.Core;
namespace Bit.Api.AdminConsole.Controllers;
namespace Bit.Api.Dirt.Controllers;
[Route("organizations")]
[Authorize("Application")]

View File

@@ -1,8 +1,7 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Dirt.Entities;
using Bit.Core.Enums;
namespace Bit.Api.AdminConsole.Models.Request.Organizations;
namespace Bit.Api.Dirt.Models.Request;
public class OrganizationIntegrationConfigurationRequestModel
{

View File

@@ -1,10 +1,10 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
using Bit.Core.Enums;
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.Enums;
using Bit.Core.Dirt.Models.Data.EventIntegrations;
namespace Bit.Api.AdminConsole.Models.Request.Organizations;
namespace Bit.Api.Dirt.Models.Request;
public class OrganizationIntegrationRequestModel : IValidatableObject
{

View File

@@ -1,8 +1,8 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Dirt.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Api;
namespace Bit.Api.AdminConsole.Models.Response.Organizations;
namespace Bit.Api.Dirt.Models.Response;
public class OrganizationIntegrationConfigurationResponseModel : ResponseModel
{

View File

@@ -1,10 +1,10 @@
using System.Text.Json;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
using Bit.Core.Enums;
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.Enums;
using Bit.Core.Dirt.Models.Data.EventIntegrations;
using Bit.Core.Models.Api;
namespace Bit.Api.AdminConsole.Models.Response.Organizations;
namespace Bit.Api.Dirt.Models.Response;
public class OrganizationIntegrationResponseModel : ResponseModel
{

View File

@@ -44,7 +44,7 @@ public class SendRotationValidator : IRotationValidator<IEnumerable<SendWithIdRe
throw new BadRequestException("All existing sends must be included in the rotation.");
}
result.Add(send.ToSend(existing, _sendAuthorizationService));
result.Add(send.UpdateSend(existing, _sendAuthorizationService));
}
return result;

View File

@@ -1,6 +1,7 @@
// FIXME: Update this file to be null safe and then delete the line below
#nullable disable
using Bit.Core;
using Bit.Core.Enums;
using Bit.Core.Models.Api;
using Bit.Core.Services;
@@ -45,7 +46,8 @@ public class ConfigResponseModel : ResponseModel
Sso = globalSettings.BaseServiceUri.Sso
};
FeatureStates = featureService.GetAll();
Push = PushSettings.Build(globalSettings);
var webPushEnabled = FeatureStates.TryGetValue(FeatureFlagKeys.WebPush, out var webPushEnabledValue) ? (bool)webPushEnabledValue : false;
Push = PushSettings.Build(webPushEnabled, globalSettings);
Settings = new ServerSettingsResponseModel
{
DisableUserRegistration = globalSettings.DisableUserRegistration
@@ -74,9 +76,9 @@ public class PushSettings
public PushTechnologyType PushTechnology { get; private init; }
public string VapidPublicKey { get; private init; }
public static PushSettings Build(IGlobalSettings globalSettings)
public static PushSettings Build(bool webPushEnabled, IGlobalSettings globalSettings)
{
var vapidPublicKey = globalSettings.WebPush.VapidPublicKey;
var vapidPublicKey = webPushEnabled ? globalSettings.WebPush.VapidPublicKey : null;
var pushTechnology = vapidPublicKey != null ? PushTechnologyType.WebPush : PushTechnologyType.SignalR;
return new()
{

View File

@@ -1,7 +1,4 @@
// FIXME: Update this file to be null safe and then delete the line below
#nullable disable
using System.Text.Json;
using System.Text.Json;
using Azure.Messaging.EventGrid;
using Bit.Api.Models.Response;
using Bit.Api.Tools.Models.Request;
@@ -16,6 +13,7 @@ using Bit.Core.Tools.Models.Data;
using Bit.Core.Tools.Repositories;
using Bit.Core.Tools.SendFeatures;
using Bit.Core.Tools.SendFeatures.Commands.Interfaces;
using Bit.Core.Tools.SendFeatures.Queries.Interfaces;
using Bit.Core.Tools.Services;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
@@ -33,6 +31,9 @@ public class SendsController : Controller
private readonly ISendFileStorageService _sendFileStorageService;
private readonly IAnonymousSendCommand _anonymousSendCommand;
private readonly INonAnonymousSendCommand _nonAnonymousSendCommand;
private readonly ISendOwnerQuery _sendOwnerQuery;
private readonly ILogger<SendsController> _logger;
private readonly GlobalSettings _globalSettings;
@@ -42,6 +43,7 @@ public class SendsController : Controller
ISendAuthorizationService sendAuthorizationService,
IAnonymousSendCommand anonymousSendCommand,
INonAnonymousSendCommand nonAnonymousSendCommand,
ISendOwnerQuery sendOwnerQuery,
ISendFileStorageService sendFileStorageService,
ILogger<SendsController> logger,
GlobalSettings globalSettings)
@@ -51,6 +53,7 @@ public class SendsController : Controller
_sendAuthorizationService = sendAuthorizationService;
_anonymousSendCommand = anonymousSendCommand;
_nonAnonymousSendCommand = nonAnonymousSendCommand;
_sendOwnerQuery = sendOwnerQuery;
_sendFileStorageService = sendFileStorageService;
_logger = logger;
_globalSettings = globalSettings;
@@ -70,7 +73,11 @@ public class SendsController : Controller
var guid = new Guid(CoreHelpers.Base64UrlDecode(id));
var send = await _sendRepository.GetByIdAsync(guid);
SendAccessResult sendAuthResult =
if (send == null)
{
throw new BadRequestException("Could not locate send");
}
var sendAuthResult =
await _sendAuthorizationService.AccessAsync(send, model.Password);
if (sendAuthResult.Equals(SendAccessResult.PasswordRequired))
{
@@ -86,7 +93,7 @@ public class SendsController : Controller
throw new NotFoundException();
}
var sendResponse = new SendAccessResponseModel(send, _globalSettings);
var sendResponse = new SendAccessResponseModel(send);
if (send.UserId.HasValue && !send.HideEmail.GetValueOrDefault())
{
var creator = await _userService.GetUserByIdAsync(send.UserId.Value);
@@ -181,33 +188,29 @@ public class SendsController : Controller
[HttpGet("{id}")]
public async Task<SendResponseModel> Get(string id)
{
var userId = _userService.GetProperUserId(User).Value;
var send = await _sendRepository.GetByIdAsync(new Guid(id));
if (send == null || send.UserId != userId)
{
throw new NotFoundException();
}
return new SendResponseModel(send, _globalSettings);
var sendId = new Guid(id);
var send = await _sendOwnerQuery.Get(sendId, User);
return new SendResponseModel(send);
}
[HttpGet("")]
public async Task<ListResponseModel<SendResponseModel>> GetAll()
{
var userId = _userService.GetProperUserId(User).Value;
var sends = await _sendRepository.GetManyByUserIdAsync(userId);
var responses = sends.Select(s => new SendResponseModel(s, _globalSettings));
return new ListResponseModel<SendResponseModel>(responses);
var sends = await _sendOwnerQuery.GetOwned(User);
var responses = sends.Select(s => new SendResponseModel(s));
var result = new ListResponseModel<SendResponseModel>(responses);
return result;
}
[HttpPost("")]
public async Task<SendResponseModel> Post([FromBody] SendRequestModel model)
{
model.ValidateCreation();
var userId = _userService.GetProperUserId(User).Value;
var userId = _userService.GetProperUserId(User) ?? throw new InvalidOperationException("User ID not found");
var send = model.ToSend(userId, _sendAuthorizationService);
await _nonAnonymousSendCommand.SaveSendAsync(send);
return new SendResponseModel(send, _globalSettings);
return new SendResponseModel(send);
}
[HttpPost("file/v2")]
@@ -229,27 +232,27 @@ public class SendsController : Controller
}
model.ValidateCreation();
var userId = _userService.GetProperUserId(User).Value;
var userId = _userService.GetProperUserId(User) ?? throw new InvalidOperationException("User ID not found");
var (send, data) = model.ToSend(userId, model.File.FileName, _sendAuthorizationService);
var uploadUrl = await _nonAnonymousSendCommand.SaveFileSendAsync(send, data, model.FileLength.Value);
return new SendFileUploadDataResponseModel
{
Url = uploadUrl,
FileUploadType = _sendFileStorageService.FileUploadType,
SendResponse = new SendResponseModel(send, _globalSettings)
SendResponse = new SendResponseModel(send)
};
}
[HttpGet("{id}/file/{fileId}")]
public async Task<SendFileUploadDataResponseModel> RenewFileUpload(string id, string fileId)
{
var userId = _userService.GetProperUserId(User).Value;
var userId = _userService.GetProperUserId(User) ?? throw new InvalidOperationException("User ID not found");
var sendId = new Guid(id);
var send = await _sendRepository.GetByIdAsync(sendId);
var fileData = JsonSerializer.Deserialize<SendFileData>(send?.Data);
var fileData = JsonSerializer.Deserialize<SendFileData>(send?.Data ?? string.Empty);
if (send == null || send.Type != SendType.File || (send.UserId.HasValue && send.UserId.Value != userId) ||
!send.UserId.HasValue || fileData.Id != fileId || fileData.Validated)
!send.UserId.HasValue || fileData?.Id != fileId || fileData.Validated)
{
// Not found if Send isn't found, user doesn't have access, request is faulty,
// or we've already validated the file. This last is to emulate create-only blob permissions for Azure
@@ -260,7 +263,7 @@ public class SendsController : Controller
{
Url = await _sendFileStorageService.GetSendFileUploadUrlAsync(send, fileId),
FileUploadType = _sendFileStorageService.FileUploadType,
SendResponse = new SendResponseModel(send, _globalSettings),
SendResponse = new SendResponseModel(send),
};
}
@@ -270,12 +273,16 @@ public class SendsController : Controller
[DisableFormValueModelBinding]
public async Task PostFileForExistingSend(string id, string fileId)
{
if (!Request?.ContentType.Contains("multipart/") ?? true)
if (!Request?.ContentType?.Contains("multipart/") ?? true)
{
throw new BadRequestException("Invalid content.");
}
var send = await _sendRepository.GetByIdAsync(new Guid(id));
if (send == null)
{
throw new BadRequestException("Could not locate send");
}
await Request.GetFileAsync(async (stream) =>
{
await _nonAnonymousSendCommand.UploadFileToExistingSendAsync(stream, send);
@@ -286,36 +293,39 @@ public class SendsController : Controller
public async Task<SendResponseModel> Put(string id, [FromBody] SendRequestModel model)
{
model.ValidateEdit();
var userId = _userService.GetProperUserId(User).Value;
var userId = _userService.GetProperUserId(User) ?? throw new InvalidOperationException("User ID not found");
var send = await _sendRepository.GetByIdAsync(new Guid(id));
if (send == null || send.UserId != userId)
{
throw new NotFoundException();
}
await _nonAnonymousSendCommand.SaveSendAsync(model.ToSend(send, _sendAuthorizationService));
return new SendResponseModel(send, _globalSettings);
await _nonAnonymousSendCommand.SaveSendAsync(model.UpdateSend(send, _sendAuthorizationService));
return new SendResponseModel(send);
}
[HttpPut("{id}/remove-password")]
public async Task<SendResponseModel> PutRemovePassword(string id)
{
var userId = _userService.GetProperUserId(User).Value;
var userId = _userService.GetProperUserId(User) ?? throw new InvalidOperationException("User ID not found");
var send = await _sendRepository.GetByIdAsync(new Guid(id));
if (send == null || send.UserId != userId)
{
throw new NotFoundException();
}
// This endpoint exists because PUT preserves existing Password/Emails when not provided.
// This allows clients to update other fields without re-submitting sensitive auth data.
send.Password = null;
send.AuthType = AuthType.None;
await _nonAnonymousSendCommand.SaveSendAsync(send);
return new SendResponseModel(send, _globalSettings);
return new SendResponseModel(send);
}
[HttpDelete("{id}")]
public async Task Delete(string id)
{
var userId = _userService.GetProperUserId(User).Value;
var userId = _userService.GetProperUserId(User) ?? throw new InvalidOperationException("User ID not found");
var send = await _sendRepository.GetByIdAsync(new Guid(id));
if (send == null || send.UserId != userId)
{

View File

@@ -3,6 +3,7 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using Bit.Api.Tools.Utilities;
using Bit.Core.Exceptions;
using Bit.Core.Tools.Entities;
using Bit.Core.Tools.Enums;
@@ -10,35 +11,119 @@ using Bit.Core.Tools.Models.Data;
using Bit.Core.Tools.Services;
using Bit.Core.Utilities;
using static System.StringSplitOptions;
namespace Bit.Api.Tools.Models.Request;
/// <summary>
/// A send request issued by a Bitwarden client
/// </summary>
public class SendRequestModel
{
/// <summary>
/// Indicates whether the send contains text or file data.
/// </summary>
public SendType Type { get; set; }
/// <summary>
/// Specifies the authentication method required to access this Send.
/// </summary>
public AuthType? AuthType { get; set; }
/// <summary>
/// Estimated length of the file accompanying the send. <see langword="null"/> when
/// <see cref="Type"/> is <see cref="SendType.Text"/>.
/// </summary>
public long? FileLength { get; set; } = null;
/// <summary>
/// Label for the send.
/// </summary>
[EncryptedString]
[EncryptedStringLength(1000)]
public string Name { get; set; }
/// <summary>
/// Notes for the send. This is only visible to the owner of the send.
/// </summary>
[EncryptedString]
[EncryptedStringLength(1000)]
public string Notes { get; set; }
/// <summary>
/// A base64-encoded byte array containing the Send's encryption key. This key is
/// also provided to send recipients in the Send's URL.
/// </summary>
[Required]
[EncryptedString]
[EncryptedStringLength(1000)]
public string Key { get; set; }
/// <summary>
/// The maximum number of times a send can be accessed before it expires.
/// When this value is <see langword="null" />, there is no limit.
/// </summary>
[Range(1, int.MaxValue)]
public int? MaxAccessCount { get; set; }
/// <summary>
/// The date after which a send cannot be accessed. When this value is
/// <see langword="null"/>, there is no expiration date.
/// </summary>
public DateTime? ExpirationDate { get; set; }
/// <summary>
/// The date after which a send may be automatically deleted from the server.
/// When this is <see langword="null" />, the send may be deleted after it has
/// exceeded the global send timeout limit.
/// </summary>
[Required]
public DateTime? DeletionDate { get; set; }
/// <summary>
/// Contains file metadata uploaded with the send.
/// The file content is uploaded separately.
/// </summary>
public SendFileModel File { get; set; }
/// <summary>
/// Contains text data uploaded with the send.
/// </summary>
public SendTextModel Text { get; set; }
/// <summary>
/// Base64-encoded byte array of a password hash that grants access to the send.
/// Mutually exclusive with <see cref="Emails"/>.
/// </summary>
[StringLength(1000)]
public string Password { get; set; }
/// <summary>
/// Comma-separated list of emails that may access the send using OTP
/// authentication. Mutually exclusive with <see cref="Password"/>.
/// </summary>
[StringLength(4000)]
public string Emails { get; set; }
/// <summary>
/// When <see langword="true"/>, send access is disabled.
/// Defaults to <see langword="false"/>.
/// </summary>
[Required]
public bool? Disabled { get; set; }
/// <summary>
/// When <see langword="true"/> send access hides the user's email address
/// and displays a confirmation message instead. Defaults to <see langword="false"/>.
/// </summary>
public bool? HideEmail { get; set; }
/// <summary>
/// Transforms the request into a send object.
/// </summary>
/// <param name="userId">The user that owns the send.</param>
/// <param name="sendAuthorizationService">Hashes the send password.</param>
/// <returns>The send object</returns>
public Send ToSend(Guid userId, ISendAuthorizationService sendAuthorizationService)
{
var send = new Send
@@ -46,12 +131,21 @@ public class SendRequestModel
Type = Type,
UserId = (Guid?)userId
};
ToSend(send, sendAuthorizationService);
send = UpdateSend(send, sendAuthorizationService);
return send;
}
/// <summary>
/// Transforms the request into a send object and file data.
/// </summary>
/// <param name="userId">The user that owns the send.</param>
/// <param name="fileName">Name of the file uploaded with the send.</param>
/// <param name="sendAuthorizationService">Hashes the send password.</param>
/// <returns>The send object and file data.</returns>
public (Send, SendFileData) ToSend(Guid userId, string fileName, ISendAuthorizationService sendAuthorizationService)
{
// FIXME: This method does two things: creates a send and a send file data.
// It should only do one thing.
var send = ToSendBase(new Send
{
Type = Type,
@@ -61,7 +155,13 @@ public class SendRequestModel
return (send, data);
}
public Send ToSend(Send existingSend, ISendAuthorizationService sendAuthorizationService)
/// <summary>
/// Update a send object with request content
/// </summary>
/// <param name="existingSend">The send to update</param>
/// <param name="sendAuthorizationService">Hashes the send password.</param>
/// <returns>The send object</returns>
public Send UpdateSend(Send existingSend, ISendAuthorizationService sendAuthorizationService)
{
existingSend = ToSendBase(existingSend, sendAuthorizationService);
switch (existingSend.Type)
@@ -81,6 +181,12 @@ public class SendRequestModel
return existingSend;
}
/// <summary>
/// Validates that the request is internally consistent for send creation.
/// </summary>
/// <exception cref="BadRequestException">
/// Thrown when the send's expiration date has already expired.
/// </exception>
public void ValidateCreation()
{
var now = DateTime.UtcNow;
@@ -94,6 +200,13 @@ public class SendRequestModel
ValidateEdit();
}
/// <summary>
/// Validates that the request is internally consistent for send administration.
/// </summary>
/// <exception cref="BadRequestException">
/// Thrown when the send's deletion date has already expired or when its
/// expiration occurs after its deletion.
/// </exception>
public void ValidateEdit()
{
var now = DateTime.UtcNow;
@@ -134,12 +247,30 @@ public class SendRequestModel
existingSend.ExpirationDate = ExpirationDate;
existingSend.DeletionDate = DeletionDate.Value;
existingSend.MaxAccessCount = MaxAccessCount;
if (!string.IsNullOrWhiteSpace(Password))
if (!string.IsNullOrWhiteSpace(Emails))
{
// normalize encoding
var emails = Emails.Split(',', RemoveEmptyEntries | TrimEntries);
existingSend.Emails = string.Join(",", emails);
existingSend.Password = null;
existingSend.AuthType = Core.Tools.Enums.AuthType.Email;
}
else if (!string.IsNullOrWhiteSpace(Password))
{
existingSend.Password = authorizationService.HashPassword(Password);
existingSend.Emails = null;
existingSend.AuthType = Core.Tools.Enums.AuthType.Password;
}
else
{
// Neither Password nor Emails provided - preserve existing values and infer AuthType
existingSend.AuthType = SendUtilities.InferAuthType(existingSend);
}
existingSend.Disabled = Disabled.GetValueOrDefault();
existingSend.HideEmail = HideEmail.GetValueOrDefault();
return existingSend;
}
@@ -149,8 +280,15 @@ public class SendRequestModel
}
}
/// <summary>
/// A send request issued by a Bitwarden client
/// </summary>
public class SendWithIdRequestModel : SendRequestModel
{
/// <summary>
/// Identifies the send. When this is <see langword="null" />, the client is requesting
/// a new send.
/// </summary>
[Required]
public Guid? Id { get; set; }
}

View File

@@ -3,7 +3,6 @@
using System.Text.Json;
using Bit.Core.Models.Api;
using Bit.Core.Settings;
using Bit.Core.Tools.Entities;
using Bit.Core.Tools.Enums;
using Bit.Core.Tools.Models.Data;
@@ -11,9 +10,22 @@ using Bit.Core.Utilities;
namespace Bit.Api.Tools.Models.Response;
/// <summary>
/// A response issued to a Bitwarden client in response to access operations.
/// </summary>
public class SendAccessResponseModel : ResponseModel
{
public SendAccessResponseModel(Send send, GlobalSettings globalSettings)
/// <summary>
/// Instantiates a send access response model
/// </summary>
/// <param name="send">Content to transmit to the client.</param>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="send"/> is <see langword="null" />
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="send" /> has an invalid <see cref="Send.Type"/>.
/// </exception>
public SendAccessResponseModel(Send send)
: base("send-access")
{
if (send == null)
@@ -23,6 +35,7 @@ public class SendAccessResponseModel : ResponseModel
Id = CoreHelpers.Base64UrlEncode(send.Id.ToByteArray());
Type = send.Type;
AuthType = send.AuthType;
SendData sendData;
switch (send.Type)
@@ -45,11 +58,52 @@ public class SendAccessResponseModel : ResponseModel
ExpirationDate = send.ExpirationDate;
}
/// <summary>
/// Identifies the send in a send URL
/// </summary>
public string Id { get; set; }
/// <summary>
/// Indicates whether the send contains text or file data.
/// </summary>
public SendType Type { get; set; }
/// <summary>
/// Specifies the authentication method required to access this Send.
/// </summary>
public AuthType? AuthType { get; set; }
/// <summary>
/// Label for the send. This is only visible to the owner of the send.
/// </summary>
/// <remarks>
/// This field contains a base64-encoded byte array. The array contains
/// the E2E-encrypted encrypted content.
/// </remarks>
public string Name { get; set; }
/// <summary>
/// Describes the file attached to the send.
/// </summary>
/// <remarks>
/// File content is downloaded separately using
/// <see cref="Bit.Api.Tools.Controllers.SendsController.GetSendFileDownloadData" />
/// </remarks>
public SendFileModel File { get; set; }
/// <summary>
/// Contains text data uploaded with the send.
/// </summary>
public SendTextModel Text { get; set; }
/// <summary>
/// The date after which a send cannot be accessed. When this value is
/// <see langword="null"/>, there is no expiration date.
/// </summary>
public DateTime? ExpirationDate { get; set; }
/// <summary>
/// Indicates the person that created the send to the accessor.
/// </summary>
public string CreatorIdentifier { get; set; }
}

View File

@@ -2,8 +2,8 @@
#nullable disable
using System.Text.Json;
using Bit.Api.Tools.Utilities;
using Bit.Core.Models.Api;
using Bit.Core.Settings;
using Bit.Core.Tools.Entities;
using Bit.Core.Tools.Enums;
using Bit.Core.Tools.Models.Data;
@@ -11,9 +11,23 @@ using Bit.Core.Utilities;
namespace Bit.Api.Tools.Models.Response;
/// <summary>
/// A response issued to a Bitwarden client in response to ownership operations.
/// </summary>
/// <seealso cref="SendAccessResponseModel" />
public class SendResponseModel : ResponseModel
{
public SendResponseModel(Send send, GlobalSettings globalSettings)
/// <summary>
/// Instantiates a send response model
/// </summary>
/// <param name="send">Content to transmit to the client.</param>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="send"/> is <see langword="null" />
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="send" /> has an invalid <see cref="Send.Type"/>.
/// </exception>
public SendResponseModel(Send send)
: base("send")
{
if (send == null)
@@ -24,6 +38,7 @@ public class SendResponseModel : ResponseModel
Id = send.Id;
AccessId = CoreHelpers.Base64UrlEncode(send.Id.ToByteArray());
Type = send.Type;
AuthType = send.AuthType ?? SendUtilities.InferAuthType(send);
Key = send.Key;
MaxAccessCount = send.MaxAccessCount;
AccessCount = send.AccessCount;
@@ -31,6 +46,7 @@ public class SendResponseModel : ResponseModel
ExpirationDate = send.ExpirationDate;
DeletionDate = send.DeletionDate;
Password = send.Password;
Emails = send.Emails;
Disabled = send.Disabled;
HideEmail = send.HideEmail.GetValueOrDefault();
@@ -55,20 +71,113 @@ public class SendResponseModel : ResponseModel
Notes = sendData.Notes;
}
/// <summary>
/// Identifies the send to its owner
/// </summary>
public Guid Id { get; set; }
/// <summary>
/// Identifies the send in a send URL
/// </summary>
public string AccessId { get; set; }
/// <summary>
/// Indicates whether the send contains text or file data.
/// </summary>
public SendType Type { get; set; }
/// <summary>
/// Specifies the authentication method required to access this Send.
/// </summary>
public AuthType? AuthType { get; set; }
/// <summary>
/// Label for the send.
/// </summary>
/// <remarks>
/// This field contains a base64-encoded byte array. The array contains
/// the E2E-encrypted encrypted content.
/// </remarks>
public string Name { get; set; }
/// <summary>
/// Notes for the send. This is only visible to the owner of the send.
/// This field is encrypted.
/// </summary>
/// <remarks>
/// This field contains a base64-encoded byte array. The array contains
/// the E2E-encrypted encrypted content.
/// </remarks>
public string Notes { get; set; }
/// <summary>
/// Contains file metadata uploaded with the send.
/// The file content is uploaded separately.
/// </summary>
public SendFileModel File { get; set; }
/// <summary>
/// Contains text data uploaded with the send.
/// </summary>
public SendTextModel Text { get; set; }
/// <summary>
/// A base64-encoded byte array containing the Send's encryption key.
/// It's also provided to send recipients in the Send's URL.
/// </summary>
/// <remarks>
/// This field contains a base64-encoded byte array. The array contains
/// the E2E-encrypted content.
/// </remarks>
public string Key { get; set; }
/// <summary>
/// The maximum number of times a send can be accessed before it expires.
/// When this value is <see langword="null" />, there is no limit.
/// </summary>
public int? MaxAccessCount { get; set; }
/// <summary>
/// The number of times a send has been accessed since it was created.
/// </summary>
public int AccessCount { get; set; }
/// <summary>
/// Base64-encoded byte array of a password hash that grants access to the send.
/// Mutually exclusive with <see cref="Emails"/>.
/// </summary>
public string Password { get; set; }
/// <summary>
/// Comma-separated list of emails that may access the send using OTP
/// authentication. Mutually exclusive with <see cref="Password"/>.
/// </summary>
public string Emails { get; set; }
/// <summary>
/// When <see langword="true"/>, send access is disabled.
/// </summary>
public bool Disabled { get; set; }
/// <summary>
/// The last time this send's data changed.
/// </summary>
public DateTime RevisionDate { get; set; }
/// <summary>
/// The date after which a send cannot be accessed. When this value is
/// <see langword="null"/>, there is no expiration date.
/// </summary>
public DateTime? ExpirationDate { get; set; }
/// <summary>
/// The date after which a send may be automatically deleted from the server.
/// </summary>
public DateTime DeletionDate { get; set; }
/// <summary>
/// When <see langword="true"/> send access hides the user's email address
/// and displays a confirmation message instead.
/// </summary>
public bool HideEmail { get; set; }
}

View File

@@ -0,0 +1,23 @@
namespace Bit.Api.Tools.Utilities;
using Bit.Core.Tools.Entities;
using Bit.Core.Tools.Enums;
public class SendUtilities
{
public static AuthType InferAuthType(Send send)
{
if (!string.IsNullOrWhiteSpace(send.Password))
{
return AuthType.Password;
}
if (!string.IsNullOrWhiteSpace(send.Emails))
{
return AuthType.Email;
}
return AuthType.None;
}
}

View File

@@ -56,7 +56,7 @@ public class SyncResponseModel() : ResponseModel("sync")
c => new CollectionDetailsResponseModel(c)) ?? new List<CollectionDetailsResponseModel>();
Domains = excludeDomains ? null : new DomainsResponseModel(user, false);
Policies = policies?.Select(p => new PolicyResponseModel(p)) ?? new List<PolicyResponseModel>();
Sends = sends.Select(s => new SendResponseModel(s, globalSettings));
Sends = sends.Select(s => new SendResponseModel(s));
UserDecryption = new UserDecryptionResponseModel
{
MasterPasswordUnlock = user.HasMasterPassword()

View File

@@ -1,3 +0,0 @@
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
public record DatadogIntegration(string ApiKey, Uri Uri);

View File

@@ -1,3 +0,0 @@
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
public record SlackIntegration(string Token);

View File

@@ -1,3 +0,0 @@
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
public record SlackIntegrationConfiguration(string ChannelId);

View File

@@ -99,8 +99,8 @@ public class CloudOrganizationSignUpCommand(
ReferenceData = signup.Owner.ReferenceData,
Enabled = true,
LicenseKey = CoreHelpers.SecureRandomString(20),
PublicKey = signup.PublicKey,
PrivateKey = signup.PrivateKey,
PublicKey = signup.Keys?.PublicKey,
PrivateKey = signup.Keys?.WrappedPrivateKey,
CreationDate = DateTime.UtcNow,
RevisionDate = DateTime.UtcNow,
Status = OrganizationStatusType.Created,

View File

@@ -0,0 +1,28 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.KeyManagement.Models.Data;
namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
public static class OrganizationExtensions
{
/// <summary>
/// Updates the organization public and private keys if provided and not already set.
/// This is legacy code for old organizations that were not created with a public/private keypair.
/// It is a soft migration that will silently migrate organizations when they perform certain actions,
/// e.g. change their details or upgrade their plan.
/// </summary>
public static void BackfillPublicPrivateKeys(this Organization organization, PublicKeyEncryptionKeyPairData? keyPair)
{
// Only backfill if both new keys are provided and both old keys are missing.
if (string.IsNullOrWhiteSpace(keyPair?.PublicKey) ||
string.IsNullOrWhiteSpace(keyPair.WrappedPrivateKey) ||
!string.IsNullOrWhiteSpace(organization.PublicKey) ||
!string.IsNullOrWhiteSpace(organization.PrivateKey))
{
return;
}
organization.PublicKey = keyPair.PublicKey;
organization.PrivateKey = keyPair.WrappedPrivateKey;
}
}

View File

@@ -93,8 +93,8 @@ public class ProviderClientOrganizationSignUpCommand : IProviderClientOrganizati
ReferenceData = signup.Owner.ReferenceData,
Enabled = true,
LicenseKey = CoreHelpers.SecureRandomString(20),
PublicKey = signup.PublicKey,
PrivateKey = signup.PrivateKey,
PublicKey = signup.Keys?.PublicKey,
PrivateKey = signup.Keys?.WrappedPrivateKey,
CreationDate = DateTime.UtcNow,
RevisionDate = DateTime.UtcNow,
Status = OrganizationStatusType.Created,

View File

@@ -39,8 +39,20 @@ public class OrganizationUpdateCommand(
var originalBillingEmail = organization.BillingEmail;
// Apply updates to organization
organization.UpdateDetails(request);
organization.BackfillPublicPrivateKeys(request);
// These values may or may not be sent by the client depending on the operation being performed.
// Skip any values not provided.
if (request.Name is not null)
{
organization.Name = request.Name;
}
if (request.BillingEmail is not null)
{
organization.BillingEmail = request.BillingEmail.ToLowerInvariant().Trim();
}
organization.BackfillPublicPrivateKeys(request.Keys);
await organizationService.ReplaceAndUpdateCacheAsync(organization, EventType.Organization_Updated);
// Update billing information in Stripe if required
@@ -56,7 +68,7 @@ public class OrganizationUpdateCommand(
/// </summary>
private async Task<Organization> UpdateSelfHostedAsync(Organization organization, OrganizationUpdateRequest request)
{
organization.BackfillPublicPrivateKeys(request);
organization.BackfillPublicPrivateKeys(request.Keys);
await organizationService.ReplaceAndUpdateCacheAsync(organization, EventType.Organization_Updated);
return organization;
}

View File

@@ -1,43 +0,0 @@
using Bit.Core.AdminConsole.Entities;
namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Update;
public static class OrganizationUpdateExtensions
{
/// <summary>
/// Updates the organization name and/or billing email.
/// Any null property on the request object will be skipped.
/// </summary>
public static void UpdateDetails(this Organization organization, OrganizationUpdateRequest request)
{
// These values may or may not be sent by the client depending on the operation being performed.
// Skip any values not provided.
if (request.Name is not null)
{
organization.Name = request.Name;
}
if (request.BillingEmail is not null)
{
organization.BillingEmail = request.BillingEmail.ToLowerInvariant().Trim();
}
}
/// <summary>
/// Updates the organization public and private keys if provided and not already set.
/// This is legacy code for old organizations that were not created with a public/private keypair. It is a soft
/// migration that will silently migrate organizations when they change their details.
/// </summary>
public static void BackfillPublicPrivateKeys(this Organization organization, OrganizationUpdateRequest request)
{
if (!string.IsNullOrWhiteSpace(request.PublicKey) && string.IsNullOrWhiteSpace(organization.PublicKey))
{
organization.PublicKey = request.PublicKey;
}
if (!string.IsNullOrWhiteSpace(request.EncryptedPrivateKey) && string.IsNullOrWhiteSpace(organization.PrivateKey))
{
organization.PrivateKey = request.EncryptedPrivateKey;
}
}
}

View File

@@ -1,4 +1,6 @@
namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Update;
using Bit.Core.KeyManagement.Models.Data;
namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Update;
/// <summary>
/// Request model for updating the name, billing email, and/or public-private keys for an organization (legacy migration code).
@@ -22,12 +24,7 @@ public record OrganizationUpdateRequest
public string? BillingEmail { get; init; }
/// <summary>
/// The organization's public key to set (optional, only set if not already present on the organization).
/// The organization's public/private key pair to set (optional, only set if not already present on the organization).
/// </summary>
public string? PublicKey { get; init; }
/// <summary>
/// The organization's encrypted private key to set (optional, only set if not already present on the organization).
/// </summary>
public string? EncryptedPrivateKey { get; init; }
public PublicKeyEncryptionKeyPairData? Keys { get; init; }
}

View File

@@ -72,6 +72,17 @@ public class OrganizationDataOwnershipPolicyRequirement : IPolicyRequirement
{
return _policyDetails.Any(p => p.OrganizationId == organizationId);
}
/// <summary>
/// Ignore storage limits if the organization has data ownership policy enabled.
/// Allows users to seamlessly migrate their data into the organization without being blocked by storage limits.
/// Organization admins will need to manage storage after migration should overages occur.
/// </summary>
public bool IgnoreStorageLimitsOnMigration(Guid organizationId)
{
return _policyDetails.Any(p => p.OrganizationId == organizationId &&
p.OrganizationUserStatus == OrganizationUserStatusType.Confirmed);
}
}
public record DefaultCollectionRequest(Guid OrganizationUserId, bool ShouldCreateDefaultCollection)

View File

@@ -1,14 +0,0 @@
#nullable enable
using Bit.Core.Enums;
using Bit.Core.Models.Data.Organizations;
namespace Bit.Core.Services;
public interface IIntegrationConfigurationDetailsCache
{
List<OrganizationIntegrationConfigurationDetails> GetConfigurationDetails(
Guid organizationId,
IntegrationType integrationType,
EventType eventType);
}

View File

@@ -5,12 +5,94 @@ public static class Policies
/// <summary>
/// Policy for managing access to the Send feature.
/// </summary>
public const string Send = "Send"; // [Authorize(Policy = Policies.Send)]
public const string Application = "Application"; // [Authorize(Policy = Policies.Application)]
public const string Web = "Web"; // [Authorize(Policy = Policies.Web)]
public const string Push = "Push"; // [Authorize(Policy = Policies.Push)]
/// <remarks>
/// <example>
/// Can be used with the <c>Authorize</c> attribute, for example:
/// <code>
/// [Authorize(Policy = Policies.Send)]
/// </code>
/// </example>
/// </remarks>
public const string Send = "Send";
/// <summary>
/// Policy to manage access to general API endpoints.
/// </summary>
/// <remarks>
/// <example>
/// Can be used with the <c>Authorize</c> attribute, for example:
/// <code>
/// [Authorize(Policy = Policies.Application)]
/// </code>
/// </example>
/// </remarks>
public const string Application = "Application";
/// <summary>
/// Policy to manage access to API endpoints intended for use by the Web Vault and browser extension only.
/// </summary>
/// <remarks>
/// <example>
/// Can be used with the <c>Authorize</c> attribute, for example:
/// <code>
/// [Authorize(Policy = Policies.Web)]
/// </code>
/// </example>
/// </remarks>
public const string Web = "Web";
/// <summary>
/// Policy to restrict access to API endpoints for the Push feature.
/// </summary>
/// <remarks>
/// <example>
/// Can be used with the <c>Authorize</c> attribute, for example:
/// <code>
/// [Authorize(Policy = Policies.Push)]
/// </code>
/// </example>
/// </remarks>
public const string Push = "Push";
// TODO: This is unused
public const string Licensing = "Licensing"; // [Authorize(Policy = Policies.Licensing)]
public const string Organization = "Organization"; // [Authorize(Policy = Policies.Organization)]
public const string Installation = "Installation"; // [Authorize(Policy = Policies.Installation)]
public const string Secrets = "Secrets"; // [Authorize(Policy = Policies.Secrets)]
/// <summary>
/// Policy to restrict access to API endpoints related to the Organization features.
/// </summary>
/// <remarks>
/// <example>
/// Can be used with the <c>Authorize</c> attribute, for example:
/// <code>
/// [Authorize(Policy = Policies.Licensing)]
/// </code>
/// </example>
/// </remarks>
public const string Organization = "Organization";
/// <summary>
/// Policy to restrict access to API endpoints related to the setting up new installations.
/// </summary>
/// <remarks>
/// <example>
/// Can be used with the <c>Authorize</c> attribute, for example:
/// <code>
/// [Authorize(Policy = Policies.Installation)]
/// </code>
/// </example>
/// </remarks>
public const string Installation = "Installation";
/// <summary>
/// Policy to restrict access to API endpoints for Secrets Manager features.
/// </summary>
/// <remarks>
/// <example>
/// Can be used with the <c>Authorize</c> attribute, for example:
/// <code>
/// [Authorize(Policy = Policies.Secrets)]
/// </code>
/// </example>
/// </remarks>
public const string Secrets = "Secrets";
}

View File

@@ -1,5 +1,6 @@
using Bit.Core.Billing.Caches;
using Bit.Core.Billing.Caches.Implementations;
using Bit.Core.Billing.Licenses;
using Bit.Core.Billing.Licenses.Extensions;
using Bit.Core.Billing.Organizations.Commands;
using Bit.Core.Billing.Organizations.Queries;
@@ -28,6 +29,7 @@ public static class ServiceCollectionExtensions
services.AddTransient<ISetupIntentCache, SetupIntentDistributedCache>();
services.AddTransient<ISubscriberService, SubscriberService>();
services.AddLicenseServices();
services.AddLicenseOperations();
services.AddPricingClient();
services.AddPaymentOperations();
services.AddOrganizationLicenseCommandsQueries();

View File

@@ -0,0 +1,44 @@
using System.Security.Claims;
using Bit.Core.Billing.Licenses.Extensions;
using Bit.Core.Billing.Models.Business;
using Bit.Core.Models.Api;
namespace Bit.Core.Billing.Licenses.Models.Api.Response;
/// <summary>
/// Response model containing user license information.
/// Separated from subscription data to maintain separation of concerns.
/// </summary>
public class LicenseResponseModel : ResponseModel
{
public LicenseResponseModel(UserLicense license, ClaimsPrincipal? claimsPrincipal)
: base("license")
{
License = license;
// CRITICAL: When a license has a Token (JWT), ALWAYS use the expiration from the token claim
// The token's expiration is cryptographically secured and cannot be tampered with
// The file's Expires property can be manually edited and should NOT be trusted for display
if (claimsPrincipal != null)
{
Expiration = claimsPrincipal.GetValue<DateTime?>(UserLicenseConstants.Expires);
}
else
{
// No token - use the license file expiration (for older licenses without tokens)
Expiration = license.Expires;
}
}
/// <summary>
/// The user's license containing feature entitlements and metadata.
/// </summary>
public UserLicense License { get; set; }
/// <summary>
/// The license expiration date.
/// Extracted from the cryptographically secured JWT token when available,
/// otherwise falls back to the license file's expiration date.
/// </summary>
public DateTime? Expiration { get; set; }
}

View File

@@ -0,0 +1,23 @@
using Bit.Core.Billing.Licenses.Models.Api.Response;
using Bit.Core.Billing.Services;
using Bit.Core.Entities;
using Bit.Core.Services;
namespace Bit.Core.Billing.Licenses.Queries;
public interface IGetUserLicenseQuery
{
Task<LicenseResponseModel> Run(User user);
}
public class GetUserLicenseQuery(
IUserService userService,
ILicensingService licensingService) : IGetUserLicenseQuery
{
public async Task<LicenseResponseModel> Run(User user)
{
var license = await userService.GenerateLicenseAsync(user);
var claimsPrincipal = licensingService.GetClaimsPrincipalFromLicense(license);
return new LicenseResponseModel(license, claimsPrincipal);
}
}

View File

@@ -0,0 +1,13 @@
using Bit.Core.Billing.Licenses.Queries;
using Microsoft.Extensions.DependencyInjection;
namespace Bit.Core.Billing.Licenses;
public static class Registrations
{
public static void AddLicenseOperations(this IServiceCollection services)
{
// Queries
services.AddTransient<IGetUserLicenseQuery, GetUserLicenseQuery>();
}
}

View File

@@ -228,11 +228,16 @@ public static class FeatureFlagKeys
public const string CxpExportMobile = "cxp-export-mobile";
/* Platform Team */
public const string WebPush = "web-push";
public const string IpcChannelFramework = "ipc-channel-framework";
public const string PushNotificationsWhenLocked = "pm-19388-push-notifications-when-locked";
public const string PushNotificationsWhenInactive = "pm-25130-receive-push-notifications-for-inactive-users";
/* Tools Team */
/// <summary>
/// Enable this flag to share the send view used by the web and browser clients
/// on the desktop client.
/// </summary>
public const string DesktopSendUIRefresh = "desktop-send-ui-refresh";
public const string UseSdkPasswordGenerators = "pm-19976-use-sdk-password-generators";
public const string UseChromiumImporter = "pm-23982-chromium-importer";
@@ -240,11 +245,20 @@ public static class FeatureFlagKeys
public const string SendUIRefresh = "pm-28175-send-ui-refresh";
public const string SendEmailOTP = "pm-19051-send-email-verification";
/// <summary>
/// Enable this flag to output email/OTP authenticated sends from the `GET sends` endpoint. When
/// this flag is disabled, the `GET sends` endpoint omits email/OTP authenticated sends.
/// </summary>
/// <remarks>
/// This flag is server-side only, and only inhibits the endpoint returning all sends.
/// Email/OTP sends can still be created and downloaded through other endpoints.
/// </remarks>
public const string PM19051_ListEmailOtpSends = "tools-send-email-otp-listing";
/* Vault Team */
public const string CipherKeyEncryption = "cipher-key-encryption";
public const string PM19941MigrateCipherDomainToSdk = "pm-19941-migrate-cipher-domain-to-sdk";
public const string PhishingDetection = "phishing-detection";
public const string RemoveCardItemTypePolicy = "pm-16442-remove-card-item-type-policy";
public const string PM22134SdkCipherListView = "pm-22134-sdk-cipher-list-view";
public const string PM22136_SdkCipherEncryption = "pm-22136-sdk-cipher-encryption";
public const string PM23904_RiskInsightsForPremium = "pm-23904-risk-insights-for-premium";

View File

@@ -1,8 +1,8 @@
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Dirt.Enums;
using Bit.Core.Entities;
using Bit.Core.Utilities;
namespace Bit.Core.AdminConsole.Entities;
namespace Bit.Core.Dirt.Entities;
public class OrganizationIntegration : ITableObject<Guid>
{

View File

@@ -2,7 +2,7 @@
using Bit.Core.Enums;
using Bit.Core.Utilities;
namespace Bit.Core.AdminConsole.Entities;
namespace Bit.Core.Dirt.Entities;
public class OrganizationIntegrationConfiguration : ITableObject<Guid>
{

View File

@@ -1,4 +1,4 @@
namespace Bit.Core.Enums;
namespace Bit.Core.Dirt.Enums;
public enum IntegrationType : int
{

View File

@@ -1,4 +1,4 @@
namespace Bit.Api.AdminConsole.Models.Response.Organizations;
namespace Bit.Core.Dirt.Enums;
public enum OrganizationIntegrationStatus : int
{

View File

@@ -1,13 +1,15 @@
using Azure.Messaging.ServiceBus;
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations;
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
using Bit.Core.AdminConsole.Models.Teams;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.AdminConsole.Services;
using Bit.Core.AdminConsole.Services.NoopImplementations;
using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations;
using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations;
using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations.Interfaces;
using Bit.Core.Dirt.Models.Data.EventIntegrations;
using Bit.Core.Dirt.Models.Data.Teams;
using Bit.Core.Dirt.Repositories;
using Bit.Core.Dirt.Services;
using Bit.Core.Dirt.Services.Implementations;
using Bit.Core.Dirt.Services.NoopImplementations;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;

View File

@@ -1,13 +1,13 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
using Bit.Core.AdminConsole.Services;
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
using Bit.Core.Dirt.Repositories;
using Bit.Core.Dirt.Services;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Utilities;
using Microsoft.Extensions.DependencyInjection;
using ZiggyCreatures.Caching.Fusion;
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations;
/// <summary>
/// Command implementation for creating organization integration configurations with validation and cache invalidation support.

View File

@@ -1,11 +1,11 @@
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
using Bit.Core.Dirt.Repositories;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Utilities;
using Microsoft.Extensions.DependencyInjection;
using ZiggyCreatures.Caching.Fusion;
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations;
/// <summary>
/// Command implementation for deleting organization integration configurations with cache invalidation support.

View File

@@ -1,9 +1,9 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
using Bit.Core.Dirt.Repositories;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations;
/// <summary>
/// Query implementation for retrieving organization integration configurations.

View File

@@ -1,6 +1,6 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Dirt.Entities;
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
/// <summary>
/// Command interface for creating organization integration configurations.

View File

@@ -1,4 +1,4 @@
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
/// <summary>
/// Command interface for deleting organization integration configurations.

View File

@@ -1,6 +1,6 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Dirt.Entities;
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
/// <summary>
/// Query interface for retrieving organization integration configurations.

View File

@@ -1,6 +1,6 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Dirt.Entities;
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
/// <summary>
/// Command interface for updating organization integration configurations.

View File

@@ -1,13 +1,13 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
using Bit.Core.AdminConsole.Services;
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
using Bit.Core.Dirt.Repositories;
using Bit.Core.Dirt.Services;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Utilities;
using Microsoft.Extensions.DependencyInjection;
using ZiggyCreatures.Caching.Fusion;
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations;
/// <summary>
/// Command implementation for updating organization integration configurations with validation and cache invalidation support.

View File

@@ -1,12 +1,12 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces;
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations.Interfaces;
using Bit.Core.Dirt.Repositories;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Utilities;
using Microsoft.Extensions.DependencyInjection;
using ZiggyCreatures.Caching.Fusion;
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations;
namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations;
/// <summary>
/// Command implementation for creating organization integrations with cache invalidation support.

View File

@@ -1,11 +1,11 @@
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces;
using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations.Interfaces;
using Bit.Core.Dirt.Repositories;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Utilities;
using Microsoft.Extensions.DependencyInjection;
using ZiggyCreatures.Caching.Fusion;
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations;
namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations;
/// <summary>
/// Command implementation for deleting organization integrations with cache invalidation support.

View File

@@ -1,8 +1,8 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces;
using Bit.Core.Repositories;
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations.Interfaces;
using Bit.Core.Dirt.Repositories;
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations;
namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations;
/// <summary>
/// Query implementation for retrieving organization integrations.

View File

@@ -1,6 +1,6 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Dirt.Entities;
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces;
namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations.Interfaces;
/// <summary>
/// Command interface for creating an OrganizationIntegration.

View File

@@ -1,4 +1,4 @@
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces;
namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations.Interfaces;
/// <summary>
/// Command interface for deleting organization integrations.

View File

@@ -1,6 +1,6 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Dirt.Entities;
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces;
namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations.Interfaces;
/// <summary>
/// Query interface for retrieving organization integrations.

View File

@@ -1,6 +1,6 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Dirt.Entities;
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces;
namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations.Interfaces;
/// <summary>
/// Command interface for updating organization integrations.

View File

@@ -1,12 +1,12 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces;
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations.Interfaces;
using Bit.Core.Dirt.Repositories;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Utilities;
using Microsoft.Extensions.DependencyInjection;
using ZiggyCreatures.Caching.Fusion;
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations;
namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations;
/// <summary>
/// Command implementation for updating organization integrations with cache invalidation support.

View File

@@ -0,0 +1,3 @@
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public record DatadogIntegration(string ApiKey, Uri Uri);

View File

@@ -1,3 +1,3 @@
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public record DatadogIntegrationConfigurationDetails(string ApiKey, Uri Uri);

View File

@@ -1,7 +1,7 @@
using Bit.Core.Enums;
using Bit.Core.Dirt.Enums;
using Bit.Core.Settings;
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public class DatadogListenerConfiguration(GlobalSettings globalSettings)
: ListenerConfiguration(globalSettings), IIntegrationListenerConfiguration

View File

@@ -1,3 +1,3 @@
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public record HecIntegration(Uri Uri, string Scheme, string Token, string? Service = null);

View File

@@ -1,7 +1,7 @@
using Bit.Core.Enums;
using Bit.Core.Dirt.Enums;
using Bit.Core.Settings;
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public class HecListenerConfiguration(GlobalSettings globalSettings)
: ListenerConfiguration(globalSettings), IIntegrationListenerConfiguration

View File

@@ -1,4 +1,4 @@
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public interface IEventListenerConfiguration
{

View File

@@ -1,6 +1,6 @@
using Bit.Core.Enums;
using Bit.Core.Dirt.Enums;
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public interface IIntegrationListenerConfiguration : IEventListenerConfiguration
{

View File

@@ -1,6 +1,6 @@
using Bit.Core.Enums;
using Bit.Core.Dirt.Enums;
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public interface IIntegrationMessage
{

View File

@@ -1,4 +1,4 @@
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
/// <summary>
/// Categories of event integration failures used for classification and retry logic.

View File

@@ -1,4 +1,4 @@
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public class IntegrationFilterGroup
{

View File

@@ -1,4 +1,4 @@
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public enum IntegrationFilterOperation
{

View File

@@ -1,4 +1,4 @@
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public class IntegrationFilterRule
{

View File

@@ -1,4 +1,4 @@
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
/// <summary>
/// Represents the result of an integration handler operation, including success status,

View File

@@ -1,7 +1,7 @@
using System.Text.Json;
using Bit.Core.Enums;
using Bit.Core.Dirt.Enums;
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public class IntegrationMessage : IIntegrationMessage
{

View File

@@ -1,8 +1,8 @@
using System.Security.Cryptography;
using System.Text;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Dirt.Entities;
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public class IntegrationOAuthState
{

View File

@@ -4,7 +4,7 @@ using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public class IntegrationTemplateContext(EventMessage eventMessage)
{

View File

@@ -1,6 +1,6 @@
using Bit.Core.Settings;
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public abstract class ListenerConfiguration
{

View File

@@ -1,9 +1,8 @@
using System.Text.Json.Nodes;
using Bit.Core.Dirt.Enums;
using Bit.Core.Enums;
#nullable enable
namespace Bit.Core.Models.Data.Organizations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public class OrganizationIntegrationConfigurationDetails
{

View File

@@ -1,6 +1,6 @@
using Bit.Core.Settings;
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public class RepositoryListenerConfiguration(GlobalSettings globalSettings)
: ListenerConfiguration(globalSettings), IEventListenerConfiguration

View File

@@ -0,0 +1,3 @@
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public record SlackIntegration(string Token);

View File

@@ -0,0 +1,3 @@
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public record SlackIntegrationConfiguration(string ChannelId);

View File

@@ -1,3 +1,3 @@
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public record SlackIntegrationConfigurationDetails(string ChannelId, string Token);

View File

@@ -1,7 +1,7 @@
using Bit.Core.Enums;
using Bit.Core.Dirt.Enums;
using Bit.Core.Settings;
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public class SlackListenerConfiguration(GlobalSettings globalSettings) :
ListenerConfiguration(globalSettings), IIntegrationListenerConfiguration

View File

@@ -1,6 +1,6 @@
using Bit.Core.Models.Teams;
using Bit.Core.Dirt.Models.Data.Teams;
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public record TeamsIntegration(
string TenantId,

View File

@@ -1,3 +1,3 @@
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public record TeamsIntegrationConfigurationDetails(string ChannelId, Uri ServiceUrl);

View File

@@ -1,7 +1,7 @@
using Bit.Core.Enums;
using Bit.Core.Dirt.Enums;
using Bit.Core.Settings;
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public class TeamsListenerConfiguration(GlobalSettings globalSettings) :
ListenerConfiguration(globalSettings), IIntegrationListenerConfiguration

View File

@@ -1,3 +1,3 @@
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public record WebhookIntegration(Uri Uri, string? Scheme = null, string? Token = null);

View File

@@ -1,3 +1,3 @@
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public record WebhookIntegrationConfiguration(Uri Uri, string? Scheme = null, string? Token = null);

View File

@@ -1,3 +1,3 @@
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public record WebhookIntegrationConfigurationDetails(Uri Uri, string? Scheme = null, string? Token = null);

View File

@@ -1,7 +1,7 @@
using Bit.Core.Enums;
using Bit.Core.Dirt.Enums;
using Bit.Core.Settings;
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public class WebhookListenerConfiguration(GlobalSettings globalSettings)
: ListenerConfiguration(globalSettings), IIntegrationListenerConfiguration

View File

@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
namespace Bit.Core.Models.Slack;
namespace Bit.Core.Dirt.Models.Data.Slack;
public abstract class SlackApiResponse
{

View File

@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
namespace Bit.Core.Models.Teams;
namespace Bit.Core.Dirt.Models.Data.Teams;
/// <summary>Represents the response returned by the Microsoft OAuth 2.0 token endpoint.
/// See <see href="https://learn.microsoft.com/graph/auth-v2-user">Microsoft identity platform and OAuth 2.0

View File

@@ -1,6 +1,6 @@
using Microsoft.Bot.Connector.Authentication;
namespace Bit.Core.AdminConsole.Models.Teams;
namespace Bit.Core.Dirt.Models.Data.Teams;
public class TeamsBotCredentialProvider(string clientId, string clientSecret) : ICredentialProvider
{

View File

@@ -1,8 +1,10 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.Enums;
using Bit.Core.Dirt.Models.Data.EventIntegrations;
using Bit.Core.Enums;
using Bit.Core.Models.Data.Organizations;
using Bit.Core.Repositories;
namespace Bit.Core.Repositories;
namespace Bit.Core.Dirt.Repositories;
public interface IOrganizationIntegrationConfigurationRepository : IRepository<OrganizationIntegrationConfiguration, Guid>
{

Some files were not shown because too many files have changed in this diff Show More