Skip to content

Commit fd0b009

Browse files
committed
Fixup
1 parent b278f7d commit fd0b009

34 files changed

+713
-126
lines changed

Diff for: .github/workflows/ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
fail-fast: false
1616
matrix:
1717
docker-tag: [ ci, lts, previous-lts ]
18-
test: [ Streams, PersistentSubscriptions, Operations, Projections, Security, Misc ]
18+
test: [ Streams, PersistentSubscriptions, Operations, ProjectionManagement, UserManagement, Security, Misc ]
1919
name: Test CE (${{ matrix.docker-tag }})
2020
with:
2121
docker-tag: ${{ matrix.docker-tag }}

Diff for: .github/workflows/dispatch-ce.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
strategy:
1919
fail-fast: false
2020
matrix:
21-
test: [ Streams, PersistentSubscriptions, Operations, Projections, Security, Misc ]
21+
test: [ Streams, PersistentSubscriptions, Operations, ProjectionManagement, UserManagement, Security, Misc ]
2222
name: Test CE (${{ inputs.docker-tag }})
2323
with:
2424
docker-tag: ${{ inputs.docker-tag }}

Diff for: src/Kurrent.Client/Core/Certificates/X509Certificates.cs

+7-21
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
using System.Security.Cryptography;
44
using System.Security.Cryptography.X509Certificates;
5+
#pragma warning disable SYSLIB0057
56

67
#if NET48
78
using Org.BouncyCastle.Crypto;
@@ -13,35 +14,20 @@
1314
namespace EventStore.Client;
1415

1516
static class X509Certificates {
16-
// TODO SS: Use .NET 8 X509Certificate2.CreateFromPemFile(certPemFilePath, keyPemFilePath) once the Windows32Exception issue is resolved
1717
public static X509Certificate2 CreateFromPemFile(string certPemFilePath, string keyPemFilePath) {
1818
try {
19-
#if NET9_0_OR_GREATER
20-
using var publicCert = X509CertificateLoader.LoadCertificateFromFile(certPemFilePath);
19+
#if NET8_0_OR_GREATER
20+
using var certificate = X509Certificate2.CreateFromPemFile(certPemFilePath, keyPemFilePath);
2121
#else
2222
using var publicCert = new X509Certificate2(certPemFilePath);
23-
#endif
24-
using var privateKey = RSA.Create().ImportPrivateKeyFromFile(keyPemFilePath);
23+
using var privateKey = RSA.Create().ImportPrivateKeyFromFile(keyPemFilePath);
2524
using var certificate = publicCert.CopyWithPrivateKey(privateKey);
26-
27-
#if NET48
28-
return new(certificate.Export(X509ContentType.Pfx));
29-
#else
30-
return X509Certificate2.CreateFromPemFile(certPemFilePath, keyPemFilePath);
3125
#endif
26+
27+
return new X509Certificate2(certificate.Export(X509ContentType.Pfx));
3228
} catch (Exception ex) {
3329
throw new CryptographicException($"Failed to load private key: {ex.Message}");
3430
}
35-
36-
// Notes:
37-
// using X509Certificate2.CreateFromPemFile(certPemFilePath, keyPemFilePath) would be the ideal choice here,
38-
// but it's currently causing a Win32Exception specifically on Windows. Alternative implementation is used until the issue is resolved.
39-
//
40-
// Error: The SSL connection could not be established, see inner exception. AuthenticationException: Authentication failed because the platform
41-
// does not support ephemeral keys. Win32Exception: No credentials are available in the security package
42-
//
43-
// public static X509Certificate2 CreateFromPemFile(string certPemFilePath, string keyPemFilePath) =>
44-
// X509Certificate2.CreateFromPemFile(certPemFilePath, keyPemFilePath);
4531
}
4632
}
4733

@@ -66,7 +52,7 @@ public static RSA ImportPrivateKeyFromFile(this RSA rsa, string privateKeyPath)
6652
public static RSA ImportPrivateKeyFromFile(this RSA rsa, string privateKeyPath) {
6753
var (content, label) = LoadPemKeyFile(privateKeyPath);
6854

69-
var privateKey = string.Join(string.Empty, content[1..^1]);
55+
var privateKey = string.Join(string.Empty, content[1..^1]);
7056
var privateKeyBytes = Convert.FromBase64String(privateKey);
7157

7258
if (label == RsaPemLabels.Pkcs8PrivateKey)

Diff for: src/Kurrent.Client/Core/KurrentClientSettings.ConnectionString.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -317,8 +317,7 @@ static void ConfigureClientCertificate(KurrentClientSettings settings, IReadOnly
317317
);
318318

319319
try {
320-
settings.ConnectivitySettings.ClientCertificate =
321-
X509Certificates.CreateFromPemFile(certPemFilePath, keyPemFilePath);
320+
settings.ConnectivitySettings.ClientCertificate = X509Certificates.CreateFromPemFile(certPemFilePath, keyPemFilePath);
322321
} catch (Exception ex) {
323322
throw new InvalidClientCertificateException("Failed to create client certificate.", ex);
324323
}

Diff for: test/Kurrent.Client.Tests.Common/Fixtures/KurrentPermanentTestNode.cs

+5
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ public static KurrentFixtureOptions DefaultOptions() {
8585
["EVENTSTORE_ADVERTISE_HTTP_PORT_TO_CLIENT_AS"] = $"{NetworkPortProvider.DefaultEsdbPort}"
8686
};
8787

88+
if (GlobalEnvironment.DockerImage.Contains("commercial")) {
89+
defaultEnvironment["EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH"] = "/etc/eventstore/certs/ca";
90+
defaultEnvironment["EventStore__Plugins__UserCertificates__Enabled"] = "true";
91+
}
92+
8893
if (port != NetworkPortProvider.DefaultEsdbPort) {
8994
if (GlobalEnvironment.Variables.TryGetValue("ES_DOCKER_TAG", out var tag) && tag == "ci")
9095
defaultEnvironment["EVENTSTORE_ADVERTISE_NODE_PORT_TO_CLIENT_AS"] = $"{port}";

Diff for: test/Kurrent.Client.Tests.Common/Fixtures/KurrentTemporaryTestNode.cs

+6-1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@ public static KurrentFixtureOptions DefaultOptions() {
8282
["EVENTSTORE_ADVERTISE_HTTP_PORT_TO_CLIENT_AS"] = $"{NetworkPortProvider.DefaultEsdbPort}"
8383
};
8484

85+
if (GlobalEnvironment.DockerImage.Contains("commercial")) {
86+
defaultEnvironment["EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH"] = "/etc/eventstore/certs/ca";
87+
defaultEnvironment["EventStore__Plugins__UserCertificates__Enabled"] = "true";
88+
}
89+
8590
if (port != NetworkPortProvider.DefaultEsdbPort) {
8691
if (GlobalEnvironment.Variables.TryGetValue("ES_DOCKER_TAG", out var tag) && tag == "ci")
8792
defaultEnvironment["EVENTSTORE_ADVERTISE_NODE_PORT_TO_CLIENT_AS"] = $"{port}";
@@ -181,7 +186,7 @@ async Task<int> GetNextAvailablePort(TimeSpan delay = default) {
181186
#if NET
182187
if (socket.Connected) await socket.DisconnectAsync(true);
183188
#else
184-
if (socket.Connected) socket.Disconnect(true);
189+
if (socket.Connected) socket.Disconnect(true);
185190
#endif
186191
}
187192
}

Diff for: test/Kurrent.Client.Tests/ClientCertificatesTests.cs

+18-11
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
using EventStore.Client;
22
using Humanizer;
3+
using Kurrent.Client.Tests.TestNode;
34

45
namespace Kurrent.Client.Tests;
56

67
[Trait("Category", "Target:Misc")]
78
[Trait("Category", "Target:Plugins")]
89
[Trait("Category", "Type:UserCertificate")]
9-
public class ClientCertificateTests(ITestOutputHelper output, KurrentPermanentFixture fixture)
10-
: KurrentPermanentTests<KurrentPermanentFixture>(output, fixture) {
10+
public class ClientCertificateTests(ITestOutputHelper output, KurrentTemporaryFixture fixture)
11+
: KurrentTemporaryTests<KurrentTemporaryFixture>(output, fixture) {
1112
[SupportsPlugins.Theory(EventStoreRepository.Commercial, "This server version does not support plugins"), BadClientCertificatesTestCases]
1213
async Task bad_certificates_combinations_should_return_authentication_error(string userCertFile, string userKeyFile, string tlsCaFile) {
13-
var stream = Fixture.GetStreamName();
14-
var seedEvents = Fixture.CreateTestEvents();
15-
var connectionString = $"esdb://localhost:2113/?tls=true&userCertFile={userCertFile}&userKeyFile={userKeyFile}&tlsCaFile={tlsCaFile}";
14+
var stream = Fixture.GetStreamName();
15+
var seedEvents = Fixture.CreateTestEvents();
16+
var port = Fixture.Options.ClientSettings.ConnectivitySettings.ResolvedAddressOrDefault.Port;
17+
18+
var connectionString = $"esdb://localhost:{port}/?tls=true&userCertFile={userCertFile}&userKeyFile={userKeyFile}&tlsCaFile={tlsCaFile}";
1619

1720
var settings = KurrentClientSettings.Create(connectionString);
1821
settings.ConnectivitySettings.TlsVerifyCert.ShouldBeTrue();
@@ -24,9 +27,11 @@ async Task bad_certificates_combinations_should_return_authentication_error(stri
2427

2528
[SupportsPlugins.Theory(EventStoreRepository.Commercial, "This server version does not support plugins"), ValidClientCertificatesTestCases]
2629
async Task valid_certificates_combinations_should_write_to_stream(string userCertFile, string userKeyFile, string tlsCaFile) {
27-
var stream = Fixture.GetStreamName();
28-
var seedEvents = Fixture.CreateTestEvents();
29-
var connectionString = $"esdb://localhost:2113/?userCertFile={userCertFile}&userKeyFile={userKeyFile}&tlsCaFile={tlsCaFile}";
30+
var stream = Fixture.GetStreamName();
31+
var seedEvents = Fixture.CreateTestEvents();
32+
var port = Fixture.Options.ClientSettings.ConnectivitySettings.ResolvedAddressOrDefault.Port;
33+
34+
var connectionString = $"esdb://localhost:{port}/?userCertFile={userCertFile}&userKeyFile={userKeyFile}&tlsCaFile={tlsCaFile}";
3035

3136
var settings = KurrentClientSettings.Create(connectionString);
3237
settings.ConnectivitySettings.TlsVerifyCert.ShouldBeTrue();
@@ -39,9 +44,11 @@ async Task valid_certificates_combinations_should_write_to_stream(string userCer
3944

4045
[SupportsPlugins.Theory(EventStoreRepository.Commercial, "This server version does not support plugins"), BadClientCertificatesTestCases]
4146
async Task basic_authentication_should_take_precedence(string userCertFile, string userKeyFile, string tlsCaFile) {
42-
var stream = Fixture.GetStreamName();
43-
var seedEvents = Fixture.CreateTestEvents();
44-
var connectionString = $"esdb://admin:changeit@localhost:2113/?userCertFile={userCertFile}&userKeyFile={userKeyFile}&tlsCaFile={tlsCaFile}";
47+
var stream = Fixture.GetStreamName();
48+
var seedEvents = Fixture.CreateTestEvents();
49+
var port = Fixture.Options.ClientSettings.ConnectivitySettings.ResolvedAddressOrDefault.Port;
50+
51+
var connectionString = $"esdb://admin:changeit@localhost:{port}/?userCertFile={userCertFile}&userKeyFile={userKeyFile}&tlsCaFile={tlsCaFile}";
4552

4653
var settings = KurrentClientSettings.Create(connectionString);
4754
settings.ConnectivitySettings.TlsVerifyCert.ShouldBeTrue();
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System.Collections;
2+
using EventStore.Client;
3+
4+
namespace Kurrent.Client.Tests;
5+
6+
public abstract record InvalidCredentialsTestCase(TestUser User, Type ExpectedException);
7+
8+
public class InvalidCredentialsTestCases : IEnumerable<object?[]> {
9+
public IEnumerator<object?[]> GetEnumerator() {
10+
yield return new object?[] { new MissingCredentials() };
11+
yield return new object?[] { new WrongUsername() };
12+
yield return new object?[] { new WrongPassword() };
13+
}
14+
15+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
16+
17+
public record MissingCredentials() : InvalidCredentialsTestCase(Fakers.Users.WithNoCredentials(), typeof(AccessDeniedException)) {
18+
public override string ToString() => nameof(MissingCredentials);
19+
}
20+
21+
public record WrongUsername() : InvalidCredentialsTestCase(Fakers.Users.WithInvalidCredentials(false), typeof(NotAuthenticatedException)) {
22+
public override string ToString() => nameof(WrongUsername);
23+
}
24+
25+
public record WrongPassword() : InvalidCredentialsTestCase(Fakers.Users.WithInvalidCredentials(wrongPassword: false), typeof(NotAuthenticatedException)) {
26+
public override string ToString() => nameof(WrongPassword);
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using EventStore.Client;
2+
using Kurrent.Client.Tests.TestNode;
3+
4+
namespace Kurrent.Client.Tests.PersistentSubscriptions;
5+
6+
[Trait("Category", "Target:PersistentSubscriptions")]
7+
public class SubscribeToAllConnectToExistingWithStartFromNotSetTests(ITestOutputHelper output, KurrentTemporaryFixture fixture)
8+
: KurrentTemporaryTests<KurrentTemporaryFixture>(output, fixture) {
9+
[RetryFact]
10+
public async Task connect_to_existing_with_start_from_not_set() {
11+
var group = Fixture.GetGroupName();
12+
var stream = Fixture.GetStreamName();
13+
14+
foreach (var @event in Fixture.CreateTestEvents(10))
15+
await Fixture.Streams.AppendToStreamAsync(
16+
stream,
17+
StreamState.Any,
18+
[@event]
19+
);
20+
21+
await Fixture.Subscriptions.CreateToAllAsync(group, new(), userCredentials: TestCredentials.Root);
22+
var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root);
23+
24+
await Assert.ThrowsAsync<TimeoutException>(
25+
() => subscription.Messages
26+
.OfType<PersistentSubscriptionMessage.Event>()
27+
.Where(e => !SystemStreams.IsSystemStream(e.ResolvedEvent.OriginalStreamId))
28+
.AnyAsync()
29+
.AsTask()
30+
.WithTimeout()
31+
);
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using EventStore.Client;
2+
using Kurrent.Client.Tests.TestNode;
3+
4+
namespace Kurrent.Client.Tests.PersistentSubscriptions;
5+
6+
[Trait("Category", "Target:PersistentSubscriptions")]
7+
public class SubscribeToAllConnectToExistingWithStartFromSetToEndPositionTests(ITestOutputHelper output, KurrentTemporaryFixture fixture)
8+
: KurrentTemporaryTests<KurrentTemporaryFixture>(output, fixture) {
9+
[RetryFact]
10+
public async Task connect_to_existing_with_start_from_set_to_end_position() {
11+
var group = Fixture.GetGroupName();
12+
var stream = Fixture.GetStreamName();
13+
14+
foreach (var @event in Fixture.CreateTestEvents(10)) {
15+
await Fixture.Streams.AppendToStreamAsync(
16+
stream,
17+
StreamState.Any,
18+
[@event]
19+
);
20+
}
21+
22+
await Fixture.Subscriptions.CreateToAllAsync(group, new(startFrom: Position.End), userCredentials: TestCredentials.Root);
23+
24+
var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root);
25+
26+
await Assert.ThrowsAsync<TimeoutException>(
27+
() => subscription.Messages
28+
.OfType<PersistentSubscriptionMessage.Event>()
29+
.Where(e => !SystemStreams.IsSystemStream(e.ResolvedEvent.OriginalStreamId))
30+
.AnyAsync()
31+
.AsTask()
32+
.WithTimeout()
33+
);
34+
}
35+
}

Diff for: test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAllTests.cs

-52
Original file line numberDiff line numberDiff line change
@@ -86,31 +86,6 @@ await Fixture.Streams.AppendToStreamAsync(
8686
Assert.Equal(events[0].Event.EventId, resolvedEvent.Event.EventId);
8787
}
8888

89-
[RetryFact]
90-
public async Task connect_to_existing_with_start_from_not_set() {
91-
var group = Fixture.GetGroupName();
92-
var stream = Fixture.GetStreamName();
93-
94-
foreach (var @event in Fixture.CreateTestEvents(10))
95-
await Fixture.Streams.AppendToStreamAsync(
96-
stream,
97-
StreamState.Any,
98-
[@event]
99-
);
100-
101-
await Fixture.Subscriptions.CreateToAllAsync(group, new(), userCredentials: TestCredentials.Root);
102-
var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root);
103-
104-
await Assert.ThrowsAsync<TimeoutException>(
105-
() => subscription.Messages
106-
.OfType<PersistentSubscriptionMessage.Event>()
107-
.Where(e => !SystemStreams.IsSystemStream(e.ResolvedEvent.OriginalStreamId))
108-
.AnyAsync()
109-
.AsTask()
110-
.WithTimeout(TimeSpan.FromMilliseconds(250))
111-
);
112-
}
113-
11489
[RetryFact]
11590
public async Task connect_to_existing_with_start_from_not_set_then_event_written() {
11691
var group = Fixture.GetGroupName();
@@ -141,33 +116,6 @@ await Fixture.Streams.AppendToStreamAsync(
141116
Assert.Equal(expectedStreamId, resolvedEvent.Event.EventStreamId);
142117
}
143118

144-
[RetryFact]
145-
public async Task connect_to_existing_with_start_from_set_to_end_position() {
146-
var group = Fixture.GetGroupName();
147-
var stream = Fixture.GetStreamName();
148-
149-
foreach (var @event in Fixture.CreateTestEvents(10)) {
150-
await Fixture.Streams.AppendToStreamAsync(
151-
stream,
152-
StreamState.Any,
153-
[@event]
154-
);
155-
}
156-
157-
await Fixture.Subscriptions.CreateToAllAsync(group, new(startFrom: Position.End), userCredentials: TestCredentials.Root);
158-
159-
var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root);
160-
161-
await Assert.ThrowsAsync<TimeoutException>(
162-
() => subscription.Messages
163-
.OfType<PersistentSubscriptionMessage.Event>()
164-
.Where(e => !SystemStreams.IsSystemStream(e.ResolvedEvent.OriginalStreamId))
165-
.AnyAsync()
166-
.AsTask()
167-
.WithTimeout(TimeSpan.FromMilliseconds(250))
168-
);
169-
}
170-
171119
[RetryFact]
172120
public async Task connect_to_existing_with_start_from_set_to_end_position_then_event_written() {
173121
var stream = Fixture.GetStreamName();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using EventStore.Client;
2+
using Kurrent.Client.Tests.TestNode;
3+
4+
namespace Kurrent.Client.Tests.PersistentSubscriptions;
5+
6+
[Trait("Category", "Target:PersistentSubscriptions")]
7+
public class SubscribeToStreamConnectToExistingWithStartFromBeginningTests(ITestOutputHelper output, KurrentTemporaryFixture fixture)
8+
: KurrentTemporaryTests<KurrentTemporaryFixture>(output, fixture) {
9+
[RetryFact]
10+
public async Task connect_to_existing_with_start_from_beginning_and_no_streamconnect_to_existing_with_start_from_not_set_and_events_in_it() {
11+
var stream = Fixture.GetStreamName();
12+
var group = Fixture.GetGroupName();
13+
var events = Fixture.CreateTestEvents(10).ToArray();
14+
15+
await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events);
16+
await Fixture.Subscriptions.CreateToStreamAsync(
17+
stream,
18+
group,
19+
new(),
20+
userCredentials: TestCredentials.Root
21+
);
22+
23+
await using var subscription = Fixture.Subscriptions.SubscribeToStream(
24+
stream,
25+
group,
26+
userCredentials: TestCredentials.Root
27+
);
28+
29+
await Assert.ThrowsAsync<TimeoutException>(
30+
() => subscription.Messages.AnyAsync(message => message is PersistentSubscriptionMessage.Event).AsTask().WithTimeout()
31+
);
32+
}
33+
}

0 commit comments

Comments
 (0)