1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-23 20:28:55 -05:00

Merge branch 'main' into ac/pm-15160/create-providerclientorganizationsignup-command

This commit is contained in:
Rui Tomé 2025-06-23 11:10:08 +01:00 committed by GitHub
commit 85988fe76f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 77 additions and 127 deletions

View File

@ -4,6 +4,9 @@ on:
workflow_call:
pull_request:
types: [labeled, unlabeled, opened, reopened, synchronize]
permissions: {}
jobs:
enforce-label:
if: ${{ contains(github.event.*.labels.*.name, 'hold') || contains(github.event.*.labels.*.name, 'needs-qa') || contains(github.event.*.labels.*.name, 'DB-migrations-changed') || contains(github.event.*.labels.*.name, 'ephemeral-environment') }}

View File

@ -16,6 +16,9 @@ jobs:
changed-files:
name: Check for file changes
runs-on: ubuntu-22.04
permissions:
contents: read
pull-requests: write
outputs:
changes: ${{steps.check-changes.outputs.changes_detected}}

View File

@ -22,6 +22,8 @@ on:
required: false
type: string
permissions: {}
jobs:
setup:
name: Setup
@ -44,49 +46,11 @@ jobs:
echo "branch=$BRANCH" >> $GITHUB_OUTPUT
cut_branch:
name: Cut branch
if: ${{ needs.setup.outputs.branch != 'none' }}
needs: setup
runs-on: ubuntu-24.04
steps:
- name: Generate GH App token
uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1
id: app-token
with:
app-id: ${{ secrets.BW_GHAPP_ID }}
private-key: ${{ secrets.BW_GHAPP_KEY }}
- name: Check out target ref
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ inputs.target_ref }}
token: ${{ steps.app-token.outputs.token }}
- name: Check if ${{ needs.setup.outputs.branch }} branch exists
env:
BRANCH_NAME: ${{ needs.setup.outputs.branch }}
run: |
if [[ $(git ls-remote --heads origin $BRANCH_NAME) ]]; then
echo "$BRANCH_NAME already exists! Please delete $BRANCH_NAME before running again." >> $GITHUB_STEP_SUMMARY
exit 1
fi
- name: Cut branch
env:
BRANCH_NAME: ${{ needs.setup.outputs.branch }}
run: |
git switch --quiet --create $BRANCH_NAME
git push --quiet --set-upstream origin $BRANCH_NAME
bump_version:
name: Bump Version
if: ${{ always() }}
runs-on: ubuntu-24.04
needs:
- cut_branch
- setup
outputs:
version: ${{ steps.set-final-version-output.outputs.version }}
@ -187,14 +151,13 @@ jobs:
- name: Push changes
run: git push
cherry_pick:
name: Cherry-Pick Commit(s)
cut_branch:
name: Cut branch
if: ${{ needs.setup.outputs.branch != 'none' }}
runs-on: ubuntu-24.04
needs:
- bump_version
- setup
- bump_version
runs-on: ubuntu-24.04
steps:
- name: Generate GH App token
uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1
@ -203,78 +166,30 @@ jobs:
app-id: ${{ secrets.BW_GHAPP_ID }}
private-key: ${{ secrets.BW_GHAPP_KEY }}
- name: Check out main branch
- name: Check out target ref
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
ref: main
ref: ${{ inputs.target_ref }}
token: ${{ steps.app-token.outputs.token }}
- name: Configure Git
run: |
git config --local user.email "actions@github.com"
git config --local user.name "Github Actions"
- name: Install xmllint
run: |
sudo apt-get update
sudo apt-get install -y libxml2-utils
- name: Perform cherry-pick(s)
- name: Check if ${{ needs.setup.outputs.branch }} branch exists
env:
CUT_BRANCH: ${{ needs.setup.outputs.branch }}
BRANCH_NAME: ${{ needs.setup.outputs.branch }}
run: |
# Function for cherry-picking
cherry_pick () {
local source_branch=$1
local destination_branch=$2
# Get project commit/version from source branch
git switch $source_branch
SOURCE_COMMIT=$(git log --reverse --pretty=format:"%H" --max-count=1 Directory.Build.props)
SOURCE_VERSION=$(xmllint -xpath "/Project/PropertyGroup/Version/text()" Directory.Build.props)
# Get project commit/version from destination branch
git switch $destination_branch
DESTINATION_VERSION=$(xmllint -xpath "/Project/PropertyGroup/Version/text()" Directory.Build.props)
if [[ "$DESTINATION_VERSION" != "$SOURCE_VERSION" ]]; then
git cherry-pick --strategy-option=theirs -x $SOURCE_COMMIT
git push -u origin $destination_branch
fi
}
# If we are cutting 'hotfix-rc':
if [[ "$CUT_BRANCH" == "hotfix-rc" ]]; then
# If the 'rc' branch exists:
if [[ $(git ls-remote --heads origin rc) ]]; then
# Chery-pick from 'rc' into 'hotfix-rc'
cherry_pick rc hotfix-rc
# Cherry-pick from 'main' into 'rc'
cherry_pick main rc
# If the 'rc' branch does not exist:
else
# Cherry-pick from 'main' into 'hotfix-rc'
cherry_pick main hotfix-rc
fi
# If we are cutting 'rc':
elif [[ "$CUT_BRANCH" == "rc" ]]; then
# Cherry-pick from 'main' into 'rc'
cherry_pick main rc
if [[ $(git ls-remote --heads origin $BRANCH_NAME) ]]; then
echo "$BRANCH_NAME already exists! Please delete $BRANCH_NAME before running again." >> $GITHUB_STEP_SUMMARY
exit 1
fi
- name: Cut branch
env:
BRANCH_NAME: ${{ needs.setup.outputs.branch }}
run: |
git switch --quiet --create $BRANCH_NAME
git push --quiet --set-upstream origin $BRANCH_NAME
move_future_db_scripts:
name: Move finalization database scripts
needs: cherry_pick
needs: cut_branch
uses: ./.github/workflows/_move_finalization_db_scripts.yml
secrets: inherit

View File

@ -8,6 +8,11 @@ jobs:
stale:
name: Check for stale issues and PRs
runs-on: ubuntu-22.04
permissions:
actions: write
contents: read
issues: write
pull-requests: write
steps:
- name: Check
uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0

View File

@ -31,10 +31,17 @@ on:
- "test/Infrastructure.IntegrationTest/**" # Any changes to the tests
- "src/**/Entities/**/*.cs" # Database entity definitions
permissions:
contents: read
jobs:
test:
name: Run tests
runs-on: ubuntu-22.04
permissions:
contents: read
actions: read
checks: write
steps:
- name: Check out repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

View File

@ -20,6 +20,7 @@ public class RabbitMqIntegrationListenerService : BackgroundService
private readonly Lazy<Task<IChannel>> _lazyChannel;
private readonly IRabbitMqService _rabbitMqService;
private readonly ILogger<RabbitMqIntegrationListenerService> _logger;
private readonly TimeProvider _timeProvider;
public RabbitMqIntegrationListenerService(IIntegrationHandler handler,
string routingKey,
@ -27,7 +28,8 @@ public class RabbitMqIntegrationListenerService : BackgroundService
string retryQueueName,
int maxRetries,
IRabbitMqService rabbitMqService,
ILogger<RabbitMqIntegrationListenerService> logger)
ILogger<RabbitMqIntegrationListenerService> logger,
TimeProvider timeProvider)
{
_handler = handler;
_routingKey = routingKey;
@ -35,6 +37,7 @@ public class RabbitMqIntegrationListenerService : BackgroundService
_queueName = queueName;
_rabbitMqService = rabbitMqService;
_logger = logger;
_timeProvider = timeProvider;
_maxRetries = maxRetries;
_lazyChannel = new Lazy<Task<IChannel>>(() => _rabbitMqService.CreateChannelAsync());
}
@ -74,7 +77,7 @@ public class RabbitMqIntegrationListenerService : BackgroundService
var integrationMessage = JsonSerializer.Deserialize<IntegrationMessage>(json);
if (integrationMessage is not null &&
integrationMessage.DelayUntilDate.HasValue &&
integrationMessage.DelayUntilDate.Value > DateTime.UtcNow)
integrationMessage.DelayUntilDate.Value > _timeProvider.GetUtcNow().UtcDateTime)
{
await _rabbitMqService.RepublishToRetryQueueAsync(channel, ea);
await channel.BasicAckAsync(ea.DeliveryTag, false, cancellationToken);

View File

@ -9,7 +9,9 @@ using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Services;
public class WebhookIntegrationHandler(IHttpClientFactory httpClientFactory)
public class WebhookIntegrationHandler(
IHttpClientFactory httpClientFactory,
TimeProvider timeProvider)
: IntegrationHandlerBase<WebhookIntegrationConfigurationDetails>
{
private readonly HttpClient _httpClient = httpClientFactory.CreateClient(HttpClientName);
@ -39,7 +41,7 @@ public class WebhookIntegrationHandler(IHttpClientFactory httpClientFactory)
if (int.TryParse(value, out var seconds))
{
// Retry-after was specified in seconds. Adjust DelayUntilDate by the requested number of seconds.
result.DelayUntilDate = DateTime.UtcNow.AddSeconds(seconds);
result.DelayUntilDate = timeProvider.GetUtcNow().AddSeconds(seconds).UtcDateTime;
}
else if (DateTimeOffset.TryParseExact(value,
"r", // "r" is the round-trip format: RFC1123

View File

@ -138,6 +138,7 @@ public static class FeatureFlagKeys
public const string EnableNewCardCombinedExpiryAutofill = "enable-new-card-combined-expiry-autofill";
public const string MacOsNativeCredentialSync = "macos-native-credential-sync";
public const string InlineMenuTotp = "inline-menu-totp";
public const string WindowsDesktopAutotype = "windows-desktop-autotype";
/* Billing Team */
public const string AC2101UpdateTrialInitiationEmail = "AC-2101-update-trial-initiation-email";
@ -206,6 +207,7 @@ public static class FeatureFlagKeys
public const string EndUserNotifications = "pm-10609-end-user-notifications";
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 static List<string> GetAllKeys()
{

View File

@ -717,7 +717,8 @@ public static class ServiceCollectionExtensions
retryQueueName: integrationRetryQueueName,
maxRetries: maxRetries,
rabbitMqService: provider.GetRequiredService<IRabbitMqService>(),
logger: provider.GetRequiredService<ILogger<RabbitMqIntegrationListenerService>>()));
logger: provider.GetRequiredService<ILogger<RabbitMqIntegrationListenerService>>(),
timeProvider: provider.GetRequiredService<TimeProvider>()));
return services;
}

View File

@ -52,7 +52,6 @@ public class AzureServiceBusIntegrationListenerServiceTests
public async Task HandleMessageAsync_FailureNotRetryable_PublishesToDeadLetterQueue(IntegrationMessage<WebhookIntegrationConfiguration> message)
{
var sutProvider = GetSutProvider();
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1);
message.RetryCount = 0;
var result = new IntegrationHandlerResult(false, message);
@ -71,7 +70,6 @@ public class AzureServiceBusIntegrationListenerServiceTests
public async Task HandleMessageAsync_FailureRetryableButTooManyRetries_PublishesToDeadLetterQueue(IntegrationMessage<WebhookIntegrationConfiguration> message)
{
var sutProvider = GetSutProvider();
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1);
message.RetryCount = _maxRetries;
var result = new IntegrationHandlerResult(false, message);
result.Retryable = true;
@ -90,12 +88,10 @@ public class AzureServiceBusIntegrationListenerServiceTests
public async Task HandleMessageAsync_FailureRetryable_PublishesToRetryQueue(IntegrationMessage<WebhookIntegrationConfiguration> message)
{
var sutProvider = GetSutProvider();
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1);
message.RetryCount = 0;
var result = new IntegrationHandlerResult(false, message);
result.Retryable = true;
result.DelayUntilDate = DateTime.UtcNow.AddMinutes(1);
_handler.HandleAsync(Arg.Any<string>()).Returns(result);
var expected = (IntegrationMessage<WebhookIntegrationConfiguration>)IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson())!;
@ -110,7 +106,6 @@ public class AzureServiceBusIntegrationListenerServiceTests
public async Task HandleMessageAsync_SuccessfulResult_Succeeds(IntegrationMessage<WebhookIntegrationConfiguration> message)
{
var sutProvider = GetSutProvider();
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1);
var result = new IntegrationHandlerResult(true, message);
_handler.HandleAsync(Arg.Any<string>()).Returns(result);

View File

@ -4,6 +4,7 @@ using Bit.Core.Services;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Bit.Test.Common.Helpers;
using Microsoft.Extensions.Time.Testing;
using NSubstitute;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
@ -18,19 +19,24 @@ public class RabbitMqIntegrationListenerServiceTests
private const string _queueName = "test_queue";
private const string _retryQueueName = "test_queue_retry";
private const string _routingKey = "test_routing_key";
private readonly DateTime _now = new DateTime(2014, 3, 2, 1, 0, 0, DateTimeKind.Utc);
private readonly IIntegrationHandler _handler = Substitute.For<IIntegrationHandler>();
private readonly IRabbitMqService _rabbitMqService = Substitute.For<IRabbitMqService>();
private SutProvider<RabbitMqIntegrationListenerService> GetSutProvider()
{
return new SutProvider<RabbitMqIntegrationListenerService>()
var sutProvider = new SutProvider<RabbitMqIntegrationListenerService>()
.SetDependency(_handler)
.SetDependency(_rabbitMqService)
.SetDependency(_queueName, "queueName")
.SetDependency(_retryQueueName, "retryQueueName")
.SetDependency(_routingKey, "routingKey")
.SetDependency(_maxRetries, "maxRetries")
.WithFakeTimeProvider()
.Create();
sutProvider.GetDependency<FakeTimeProvider>().SetUtcNow(_now);
return sutProvider;
}
[Fact]
@ -55,7 +61,7 @@ public class RabbitMqIntegrationListenerServiceTests
var cancellationToken = CancellationToken.None;
await sutProvider.Sut.StartAsync(cancellationToken);
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1);
message.DelayUntilDate = null;
message.RetryCount = 0;
var eventArgs = new BasicDeliverEventArgs(
consumerTag: string.Empty,
@ -94,7 +100,7 @@ public class RabbitMqIntegrationListenerServiceTests
var cancellationToken = CancellationToken.None;
await sutProvider.Sut.StartAsync(cancellationToken);
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1);
message.DelayUntilDate = null;
message.RetryCount = _maxRetries;
var eventArgs = new BasicDeliverEventArgs(
consumerTag: string.Empty,
@ -132,7 +138,7 @@ public class RabbitMqIntegrationListenerServiceTests
var cancellationToken = CancellationToken.None;
await sutProvider.Sut.StartAsync(cancellationToken);
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1);
message.DelayUntilDate = null;
message.RetryCount = 0;
var eventArgs = new BasicDeliverEventArgs(
consumerTag: string.Empty,
@ -145,7 +151,7 @@ public class RabbitMqIntegrationListenerServiceTests
);
var result = new IntegrationHandlerResult(false, message);
result.Retryable = true;
result.DelayUntilDate = DateTime.UtcNow.AddMinutes(1);
result.DelayUntilDate = _now.AddMinutes(1);
_handler.HandleAsync(Arg.Any<string>()).Returns(result);
var expected = IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson());
@ -173,7 +179,7 @@ public class RabbitMqIntegrationListenerServiceTests
var cancellationToken = CancellationToken.None;
await sutProvider.Sut.StartAsync(cancellationToken);
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1);
message.DelayUntilDate = null;
var eventArgs = new BasicDeliverEventArgs(
consumerTag: string.Empty,
deliveryTag: 0,
@ -205,7 +211,7 @@ public class RabbitMqIntegrationListenerServiceTests
var cancellationToken = CancellationToken.None;
await sutProvider.Sut.StartAsync(cancellationToken);
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(1);
message.DelayUntilDate = _now.AddMinutes(1);
var eventArgs = new BasicDeliverEventArgs(
consumerTag: string.Empty,
deliveryTag: 0,

View File

@ -5,6 +5,7 @@ using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Bit.Test.Common.Helpers;
using Bit.Test.Common.MockedHttpClient;
using Microsoft.Extensions.Time.Testing;
using NSubstitute;
using Xunit;
@ -33,6 +34,7 @@ public class WebhookIntegrationHandlerTests
return new SutProvider<WebhookIntegrationHandler>()
.SetDependency(clientFactory)
.WithFakeTimeProvider()
.Create();
}
@ -62,9 +64,13 @@ public class WebhookIntegrationHandlerTests
}
[Theory, BitAutoData]
public async Task HandleAsync_TooManyRequests_ReturnsFailureSetsNotBeforUtc(IntegrationMessage<WebhookIntegrationConfigurationDetails> message)
public async Task HandleAsync_TooManyRequests_ReturnsFailureSetsDelayUntilDate(IntegrationMessage<WebhookIntegrationConfigurationDetails> message)
{
var sutProvider = GetSutProvider();
var now = new DateTime(2014, 3, 2, 1, 0, 0, DateTimeKind.Utc);
var retryAfter = now.AddSeconds(60);
sutProvider.GetDependency<FakeTimeProvider>().SetUtcNow(now);
message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl);
_handler.Fallback
@ -78,19 +84,21 @@ public class WebhookIntegrationHandlerTests
Assert.True(result.Retryable);
Assert.Equal(result.Message, message);
Assert.True(result.DelayUntilDate.HasValue);
Assert.InRange(result.DelayUntilDate.Value, DateTime.UtcNow.AddSeconds(59), DateTime.UtcNow.AddSeconds(61));
Assert.Equal(retryAfter, result.DelayUntilDate.Value);
Assert.Equal("Too Many Requests", result.FailureReason);
}
[Theory, BitAutoData]
public async Task HandleAsync_TooManyRequestsWithDate_ReturnsFailureSetsNotBeforUtc(IntegrationMessage<WebhookIntegrationConfigurationDetails> message)
public async Task HandleAsync_TooManyRequestsWithDate_ReturnsFailureSetsDelayUntilDate(IntegrationMessage<WebhookIntegrationConfigurationDetails> message)
{
var sutProvider = GetSutProvider();
var now = new DateTime(2014, 3, 2, 1, 0, 0, DateTimeKind.Utc);
var retryAfter = now.AddSeconds(60);
message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl);
_handler.Fallback
.WithStatusCode(HttpStatusCode.TooManyRequests)
.WithHeader("Retry-After", DateTime.UtcNow.AddSeconds(60).ToString("r")) // "r" is the round-trip format: RFC1123
.WithHeader("Retry-After", retryAfter.ToString("r"))
.WithContent(new StringContent("<html><head><title>test</title></head><body>test</body></html>"));
var result = await sutProvider.Sut.HandleAsync(message);
@ -99,7 +107,7 @@ public class WebhookIntegrationHandlerTests
Assert.True(result.Retryable);
Assert.Equal(result.Message, message);
Assert.True(result.DelayUntilDate.HasValue);
Assert.InRange(result.DelayUntilDate.Value, DateTime.UtcNow.AddSeconds(59), DateTime.UtcNow.AddSeconds(61));
Assert.Equal(retryAfter, result.DelayUntilDate.Value);
Assert.Equal("Too Many Requests", result.FailureReason);
}