From 96c7a31e986bf1bdfda05a38e78136c6e1334aa2 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Sat, 12 Sep 2026 18:23:41 -0400 Subject: [PATCH] fix(security): preserve authorization behavior over gRPC Signed-off-by: Yordis Prieto --- .../workflows/build-container-ubuntu-lts.yml | 2 +- scripts/test.sh | 17 +- .../Security/AuthenticationTestBase.cs | 268 ---------- .../all_stream_with_no_acl_security.cs | 87 ---- ...authorized_default_credentials_security.cs | 100 ---- .../Security/delete_stream_security.cs | 220 -------- .../Security/multiple_role_security.cs | 45 -- .../overriden_system_stream_security.cs | 138 ----- ...verriden_system_stream_security_for_all.cs | 117 ----- .../overriden_user_stream_security.cs | 140 ------ .../ClientAPI/Security/read_all_security.cs | 45 -- .../Security/read_stream_meta_security.cs | 93 ---- .../Security/read_stream_security.cs | 122 ----- .../Security/stream_security_inheritance.cs | 126 ----- .../Security/subscribe_to_all_security.cs | 40 -- .../Security/subscribe_to_stream_security.cs | 93 ---- .../Security/system_stream_security.cs | 282 ----------- .../transactional_write_stream_security.cs | 181 ------- .../Security/write_stream_meta_security.cs | 94 ---- .../Security/write_stream_security.cs | 101 ---- .../Grpc/Security/AuthenticationTestBase.cs | 471 ++++++++++++++++++ .../all_stream_with_no_acl_security.cs | 72 +++ ...authorized_default_credentials_security.cs | 101 ++++ .../Grpc/Security/delete_stream_security.cs | 118 +++++ .../Grpc/Security/multiple_role_security.cs | 46 ++ .../overriden_system_stream_security.cs | 41 ++ ...verriden_system_stream_security_for_all.cs | 34 ++ .../overriden_user_stream_security.cs | 41 ++ .../Grpc/Security/read_all_security.cs | 38 ++ .../Security/read_stream_meta_security.cs | 70 +++ .../Grpc/Security/read_stream_security.cs | 69 +++ .../Security/stream_security_inheritance.cs | 120 +++++ .../Security/subscribe_to_all_security.cs | 36 ++ .../Security/subscribe_to_stream_security.cs | 69 +++ .../Grpc/Security/system_stream_security.cs | 75 +++ .../transactional_write_stream_security.cs | 81 +++ .../Security/write_stream_meta_security.cs | 70 +++ .../Grpc/Security/write_stream_security.cs | 85 ++++ 38 files changed, 1649 insertions(+), 2299 deletions(-) delete mode 100644 src/EventStore.Core.Tests/ClientAPI/Security/AuthenticationTestBase.cs delete mode 100644 src/EventStore.Core.Tests/ClientAPI/Security/all_stream_with_no_acl_security.cs delete mode 100644 src/EventStore.Core.Tests/ClientAPI/Security/authorized_default_credentials_security.cs delete mode 100644 src/EventStore.Core.Tests/ClientAPI/Security/delete_stream_security.cs delete mode 100644 src/EventStore.Core.Tests/ClientAPI/Security/multiple_role_security.cs delete mode 100644 src/EventStore.Core.Tests/ClientAPI/Security/overriden_system_stream_security.cs delete mode 100644 src/EventStore.Core.Tests/ClientAPI/Security/overriden_system_stream_security_for_all.cs delete mode 100644 src/EventStore.Core.Tests/ClientAPI/Security/overriden_user_stream_security.cs delete mode 100644 src/EventStore.Core.Tests/ClientAPI/Security/read_all_security.cs delete mode 100644 src/EventStore.Core.Tests/ClientAPI/Security/read_stream_meta_security.cs delete mode 100644 src/EventStore.Core.Tests/ClientAPI/Security/read_stream_security.cs delete mode 100644 src/EventStore.Core.Tests/ClientAPI/Security/stream_security_inheritance.cs delete mode 100644 src/EventStore.Core.Tests/ClientAPI/Security/subscribe_to_all_security.cs delete mode 100644 src/EventStore.Core.Tests/ClientAPI/Security/subscribe_to_stream_security.cs delete mode 100644 src/EventStore.Core.Tests/ClientAPI/Security/system_stream_security.cs delete mode 100644 src/EventStore.Core.Tests/ClientAPI/Security/transactional_write_stream_security.cs delete mode 100644 src/EventStore.Core.Tests/ClientAPI/Security/write_stream_meta_security.cs delete mode 100644 src/EventStore.Core.Tests/ClientAPI/Security/write_stream_security.cs create mode 100644 src/EventStore.Core.Tests/Services/Transport/Grpc/Security/AuthenticationTestBase.cs create mode 100644 src/EventStore.Core.Tests/Services/Transport/Grpc/Security/all_stream_with_no_acl_security.cs create mode 100644 src/EventStore.Core.Tests/Services/Transport/Grpc/Security/authorized_default_credentials_security.cs create mode 100644 src/EventStore.Core.Tests/Services/Transport/Grpc/Security/delete_stream_security.cs create mode 100644 src/EventStore.Core.Tests/Services/Transport/Grpc/Security/multiple_role_security.cs create mode 100644 src/EventStore.Core.Tests/Services/Transport/Grpc/Security/overriden_system_stream_security.cs create mode 100644 src/EventStore.Core.Tests/Services/Transport/Grpc/Security/overriden_system_stream_security_for_all.cs create mode 100644 src/EventStore.Core.Tests/Services/Transport/Grpc/Security/overriden_user_stream_security.cs create mode 100644 src/EventStore.Core.Tests/Services/Transport/Grpc/Security/read_all_security.cs create mode 100644 src/EventStore.Core.Tests/Services/Transport/Grpc/Security/read_stream_meta_security.cs create mode 100644 src/EventStore.Core.Tests/Services/Transport/Grpc/Security/read_stream_security.cs create mode 100644 src/EventStore.Core.Tests/Services/Transport/Grpc/Security/stream_security_inheritance.cs create mode 100644 src/EventStore.Core.Tests/Services/Transport/Grpc/Security/subscribe_to_all_security.cs create mode 100644 src/EventStore.Core.Tests/Services/Transport/Grpc/Security/subscribe_to_stream_security.cs create mode 100644 src/EventStore.Core.Tests/Services/Transport/Grpc/Security/system_stream_security.cs create mode 100644 src/EventStore.Core.Tests/Services/Transport/Grpc/Security/transactional_write_stream_security.cs create mode 100644 src/EventStore.Core.Tests/Services/Transport/Grpc/Security/write_stream_meta_security.cs create mode 100644 src/EventStore.Core.Tests/Services/Transport/Grpc/Security/write_stream_security.cs diff --git a/.github/workflows/build-container-ubuntu-lts.yml b/.github/workflows/build-container-ubuntu-lts.yml index bbee6ddc43..7355e8fd5d 100644 --- a/.github/workflows/build-container-ubuntu-lts.yml +++ b/.github/workflows/build-container-ubuntu-lts.yml @@ -35,7 +35,7 @@ jobs: fail-fast: true matrix: include: - - test-group-name: core-clientapi-security + - test-group-name: core-grpc-security - test-group-name: core-clientapi-streams - test-group-name: core-http - test-group-name: core-services diff --git a/scripts/test.sh b/scripts/test.sh index b606d3c713..6de3fed7f2 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -9,6 +9,10 @@ core_clientapi_projects=( EventStore.Core.Tests ) +core_grpc_security_projects=( + EventStore.Core.Tests +) + core_rest_projects=( EventStore.Core.Tests ) @@ -65,8 +69,8 @@ load_requested_projects() { core-clientapi) requested_projects=("${core_clientapi_projects[@]}") ;; - core-clientapi-security) - requested_projects=("${core_clientapi_projects[@]}") + core-grpc-security) + requested_projects=("${core_grpc_security_projects[@]}") ;; core-clientapi-streams) requested_projects=("${core_clientapi_projects[@]}") @@ -114,6 +118,7 @@ validate_shard_coverage() { declared_projects="$( printf '%s\n' \ "${core_clientapi_projects[@]}" \ + "${core_grpc_security_projects[@]}" \ "${core_rest_projects[@]}" \ "${core_services_projects[@]}" \ "${core_cluster_services_projects[@]}" \ @@ -204,8 +209,8 @@ project_filter() { core-clientapi:EventStore.Core.Tests) printf '%s\n' "FullyQualifiedName~EventStore.Core.Tests.ClientAPI" ;; - core-clientapi-security:EventStore.Core.Tests) - printf '%s\n' "FullyQualifiedName~EventStore.Core.Tests.ClientAPI.Security" + core-grpc-security:EventStore.Core.Tests) + printf '%s\n' "FullyQualifiedName~EventStore.Core.Tests.Services.Transport.Grpc.Security" ;; core-clientapi-streams:EventStore.Core.Tests) printf '%s\n' "FullyQualifiedName~EventStore.Core.Tests.ClientAPI&FullyQualifiedName!~persistent&FullyQualifiedName!~Persistent&FullyQualifiedName!~EventStore.Core.Tests.ClientAPI.Security" @@ -214,7 +219,7 @@ project_filter() { printf '%s\n' "(FullyQualifiedName~EventStore.Core.Tests.Http|FullyQualifiedName~EventStore.Core.Tests.Services.Transport.Http)&FullyQualifiedName!~EventStore.Core.Tests.ClientAPI" ;; core-services:EventStore.Core.Tests) - printf '%s\n' "((FullyQualifiedName~EventStore.Core.Tests.Services&FullyQualifiedName!~EventStore.Core.Tests.Services.Storage&FullyQualifiedName!~EventStore.Core.Tests.Services.Transport.Http&FullyQualifiedName!~EventStore.Core.Tests.Services.Transport.Grpc.ServerFeaturesTests&FullyQualifiedName!~EventStore.Core.Tests.Services.ElectionsService)|FullyQualifiedName~EventStore.Core.Tests.Bus|FullyQualifiedName~EventStore.Core.Tests.Helpers|FullyQualifiedName~EventStore.Core.Tests.ClientOperations|FullyQualifiedName~EventStore.Core.Tests.Authentication|FullyQualifiedName~EventStore.Core.Tests.Authorization|FullyQualifiedName~EventStore.Core.Tests.Certificates|FullyQualifiedName~EventStore.Core.Tests.AwakeService|FullyQualifiedName~EventStore.Core.Tests.Settings|FullyQualifiedName~EventStore.Core.Tests.TcpApiTestPlugin)" + printf '%s\n' "((FullyQualifiedName~EventStore.Core.Tests.Services&FullyQualifiedName!~EventStore.Core.Tests.Services.Storage&FullyQualifiedName!~EventStore.Core.Tests.Services.Transport.Http&FullyQualifiedName!~EventStore.Core.Tests.Services.Transport.Grpc.Security&FullyQualifiedName!~EventStore.Core.Tests.Services.Transport.Grpc.ServerFeaturesTests&FullyQualifiedName!~EventStore.Core.Tests.Services.ElectionsService)|FullyQualifiedName~EventStore.Core.Tests.Bus|FullyQualifiedName~EventStore.Core.Tests.Helpers|FullyQualifiedName~EventStore.Core.Tests.ClientOperations|FullyQualifiedName~EventStore.Core.Tests.Authentication|FullyQualifiedName~EventStore.Core.Tests.Authorization|FullyQualifiedName~EventStore.Core.Tests.Certificates|FullyQualifiedName~EventStore.Core.Tests.AwakeService|FullyQualifiedName~EventStore.Core.Tests.Settings|FullyQualifiedName~EventStore.Core.Tests.TcpApiTestPlugin)" ;; core-cluster-services:EventStore.Core.Tests) printf '%s\n' "(FullyQualifiedName~EventStore.Core.Tests.Integration|FullyQualifiedName~EventStore.Core.Tests.Cluster|FullyQualifiedName~EventStore.Core.Tests.Replication|FullyQualifiedName~EventStore.Core.Tests.Synchronization|FullyQualifiedName~EventStore.Core.Tests.Services.ElectionsService|FullyQualifiedName~EventStore.Core.Tests.Services.Transport.Grpc.ServerFeaturesTests)" @@ -241,7 +246,7 @@ project_timeout() { core-clientapi:EventStore.Core.Tests) printf '%s\n' "20m" ;; - core-clientapi-security:EventStore.Core.Tests) + core-grpc-security:EventStore.Core.Tests) printf '%s\n' "20m" ;; core-clientapi-streams:EventStore.Core.Tests) diff --git a/src/EventStore.Core.Tests/ClientAPI/Security/AuthenticationTestBase.cs b/src/EventStore.Core.Tests/ClientAPI/Security/AuthenticationTestBase.cs deleted file mode 100644 index 63d7ca9947..0000000000 --- a/src/EventStore.Core.Tests/ClientAPI/Security/AuthenticationTestBase.cs +++ /dev/null @@ -1,268 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using EventStore.ClientAPI; -using EventStore.ClientAPI.SystemData; -using EventStore.Core.Messages; -using EventStore.Core.Messaging; -using EventStore.Core.Services; -using EventStore.Core.Services.UserManagement; -using EventStore.Core.Tests.ClientAPI.Helpers; -using EventStore.Core.Tests.Helpers; -using NUnit.Framework; - -namespace EventStore.Core.Tests.ClientAPI.Security; - -public abstract class AuthenticationTestBase : SpecificationWithDirectoryPerTestFixture -{ - private readonly UserCredentials _userCredentials; - private MiniNode _node; - protected IEventStoreConnection Connection; - - protected AuthenticationTestBase(UserCredentials userCredentials = null) - { - _userCredentials = userCredentials; - } - - - public virtual IEventStoreConnection SetupConnection(MiniNode node) - { - return TestConnection.Create(node.TcpEndPoint, TcpType.Ssl, _userCredentials); - } - - [OneTimeSetUp] - public override async Task TestFixtureSetUp() - { - await base.TestFixtureSetUp(); - _node = new MiniNode(PathName, enableTrustedAuth: true); - await _node.Start(); - - var userCreateEvent1 = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - _node.Node.MainQueue.Publish( - new UserManagementMessage.Create( - new CallbackEnvelope( - m => - { - Assert.IsTrue(m is UserManagementMessage.UpdateResult); - var msg = (UserManagementMessage.UpdateResult)m; - Assert.IsTrue(msg.Success); - - userCreateEvent1.SetResult(true); - }), - SystemAccounts.System, - "user1", - "Test User 1", - new string[0], - "pa$$1")); - - var userCreateEvent2 = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - _node.Node.MainQueue.Publish( - new UserManagementMessage.Create( - new CallbackEnvelope( - m => - { - Assert.IsTrue(m is UserManagementMessage.UpdateResult); - var msg = (UserManagementMessage.UpdateResult)m; - Assert.IsTrue(msg.Success); - - userCreateEvent2.SetResult(true); - }), - SystemAccounts.System, - "user2", - "Test User 2", - new string[0], - "pa$$2")); - - var adminCreateEvent2 = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - _node.Node.MainQueue.Publish( - new UserManagementMessage.Create( - new CallbackEnvelope( - m => - { - Assert.IsTrue(m is UserManagementMessage.UpdateResult); - var msg = (UserManagementMessage.UpdateResult)m; - Assert.IsTrue(msg.Success); - - adminCreateEvent2.SetResult(true); - }), - SystemAccounts.System, - "adm", - "Administrator User", - new[] { SystemRoles.Admins }, - "admpa$$")); - - Assert.IsTrue(await userCreateEvent1.Task.WithTimeout(10000), "User 1 creation failed"); - Assert.IsTrue(await userCreateEvent2.Task.WithTimeout(10000), "User 2 creation failed"); - Assert.IsTrue(await adminCreateEvent2.Task.WithTimeout(10000), "Administrator User creation failed"); - - Connection = SetupConnection(_node); - await Connection.ConnectAsync(); - - await Connection.SetStreamMetadataAsync("noacl-stream", ExpectedVersion.NoStream, StreamMetadata.Build()); - await Connection.SetStreamMetadataAsync( - "read-stream", - ExpectedVersion.NoStream, - StreamMetadata.Build().SetReadRole("user1")); - await Connection.SetStreamMetadataAsync( - "write-stream", - ExpectedVersion.NoStream, - StreamMetadata.Build().SetWriteRole("user1")); - await Connection.SetStreamMetadataAsync( - "metaread-stream", - ExpectedVersion.NoStream, - StreamMetadata.Build().SetMetadataReadRole("user1")); - await Connection.SetStreamMetadataAsync( - "metawrite-stream", - ExpectedVersion.NoStream, - StreamMetadata.Build().SetMetadataWriteRole("user1")); - - await Connection.SetStreamMetadataAsync( - "$all", - ExpectedVersion.Any, - StreamMetadata.Build().SetReadRole("user1"), - new UserCredentials("adm", "admpa$$")); - - await Connection.SetStreamMetadataAsync( - "$system-acl", - ExpectedVersion.NoStream, - StreamMetadata.Build() - .SetReadRole("user1") - .SetWriteRole("user1") - .SetMetadataReadRole("user1") - .SetMetadataWriteRole("user1"), - new UserCredentials("adm", "admpa$$")); - await Connection.SetStreamMetadataAsync( - "$system-adm", - ExpectedVersion.NoStream, - StreamMetadata.Build() - .SetReadRole(SystemRoles.Admins) - .SetWriteRole(SystemRoles.Admins) - .SetMetadataReadRole(SystemRoles.Admins) - .SetMetadataWriteRole(SystemRoles.Admins), - new UserCredentials("adm", "admpa$$")); - - await Connection.SetStreamMetadataAsync( - "normal-all", - ExpectedVersion.NoStream, - StreamMetadata.Build() - .SetReadRole(SystemRoles.All) - .SetWriteRole(SystemRoles.All) - .SetMetadataReadRole(SystemRoles.All) - .SetMetadataWriteRole(SystemRoles.All)); - await Connection.SetStreamMetadataAsync( - "$system-all", - ExpectedVersion.NoStream, - StreamMetadata.Build() - .SetReadRole(SystemRoles.All) - .SetWriteRole(SystemRoles.All) - .SetMetadataReadRole(SystemRoles.All) - .SetMetadataWriteRole(SystemRoles.All), - new UserCredentials("adm", "admpa$$")); - } - - [OneTimeTearDown] - public override async Task TestFixtureTearDown() - { - await _node.Shutdown(); - Connection.Close(); - await base.TestFixtureTearDown(); - } - - protected Task ReadEvent(string streamId, string login, string password) - { - return Connection.ReadEventAsync(streamId, -1, false, - login == null && password == null ? null : new UserCredentials(login, password)); - } - - protected Task ReadStreamForward(string streamId, string login, string password) - { - return Connection.ReadStreamEventsForwardAsync(streamId, 0, 1, false, - login == null && password == null ? null : new UserCredentials(login, password)); - } - - protected Task ReadStreamBackward(string streamId, string login, string password) - { - return Connection.ReadStreamEventsBackwardAsync(streamId, 0, 1, false, - login == null && password == null ? null : new UserCredentials(login, password)); - } - - protected Task WriteStream(string streamId, string login, string password) - { - return Connection.AppendToStreamAsync(streamId, ExpectedVersion.Any, CreateEvents(), - login == null && password == null ? null : new UserCredentials(login, password)); - } - - protected Task TransStart(string streamId, string login, string password) - { - return Connection.StartTransactionAsync(streamId, ExpectedVersion.Any, - login == null && password == null ? null : new UserCredentials(login, password)); - } - - protected Task ReadAllForward(string login, string password) - { - return Connection.ReadAllEventsForwardAsync(Position.Start, 1, false, - login == null && password == null ? null : new UserCredentials(login, password)); - } - - protected Task ReadAllBackward(string login, string password) - { - return Connection.ReadAllEventsBackwardAsync(Position.End, 1, false, - login == null && password == null ? null : new UserCredentials(login, password)); - } - - protected Task ReadMeta(string streamId, string login, string password) - { - return Connection.GetStreamMetadataAsRawBytesAsync(streamId, - login == null && password == null ? null : new UserCredentials(login, password)); - } - - protected Task WriteMeta(string streamId, string login, string password, string metawriteRole) - { - return Connection.SetStreamMetadataAsync(streamId, ExpectedVersion.Any, - metawriteRole == null - ? StreamMetadata.Build() - : StreamMetadata.Build().SetReadRole(metawriteRole) - .SetWriteRole(metawriteRole) - .SetMetadataReadRole(metawriteRole) - .SetMetadataWriteRole(metawriteRole), - login == null && password == null ? null : new UserCredentials(login, password)); - } - - protected async Task SubscribeToStream(string streamId, string login, string password) - { - using (await Connection.SubscribeToStreamAsync(streamId, false, (x, y) => Task.CompletedTask, - (x, y, z) => { }, - login == null && password == null ? null : new UserCredentials(login, password))) - { - } - } - - protected async Task SubscribeToAll(string login, string password) - { - using (await Connection.SubscribeToAllAsync(false, (x, y) => Task.CompletedTask, (x, y, z) => { }, - login == null && password == null ? null : new UserCredentials(login, password))) - { - } - } - - protected async Task CreateStreamWithMeta(StreamMetadata metadata, string streamPrefix = null) - { - var stream = (streamPrefix ?? string.Empty) + Guid.NewGuid().ToString(); - await Connection.SetStreamMetadataAsync(stream, ExpectedVersion.NoStream, - metadata, new UserCredentials("adm", "admpa$$")); - return stream; - } - - protected Task DeleteStream(string streamId, string login, string password) - { - return Connection.DeleteStreamAsync(streamId, ExpectedVersion.Any, true, - login == null && password == null ? null : new UserCredentials(login, password)); - } - - protected Task ExpectNoException(Func action) => action(); - - protected EventData[] CreateEvents() - { - return new[] { new EventData(Guid.NewGuid(), "some-type", false, new byte[] { 1, 2, 3 }, null) }; - } -} diff --git a/src/EventStore.Core.Tests/ClientAPI/Security/all_stream_with_no_acl_security.cs b/src/EventStore.Core.Tests/ClientAPI/Security/all_stream_with_no_acl_security.cs deleted file mode 100644 index 82d23ba1ce..0000000000 --- a/src/EventStore.Core.Tests/ClientAPI/Security/all_stream_with_no_acl_security.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System.Threading.Tasks; -using EventStore.ClientAPI; -using EventStore.ClientAPI.Exceptions; -using EventStore.ClientAPI.SystemData; -using NUnit.Framework; - -namespace EventStore.Core.Tests.ClientAPI.Security; - -[Category("ClientAPI"), Category("LongRunning"), Category("Network")] -[TestFixture(typeof(LogFormat.V2), typeof(string))] -public class all_stream_with_no_acl_security : AuthenticationTestBase -{ - [OneTimeSetUp] - public override async Task TestFixtureSetUp() - { - await base.TestFixtureSetUp(); - - await Connection.SetStreamMetadataAsync("$all", ExpectedVersion.Any, StreamMetadata.Build(), - new UserCredentials("adm", "admpa$$")); - } - - [Test] - public async Task write_to_all_is_never_allowed() - { - await AssertEx.ThrowsAsync(() => WriteStream("$all", null, null)); - await AssertEx.ThrowsAsync(() => WriteStream("$all", "user1", "pa$$1")); - await AssertEx.ThrowsAsync(() => WriteStream("$all", "adm", "admpa$$")); - } - - [Test] - public async Task delete_of_all_is_never_allowed() - { - await AssertEx.ThrowsAsync(() => DeleteStream("$all", null, null)); - await AssertEx.ThrowsAsync(() => DeleteStream("$all", "user1", "pa$$1")); - await AssertEx.ThrowsAsync(() => DeleteStream("$all", "adm", "admpa$$")); - } - - - [Test] - public async Task reading_and_subscribing_is_not_allowed_when_no_credentials_are_passed() - { - await AssertEx.ThrowsAsync(() => ReadEvent("$all", null, null)); - await AssertEx.ThrowsAsync(() => ReadStreamForward("$all", null, null)); - await AssertEx.ThrowsAsync(() => ReadStreamBackward("$all", null, null)); - await AssertEx.ThrowsAsync(() => ReadMeta("$all", null, null)); - await AssertEx.ThrowsAsync(() => SubscribeToStream("$all", null, null)); - } - - [Test] - public async Task reading_and_subscribing_is_not_allowed_for_usual_user() - { - await AssertEx.ThrowsAsync(() => ReadEvent("$all", "user1", "pa$$1")); - await AssertEx.ThrowsAsync(() => ReadStreamForward("$all", "user1", "pa$$1")); - await AssertEx.ThrowsAsync(() => ReadStreamBackward("$all", "user1", "pa$$1")); - await AssertEx.ThrowsAsync(() => ReadMeta("$all", "user1", "pa$$1")); - await AssertEx.ThrowsAsync(() => SubscribeToStream("$all", "user1", "pa$$1")); - } - - [Test] - public async Task reading_and_subscribing_is_allowed_for_admin_user() - { - await ReadEvent("$all", "adm", "admpa$$"); - await ReadStreamForward("$all", "adm", "admpa$$"); - await ReadStreamBackward("$all", "adm", "admpa$$"); - await ReadMeta("$all", "adm", "admpa$$"); - await SubscribeToStream("$all", "adm", "admpa$$"); - } - - - [Test] - public async Task meta_write_is_not_allowed_when_no_credentials_are_passed() - { - await AssertEx.ThrowsAsync(() => WriteMeta("$all", null, null, null)); - } - - [Test] - public async Task meta_write_is_not_allowed_for_usual_user() - { - await AssertEx.ThrowsAsync(() => WriteMeta("$all", "user1", "pa$$1", null)); - } - - [Test] - public async Task meta_write_is_allowed_for_admin_user() - { - await WriteMeta("$all", "adm", "admpa$$", null); - } -} diff --git a/src/EventStore.Core.Tests/ClientAPI/Security/authorized_default_credentials_security.cs b/src/EventStore.Core.Tests/ClientAPI/Security/authorized_default_credentials_security.cs deleted file mode 100644 index b2ee2aec05..0000000000 --- a/src/EventStore.Core.Tests/ClientAPI/Security/authorized_default_credentials_security.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System.Threading.Tasks; -using EventStore.ClientAPI.Exceptions; -using EventStore.ClientAPI.SystemData; -using NUnit.Framework; - -namespace EventStore.Core.Tests.ClientAPI.Security; - -[Category("ClientAPI"), Category("LongRunning"), Category("Network")] -[TestFixture(typeof(LogFormat.V2), typeof(string))] -public class authorized_default_credentials_security : AuthenticationTestBase -{ - public authorized_default_credentials_security() : base(new UserCredentials("user1", "pa$$1")) - { - } - - [Test] - public async Task all_operations_succeeds_when_passing_no_explicit_credentials() - { - await ReadAllForward(null, null); - await ReadAllBackward(null, null); - - await ReadEvent("read-stream", null, null); - await ReadStreamForward("read-stream", null, null); - await ReadStreamBackward("read-stream", null, null); - - await WriteStream("write-stream", null, null); - - if (LogFormatHelper.SupportsExplicitTransactions) - { - await ExpectNoException(async () => - { - var trans = await TransStart("write-stream", null, null); - await trans.WriteAsync(); - await trans.CommitAsync(); - }); - } - - await ReadMeta("metaread-stream", null, null); - await WriteMeta("metawrite-stream", null, null, "user1"); - - await SubscribeToStream("read-stream", null, null); - await SubscribeToAll(null, null); - } - - [Test] - public async Task all_operations_are_not_authenticated_when_overriden_with_not_existing_credentials() - { - await AssertEx.ThrowsAsync(() => ReadAllForward("badlogin", "badpass")); - await AssertEx.ThrowsAsync(() => ReadAllBackward("badlogin", "badpass")); - - await AssertEx.ThrowsAsync(() => ReadEvent("read-stream", "badlogin", "badpass")); - await AssertEx.ThrowsAsync(() => ReadStreamForward("read-stream", "badlogin", "badpass")); - await AssertEx.ThrowsAsync(() => ReadStreamBackward("read-stream", "badlogin", "badpass")); - - await AssertEx.ThrowsAsync(() => WriteStream("write-stream", "badlogin", "badpass")); - await AssertEx.ThrowsAsync(() => TransStart("write-stream", "badlogin", "badpass")); - - if (LogFormatHelper.SupportsExplicitTransactions) - { - var transId = (await TransStart("write-stream", null, null)).TransactionId; - var trans = Connection.ContinueTransaction(transId, new UserCredentials("badlogin", "badpass")); - await AssertEx.ThrowsAsync(() => trans.WriteAsync()); - await AssertEx.ThrowsAsync(() => trans.CommitAsync()); - } - - await AssertEx.ThrowsAsync(() => ReadMeta("metaread-stream", "badlogin", "badpass")); - await AssertEx.ThrowsAsync(() => WriteMeta("metawrite-stream", "badlogin", "badpass", "user1")); - - await AssertEx.ThrowsAsync(() => SubscribeToStream("read-stream", "badlogin", "badpass")); - await AssertEx.ThrowsAsync(() => SubscribeToAll("badlogin", "badpass")); - } - - [Test] - public async Task all_operations_are_not_authorized_when_overriden_with_not_authorized_credentials() - { - await AssertEx.ThrowsAsync(() => ReadAllForward("user2", "pa$$2")); - await AssertEx.ThrowsAsync(() => ReadAllBackward("user2", "pa$$2")); - - await AssertEx.ThrowsAsync(() => ReadEvent("read-stream", "user2", "pa$$2")); - await AssertEx.ThrowsAsync(() => ReadStreamForward("read-stream", "user2", "pa$$2")); - await AssertEx.ThrowsAsync(() => ReadStreamBackward("read-stream", "user2", "pa$$2")); - - await AssertEx.ThrowsAsync(() => WriteStream("write-stream", "user2", "pa$$2")); - await AssertEx.ThrowsAsync(() => TransStart("write-stream", "user2", "pa$$2")); - - if (LogFormatHelper.SupportsExplicitTransactions) - { - var transId = (await TransStart("write-stream", null, null)).TransactionId; - var trans = Connection.ContinueTransaction(transId, new UserCredentials("user2", "pa$$2")); - await AssertEx.ThrowsAsync(() => trans.WriteAsync()); - await AssertEx.ThrowsAsync(() => trans.CommitAsync()); - } - - await AssertEx.ThrowsAsync(() => ReadMeta("metaread-stream", "user2", "pa$$2")); - await AssertEx.ThrowsAsync(() => WriteMeta("metawrite-stream", "user2", "pa$$2", "user1")); - - await AssertEx.ThrowsAsync(() => SubscribeToStream("read-stream", "user2", "pa$$2")); - await AssertEx.ThrowsAsync(() => SubscribeToAll("user2", "pa$$2")); - } -} diff --git a/src/EventStore.Core.Tests/ClientAPI/Security/delete_stream_security.cs b/src/EventStore.Core.Tests/ClientAPI/Security/delete_stream_security.cs deleted file mode 100644 index 6283707220..0000000000 --- a/src/EventStore.Core.Tests/ClientAPI/Security/delete_stream_security.cs +++ /dev/null @@ -1,220 +0,0 @@ -using System.Threading.Tasks; -using EventStore.ClientAPI; -using EventStore.ClientAPI.Exceptions; -using EventStore.Core.Services; -using NUnit.Framework; - -namespace EventStore.Core.Tests.ClientAPI.Security; - -[Category("ClientAPI"), Category("LongRunning"), Category("Network")] -[TestFixture(typeof(LogFormat.V2), typeof(string))] -public class delete_stream_security : AuthenticationTestBase -{ - [Test] - public async Task delete_of_all_is_never_allowed() - { - await AssertEx.ThrowsAsync(() => DeleteStream("$all", null, null)); - await AssertEx.ThrowsAsync(() => DeleteStream("$all", "user1", "pa$$1")); - await AssertEx.ThrowsAsync(() => DeleteStream("$all", "adm", "admpa$$")); - } - - [Test] - public async Task deleting_normal_no_acl_stream_with_no_user_is_allowed() - { - var streamId = await CreateStreamWithMeta(StreamMetadata.Build()); - await DeleteStream(streamId, null, null); - } - - [Test] - public async Task deleting_normal_no_acl_stream_with_existing_user_is_allowed() - { - var streamId = await CreateStreamWithMeta(StreamMetadata.Build()); - await DeleteStream(streamId, "user1", "pa$$1"); - } - - [Test] - public async Task deleting_normal_no_acl_stream_with_admin_user_is_allowed() - { - var streamId = await CreateStreamWithMeta(StreamMetadata.Build()); - await DeleteStream(streamId, "adm", "admpa$$"); - } - - - [Test] - public async Task deleting_normal_user_stream_with_no_user_is_not_allowed() - { - var streamId = await CreateStreamWithMeta(StreamMetadata.Build().SetDeleteRole("user1")); - await AssertEx.ThrowsAsync(() => DeleteStream(streamId, null, null)); - } - - [Test] - public async Task deleting_normal_user_stream_with_not_authorized_user_is_not_allowed() - { - var streamId = await CreateStreamWithMeta(StreamMetadata.Build().SetDeleteRole("user1")); - await AssertEx.ThrowsAsync(() => DeleteStream(streamId, "user2", "pa$$2")); - } - - [Test] - public async Task deleting_normal_user_stream_with_authorized_user_is_allowed() - { - var streamId = await CreateStreamWithMeta(StreamMetadata.Build().SetDeleteRole("user1")); - await DeleteStream(streamId, "user1", "pa$$1"); - } - - [Test] - public async Task deleting_normal_user_stream_with_admin_user_is_allowed() - { - var streamId = await CreateStreamWithMeta(StreamMetadata.Build().SetDeleteRole("user1")); - await DeleteStream(streamId, "adm", "admpa$$"); - } - - - [Test] - public async Task deleting_normal_admin_stream_with_no_user_is_not_allowed() - { - var streamId = await CreateStreamWithMeta(StreamMetadata.Build().SetDeleteRole(SystemRoles.Admins)); - await AssertEx.ThrowsAsync(() => DeleteStream(streamId, null, null)); - } - - [Test] - public async Task deleting_normal_admin_stream_with_existing_user_is_not_allowed() - { - var streamId = await CreateStreamWithMeta(StreamMetadata.Build().SetDeleteRole(SystemRoles.Admins)); - await AssertEx.ThrowsAsync(() => DeleteStream(streamId, "user1", "pa$$1")); - } - - [Test] - public async Task deleting_normal_admin_stream_with_admin_user_is_allowed() - { - var streamId = await CreateStreamWithMeta(StreamMetadata.Build().SetDeleteRole(SystemRoles.Admins)); - await DeleteStream(streamId, "adm", "admpa$$"); - } - - - [Test] - public async Task deleting_normal_all_stream_with_no_user_is_allowed() - { - var streamId = await CreateStreamWithMeta(StreamMetadata.Build().SetDeleteRole(SystemRoles.All)); - await DeleteStream(streamId, null, null); - } - - [Test] - public async Task deleting_normal_all_stream_with_existing_user_is_allowed() - { - var streamId = await CreateStreamWithMeta(StreamMetadata.Build().SetDeleteRole(SystemRoles.All)); - await DeleteStream(streamId, "user1", "pa$$1"); - } - - [Test] - public async Task deleting_normal_all_stream_with_admin_user_is_allowed() - { - var streamId = await CreateStreamWithMeta(StreamMetadata.Build().SetDeleteRole(SystemRoles.All)); - await DeleteStream(streamId, "adm", "admpa$$"); - } - - // $-stream - - [Test] - public async Task deleting_system_no_acl_stream_with_no_user_is_not_allowed() - { - var streamId = await CreateStreamWithMeta(streamPrefix: "$", metadata: StreamMetadata.Build()); - await AssertEx.ThrowsAsync(() => DeleteStream(streamId, null, null)); - } - - [Test] - public async Task deleting_system_no_acl_stream_with_existing_user_is_not_allowed() - { - var streamId = await CreateStreamWithMeta(streamPrefix: "$", metadata: StreamMetadata.Build()); - await AssertEx.ThrowsAsync(() => DeleteStream(streamId, "user1", "pa$$1")); - } - - [Test] - public async Task deleting_system_no_acl_stream_with_admin_user_is_allowed() - { - var streamId = await CreateStreamWithMeta(streamPrefix: "$", metadata: StreamMetadata.Build()); - await DeleteStream(streamId, "adm", "admpa$$"); - } - - - [Test] - public async Task deleting_system_user_stream_with_no_user_is_not_allowed() - { - var streamId = await CreateStreamWithMeta(streamPrefix: "$", - metadata: StreamMetadata.Build().SetDeleteRole("user1")); - await AssertEx.ThrowsAsync(() => DeleteStream(streamId, null, null)); - } - - [Test] - public async Task deleting_system_user_stream_with_not_authorized_user_is_not_allowed() - { - var streamId = await CreateStreamWithMeta(streamPrefix: "$", - metadata: StreamMetadata.Build().SetDeleteRole("user1")); - await AssertEx.ThrowsAsync(() => DeleteStream(streamId, "user2", "pa$$2")); - } - - [Test] - public async Task deleting_system_user_stream_with_authorized_user_is_allowed() - { - var streamId = await CreateStreamWithMeta(streamPrefix: "$", - metadata: StreamMetadata.Build().SetDeleteRole("user1")); - await DeleteStream(streamId, "user1", "pa$$1"); - } - - [Test] - public async Task deleting_system_user_stream_with_admin_user_is_allowed() - { - var streamId = await CreateStreamWithMeta(streamPrefix: "$", - metadata: StreamMetadata.Build().SetDeleteRole("user1")); - await DeleteStream(streamId, "adm", "admpa$$"); - } - - - [Test] - public async Task deleting_system_admin_stream_with_no_user_is_not_allowed() - { - var streamId = await CreateStreamWithMeta(streamPrefix: "$", - metadata: StreamMetadata.Build().SetDeleteRole(SystemRoles.Admins)); - await AssertEx.ThrowsAsync(() => DeleteStream(streamId, null, null)); - } - - [Test] - public async Task deleting_system_admin_stream_with_existing_user_is_not_allowed() - { - var streamId = await CreateStreamWithMeta(streamPrefix: "$", - metadata: StreamMetadata.Build().SetDeleteRole(SystemRoles.Admins)); - await AssertEx.ThrowsAsync(() => DeleteStream(streamId, "user1", "pa$$1")); - } - - [Test] - public async Task deleting_system_admin_stream_with_admin_user_is_allowed() - { - var streamId = await CreateStreamWithMeta(streamPrefix: "$", - metadata: StreamMetadata.Build().SetDeleteRole(SystemRoles.Admins)); - await DeleteStream(streamId, "adm", "admpa$$"); - } - - - [Test] - public async Task deleting_system_all_stream_with_no_user_is_allowed() - { - var streamId = await CreateStreamWithMeta(streamPrefix: "$", - metadata: StreamMetadata.Build().SetDeleteRole(SystemRoles.All)); - await DeleteStream(streamId, null, null); - } - - [Test] - public async Task deleting_system_all_stream_with_existing_user_is_allowed() - { - var streamId = await CreateStreamWithMeta(streamPrefix: "$", - metadata: StreamMetadata.Build().SetDeleteRole(SystemRoles.All)); - await DeleteStream(streamId, "user1", "pa$$1"); - } - - [Test] - public async Task deleting_system_all_stream_with_admin_user_is_allowed() - { - var streamId = await CreateStreamWithMeta(streamPrefix: "$", - metadata: StreamMetadata.Build().SetDeleteRole(SystemRoles.All)); - await DeleteStream(streamId, "adm", "admpa$$"); - } -} diff --git a/src/EventStore.Core.Tests/ClientAPI/Security/multiple_role_security.cs b/src/EventStore.Core.Tests/ClientAPI/Security/multiple_role_security.cs deleted file mode 100644 index 7cb6b9fe42..0000000000 --- a/src/EventStore.Core.Tests/ClientAPI/Security/multiple_role_security.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Threading.Tasks; -using EventStore.ClientAPI; -using EventStore.ClientAPI.Exceptions; -using EventStore.ClientAPI.SystemData; -using EventStore.Core.Services; -using NUnit.Framework; - -namespace EventStore.Core.Tests.ClientAPI.Security; - -[Category("ClientAPI"), Category("LongRunning"), Category("Network")] -[TestFixture(typeof(LogFormat.V2), typeof(string))] -public class multiple_role_security : AuthenticationTestBase -{ - [OneTimeSetUp] - public override async Task TestFixtureSetUp() - { - await base.TestFixtureSetUp(); - - var settings = new SystemSettings( - new StreamAcl(new[] { "user1", "user2" }, new[] { "$admins", "user1" }, new[] { "user1", SystemRoles.All }, - null, null), - null); - await Connection.SetSystemSettingsAsync(settings, new UserCredentials("adm", "admpa$$")); - } - - [Test] - public async Task multiple_roles_are_handled_correctly() - { - await AssertEx.ThrowsAsync(() => ReadEvent("usr-stream", null, null)); - await ReadEvent("usr-stream", "user1", "pa$$1"); - await ReadEvent("usr-stream", "user2", "pa$$2"); - await ReadEvent("usr-stream", "adm", "admpa$$"); - - await AssertEx.ThrowsAsync(() => WriteStream("usr-stream", null, null)); - await WriteStream("usr-stream", "user1", "pa$$1"); - await AssertEx.ThrowsAsync(() => WriteStream("usr-stream", "user2", "pa$$2")); - await WriteStream("usr-stream", "adm", "admpa$$"); - - await DeleteStream("usr-stream1", null, null); - await DeleteStream("usr-stream2", "user1", "pa$$1"); - await DeleteStream("usr-stream3", "user2", "pa$$2"); - await DeleteStream("usr-stream4", "adm", "admpa$$"); - } -} diff --git a/src/EventStore.Core.Tests/ClientAPI/Security/overriden_system_stream_security.cs b/src/EventStore.Core.Tests/ClientAPI/Security/overriden_system_stream_security.cs deleted file mode 100644 index 8de06e3df1..0000000000 --- a/src/EventStore.Core.Tests/ClientAPI/Security/overriden_system_stream_security.cs +++ /dev/null @@ -1,138 +0,0 @@ -using System.Threading.Tasks; -using EventStore.ClientAPI; -using EventStore.ClientAPI.Exceptions; -using EventStore.ClientAPI.SystemData; -using NUnit.Framework; - -namespace EventStore.Core.Tests.ClientAPI.Security; - -[Category("ClientAPI"), Category("LongRunning"), Category("Network")] -[TestFixture(typeof(LogFormat.V2), typeof(string))] -public class overriden_system_stream_security : AuthenticationTestBase -{ - [OneTimeSetUp] - public override async Task TestFixtureSetUp() - { - await base.TestFixtureSetUp(); - - var settings = new SystemSettings(userStreamAcl: null, - systemStreamAcl: new StreamAcl("user1", "user1", "user1", "user1", "user1")); - await Connection.SetSystemSettingsAsync(settings, new UserCredentials("adm", "admpa$$")); - } - - [Test] - public async Task operations_on_system_stream_succeed_for_authorized_user() - { - const string stream = "$sys-authorized-user"; - await ReadEvent(stream, "user1", "pa$$1"); - await ReadStreamForward(stream, "user1", "pa$$1"); - await ReadStreamBackward(stream, "user1", "pa$$1"); - - await WriteStream(stream, "user1", "pa$$1"); - - if (LogFormatHelper.SupportsExplicitTransactions) - { - await TransStart(stream, "user1", "pa$$1"); - } - - if (LogFormatHelper.SupportsExplicitTransactions) - { - var transId = (await TransStart(stream, "adm", "admpa$$")).TransactionId; - var trans = Connection.ContinueTransaction(transId, new UserCredentials("user1", "pa$$1")); - await trans.WriteAsync(); - await trans.CommitAsync(); - } - - await ReadMeta(stream, "user1", "pa$$1"); - await WriteMeta(stream, "user1", "pa$$1", null); - - await SubscribeToStream(stream, "user1", "pa$$1"); - - await DeleteStream(stream, "user1", "pa$$1"); - } - - [Test] - public async Task operations_on_system_stream_fail_for_not_authorized_user() - { - const string stream = "$sys-not-authorized-user"; - await AssertEx.ThrowsAsync(() => ReadEvent(stream, "user2", "pa$$2")); - await AssertEx.ThrowsAsync(() => ReadStreamForward(stream, "user2", "pa$$2")); - await AssertEx.ThrowsAsync(() => ReadStreamBackward(stream, "user2", "pa$$2")); - - await AssertEx.ThrowsAsync(() => WriteStream(stream, "user2", "pa$$2")); - await AssertEx.ThrowsAsync(() => TransStart(stream, "user2", "pa$$2")); - - if (LogFormatHelper.SupportsExplicitTransactions) - { - var transId = (await TransStart(stream, "adm", "admpa$$")).TransactionId; - var trans = Connection.ContinueTransaction(transId, new UserCredentials("user2", "pa$$2")); - await AssertEx.ThrowsAsync(() => trans.WriteAsync()); - await AssertEx.ThrowsAsync(() => trans.CommitAsync()); - } - - await AssertEx.ThrowsAsync(() => ReadMeta(stream, "user2", "pa$$2")); - await AssertEx.ThrowsAsync(() => WriteMeta(stream, "user2", "pa$$2", null)); - - await AssertEx.ThrowsAsync(() => SubscribeToStream(stream, "user2", "pa$$2")); - - await AssertEx.ThrowsAsync(() => DeleteStream(stream, "user2", "pa$$2")); - } - - [Test] - public async Task operations_on_system_stream_fail_for_anonymous_user() - { - const string stream = "$sys-anonymous-user"; - await AssertEx.ThrowsAsync(() => ReadEvent(stream, null, null)); - await AssertEx.ThrowsAsync(() => ReadStreamForward(stream, null, null)); - await AssertEx.ThrowsAsync(() => ReadStreamBackward(stream, null, null)); - - await AssertEx.ThrowsAsync(() => WriteStream(stream, null, null)); - await AssertEx.ThrowsAsync(() => TransStart(stream, null, null)); - - if (LogFormatHelper.SupportsExplicitTransactions) - { - var transId = (await TransStart(stream, "adm", "admpa$$")).TransactionId; - var trans = Connection.ContinueTransaction(transId); - await AssertEx.ThrowsAsync(() => trans.WriteAsync()); - await AssertEx.ThrowsAsync(() => trans.CommitAsync()); - } - - await AssertEx.ThrowsAsync(() => ReadMeta(stream, null, null)); - await AssertEx.ThrowsAsync(() => WriteMeta(stream, null, null, null)); - - await AssertEx.ThrowsAsync(() => SubscribeToStream(stream, null, null)); - - await AssertEx.ThrowsAsync(() => DeleteStream(stream, null, null)); - } - - [Test] - public async Task operations_on_system_stream_succeed_for_admin() - { - const string stream = "$sys-admin"; - await ReadEvent(stream, "adm", "admpa$$"); - await ReadStreamForward(stream, "adm", "admpa$$"); - await ReadStreamBackward(stream, "adm", "admpa$$"); - - await WriteStream(stream, "adm", "admpa$$"); - - if (LogFormatHelper.SupportsExplicitTransactions) - { - await TransStart(stream, "adm", "admpa$$"); - } - - if (LogFormatHelper.SupportsExplicitTransactions) - { - var transId = (await TransStart(stream, "adm", "admpa$$")).TransactionId; - var trans = Connection.ContinueTransaction(transId, new UserCredentials("adm", "admpa$$")); - await trans.WriteAsync(); - await trans.CommitAsync(); - } - - await ReadMeta(stream, "adm", "admpa$$"); - await WriteMeta(stream, "adm", "admpa$$", null); - - await SubscribeToStream(stream, "adm", "admpa$$"); - - await DeleteStream(stream, "adm", "admpa$$"); - } -} diff --git a/src/EventStore.Core.Tests/ClientAPI/Security/overriden_system_stream_security_for_all.cs b/src/EventStore.Core.Tests/ClientAPI/Security/overriden_system_stream_security_for_all.cs deleted file mode 100644 index d3e17bf9b3..0000000000 --- a/src/EventStore.Core.Tests/ClientAPI/Security/overriden_system_stream_security_for_all.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System.Threading.Tasks; -using EventStore.ClientAPI; -using EventStore.ClientAPI.SystemData; -using EventStore.Core.Services; -using NUnit.Framework; - -namespace EventStore.Core.Tests.ClientAPI.Security; - -[Category("ClientAPI"), Category("LongRunning"), Category("Network")] -[TestFixture(typeof(LogFormat.V2), typeof(string))] -public class overriden_system_stream_security_for_all : AuthenticationTestBase -{ - [OneTimeSetUp] - public override async Task TestFixtureSetUp() - { - await base.TestFixtureSetUp(); - - var settings = new SystemSettings( - userStreamAcl: null, - systemStreamAcl: new StreamAcl(SystemRoles.All, SystemRoles.All, SystemRoles.All, SystemRoles.All, - SystemRoles.All)); - await Connection.SetSystemSettingsAsync(settings, new UserCredentials("adm", "admpa$$")); - } - - [Test] - public async Task operations_on_system_stream_succeeds_for_user() - { - const string stream = "$sys-authorized-user"; - await ReadEvent(stream, "user1", "pa$$1"); - await ReadStreamForward(stream, "user1", "pa$$1"); - await ReadStreamBackward(stream, "user1", "pa$$1"); - - await WriteStream(stream, "user1", "pa$$1"); - - if (LogFormatHelper.SupportsExplicitTransactions) - { - await TransStart(stream, "user1", "pa$$1"); - } - - if (LogFormatHelper.SupportsExplicitTransactions) - { - var transId = (await TransStart(stream, "adm", "admpa$$")).TransactionId; - var trans = Connection.ContinueTransaction(transId, new UserCredentials("user1", "pa$$1")); - await trans.WriteAsync(); - await trans.CommitAsync(); - } - - await ReadMeta(stream, "user1", "pa$$1"); - await WriteMeta(stream, "user1", "pa$$1", null); - - await SubscribeToStream(stream, "user1", "pa$$1"); - - await DeleteStream(stream, "user1", "pa$$1"); - } - - [Test] - public async Task operations_on_system_stream_fail_for_anonymous_user() - { - const string stream = "$sys-anonymous-user"; - await ReadEvent(stream, null, null); - await ReadStreamForward(stream, null, null); - await ReadStreamBackward(stream, null, null); - - await WriteStream(stream, null, null); - - if (LogFormatHelper.SupportsExplicitTransactions) - { - await TransStart(stream, null, null); - } - - if (LogFormatHelper.SupportsExplicitTransactions) - { - var transId = (await TransStart(stream, "adm", "admpa$$")).TransactionId; - var trans = Connection.ContinueTransaction(transId); - await trans.WriteAsync(); - await trans.CommitAsync(); - } - - await ReadMeta(stream, null, null); - await WriteMeta(stream, null, null, null); - - await SubscribeToStream(stream, null, null); - - await DeleteStream(stream, null, null); - } - - [Test] - public async Task operations_on_system_stream_succeed_for_admin() - { - const string stream = "$sys-admin"; - await ReadEvent(stream, "adm", "admpa$$"); - await ReadStreamForward(stream, "adm", "admpa$$"); - await ReadStreamBackward(stream, "adm", "admpa$$"); - - await WriteStream(stream, "adm", "admpa$$"); - - if (LogFormatHelper.SupportsExplicitTransactions) - { - await TransStart(stream, "adm", "admpa$$"); - } - - if (LogFormatHelper.SupportsExplicitTransactions) - { - var transId = (await TransStart(stream, "adm", "admpa$$")).TransactionId; - var trans = Connection.ContinueTransaction(transId, new UserCredentials("adm", "admpa$$")); - await trans.WriteAsync(); - await trans.CommitAsync(); - } - - await ReadMeta(stream, "adm", "admpa$$"); - await WriteMeta(stream, "adm", "admpa$$", null); - - await SubscribeToStream(stream, "adm", "admpa$$"); - - await DeleteStream(stream, "adm", "admpa$$"); - } -} diff --git a/src/EventStore.Core.Tests/ClientAPI/Security/overriden_user_stream_security.cs b/src/EventStore.Core.Tests/ClientAPI/Security/overriden_user_stream_security.cs deleted file mode 100644 index 26c9867709..0000000000 --- a/src/EventStore.Core.Tests/ClientAPI/Security/overriden_user_stream_security.cs +++ /dev/null @@ -1,140 +0,0 @@ -using System; -using System.Threading.Tasks; -using EventStore.ClientAPI; -using EventStore.ClientAPI.Exceptions; -using EventStore.ClientAPI.SystemData; -using EventStore.Core.Services; -using NUnit.Framework; - -namespace EventStore.Core.Tests.ClientAPI.Security; - -[Category("ClientAPI"), Category("LongRunning"), Category("Network")] -[TestFixture(typeof(LogFormat.V2), typeof(string))] -public class overriden_user_stream_security : AuthenticationTestBase -{ - [OneTimeSetUp] - public override async Task TestFixtureSetUp() - { - await base.TestFixtureSetUp(); - - var settings = new SystemSettings(userStreamAcl: new StreamAcl("user1", "user1", "user1", "user1", "user1"), - systemStreamAcl: null); - await Connection.SetSystemSettingsAsync(settings, new UserCredentials("adm", "admpa$$")); - } - - [Test] - public async Task operations_on_user_stream_succeeds_for_authorized_user() - { - const string stream = "user-authorized-user"; - await ReadEvent(stream, "user1", "pa$$1"); - await ReadStreamForward(stream, "user1", "pa$$1"); - await ReadStreamBackward(stream, "user1", "pa$$1"); - - await WriteStream(stream, "user1", "pa$$1"); - - if (LogFormatHelper.SupportsExplicitTransactions) - { - await TransStart(stream, "user1", "pa$$1"); - } - - if (LogFormatHelper.SupportsExplicitTransactions) - { - var transId = (await TransStart(stream, "adm", "admpa$$")).TransactionId; - var trans = Connection.ContinueTransaction(transId, new UserCredentials("user1", "pa$$1")); - await trans.WriteAsync(); - await trans.CommitAsync(); - } - - await ReadMeta(stream, "user1", "pa$$1"); - await WriteMeta(stream, "user1", "pa$$1", null); - - await SubscribeToStream(stream, "user1", "pa$$1"); - - await DeleteStream(stream, "user1", "pa$$1"); - } - - [Test] - public async Task operations_on_user_stream_fail_for_not_authorized_user() - { - const string stream = "user-not-authorized"; - await AssertEx.ThrowsAsync(() => ReadEvent(stream, "user2", "pa$$2")); - await AssertEx.ThrowsAsync(() => ReadStreamForward(stream, "user2", "pa$$2")); - await AssertEx.ThrowsAsync(() => ReadStreamBackward(stream, "user2", "pa$$2")); - - await AssertEx.ThrowsAsync(() => WriteStream(stream, "user2", "pa$$2")); - await AssertEx.ThrowsAsync(() => TransStart(stream, "user2", "pa$$2")); - - if (LogFormatHelper.SupportsExplicitTransactions) - { - var transId = (await TransStart(stream, "adm", "admpa$$")).TransactionId; - var trans = Connection.ContinueTransaction(transId, new UserCredentials("user2", "pa$$2")); - await AssertEx.ThrowsAsync(() => trans.WriteAsync()); - await AssertEx.ThrowsAsync(() => trans.CommitAsync()); - } - - await AssertEx.ThrowsAsync(() => ReadMeta(stream, "user2", "pa$$2")); - await AssertEx.ThrowsAsync(() => WriteMeta(stream, "user2", "pa$$2", null)); - - await AssertEx.ThrowsAsync(() => SubscribeToStream(stream, "user2", "pa$$2")); - - await AssertEx.ThrowsAsync(() => DeleteStream(stream, "user2", "pa$$2")); - } - - [Test] - public async Task operations_on_user_stream_fail_for_anonymous_user() - { - const string stream = "user-anonymous-user"; - await AssertEx.ThrowsAsync(() => ReadEvent(stream, null, null)); - await AssertEx.ThrowsAsync(() => ReadStreamForward(stream, null, null)); - await AssertEx.ThrowsAsync(() => ReadStreamBackward(stream, null, null)); - - await AssertEx.ThrowsAsync(() => WriteStream(stream, null, null)); - await AssertEx.ThrowsAsync(() => TransStart(stream, null, null)); - - if (LogFormatHelper.SupportsExplicitTransactions) - { - var transId = (await TransStart(stream, "adm", "admpa$$")).TransactionId; - var trans = Connection.ContinueTransaction(transId); - await AssertEx.ThrowsAsync(() => trans.WriteAsync()); - await AssertEx.ThrowsAsync(() => trans.CommitAsync()); - } - - await AssertEx.ThrowsAsync(() => ReadMeta(stream, null, null)); - await AssertEx.ThrowsAsync(() => WriteMeta(stream, null, null, null)); - - await AssertEx.ThrowsAsync(() => SubscribeToStream(stream, null, null)); - - await AssertEx.ThrowsAsync(() => DeleteStream(stream, null, null)); - } - - [Test] - public async Task operations_on_user_stream_succeed_for_admin() - { - const string stream = "user-admin"; - await ReadEvent(stream, "adm", "admpa$$"); - await ReadStreamForward(stream, "adm", "admpa$$"); - await ReadStreamBackward(stream, "adm", "admpa$$"); - - await WriteStream(stream, "adm", "admpa$$"); - - if (LogFormatHelper.SupportsExplicitTransactions) - { - await TransStart(stream, "adm", "admpa$$"); - } - - if (LogFormatHelper.SupportsExplicitTransactions) - { - var transId = (await TransStart(stream, "adm", "admpa$$")).TransactionId; - var trans = Connection.ContinueTransaction(transId, new UserCredentials("adm", "admpa$$")); - await trans.WriteAsync(); - await trans.CommitAsync(); - } - - await ReadMeta(stream, "adm", "admpa$$"); - await WriteMeta(stream, "adm", "admpa$$", null); - - await SubscribeToStream(stream, "adm", "admpa$$"); - - await DeleteStream(stream, "adm", "admpa$$"); - } -} diff --git a/src/EventStore.Core.Tests/ClientAPI/Security/read_all_security.cs b/src/EventStore.Core.Tests/ClientAPI/Security/read_all_security.cs deleted file mode 100644 index cf18b122fc..0000000000 --- a/src/EventStore.Core.Tests/ClientAPI/Security/read_all_security.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Threading.Tasks; -using EventStore.ClientAPI.Exceptions; -using NUnit.Framework; - -namespace EventStore.Core.Tests.ClientAPI.Security; - -[Category("ClientAPI"), Category("LongRunning"), Category("Network")] -[TestFixture(typeof(LogFormat.V2), typeof(string))] -public class read_all_security : AuthenticationTestBase -{ - [Test] - public async Task reading_all_with_not_existing_credentials_is_not_authenticated() - { - await AssertEx.ThrowsAsync(() => ReadAllForward("badlogin", "badpass")); - await AssertEx.ThrowsAsync(() => ReadAllBackward("badlogin", "badpass")); - } - - [Test] - public async Task reading_all_with_no_credentials_is_denied() - { - await AssertEx.ThrowsAsync(() => ReadAllForward(null, null)); - await AssertEx.ThrowsAsync(() => ReadAllBackward(null, null)); - } - - [Test] - public async Task reading_all_with_not_authorized_user_credentials_is_denied() - { - await AssertEx.ThrowsAsync(() => ReadAllForward("user2", "pa$$2")); - await AssertEx.ThrowsAsync(() => ReadAllBackward("user2", "pa$$2")); - } - - [Test] - public async Task reading_all_with_authorized_user_credentials_succeeds() - { - await ReadAllForward("user1", "pa$$1"); - await ReadAllBackward("user1", "pa$$1"); - } - - [Test] - public async Task reading_all_with_admin_credentials_succeeds() - { - await ReadAllForward("adm", "admpa$$"); - await ReadAllBackward("adm", "admpa$$"); - } -} diff --git a/src/EventStore.Core.Tests/ClientAPI/Security/read_stream_meta_security.cs b/src/EventStore.Core.Tests/ClientAPI/Security/read_stream_meta_security.cs deleted file mode 100644 index 6f6ebdd066..0000000000 --- a/src/EventStore.Core.Tests/ClientAPI/Security/read_stream_meta_security.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System.Threading.Tasks; -using EventStore.ClientAPI.Exceptions; -using NUnit.Framework; - -namespace EventStore.Core.Tests.ClientAPI.Security; - -[Category("ClientAPI"), Category("LongRunning"), Category("Network")] -[TestFixture(typeof(LogFormat.V2), typeof(string))] -public class read_stream_meta_security : AuthenticationTestBase -{ - [Test] - public async Task reading_stream_meta_with_not_existing_credentials_is_not_authenticated() - { - await AssertEx.ThrowsAsync(() => ReadMeta("metaread-stream", "badlogin", "badpass")); - } - - [Test] - public async Task reading_stream_meta_with_no_credentials_is_denied() - { - await AssertEx.ThrowsAsync(() => ReadMeta("metaread-stream", null, null)); - } - - [Test] - public async Task reading_stream_meta_with_not_authorized_user_credentials_is_denied() - { - await AssertEx.ThrowsAsync(() => ReadMeta("metaread-stream", "user2", "pa$$2")); - } - - [Test] - public async Task reading_stream_meta_with_authorized_user_credentials_succeeds() - { - await ReadMeta("metaread-stream", "user1", "pa$$1"); - } - - [Test] - public async Task reading_stream_meta_with_admin_user_credentials_succeeds() - { - await ReadMeta("metaread-stream", "adm", "admpa$$"); - } - - - [Test] - public async Task reading_no_acl_stream_meta_succeeds_when_no_credentials_are_passed() - { - await ReadMeta("noacl-stream", null, null); - } - - [Test] - public async Task reading_no_acl_stream_meta_is_not_authenticated_when_not_existing_credentials_are_passed() - { - await AssertEx.ThrowsAsync(() => ReadMeta("noacl-stream", "badlogin", "badpass")); - } - - [Test] - public async Task reading_no_acl_stream_meta_succeeds_when_any_existing_user_credentials_are_passed() - { - await ReadMeta("noacl-stream", "user1", "pa$$1"); - await ReadMeta("noacl-stream", "user2", "pa$$2"); - } - - [Test] - public async Task reading_no_acl_stream_meta_succeeds_when_admin_user_credentials_are_passed() - { - await ReadMeta("noacl-stream", "adm", "admpa$$"); - } - - - [Test] - public async Task reading_all_access_normal_stream_meta_succeeds_when_no_credentials_are_passed() - { - await ReadMeta("normal-all", null, null); - } - - [Test] - public async Task - reading_all_access_normal_stream_meta_is_not_authenticated_when_not_existing_credentials_are_passed() - { - await AssertEx.ThrowsAsync(() => ReadMeta("normal-all", "badlogin", "badpass")); - } - - [Test] - public async Task reading_all_access_normal_stream_meta_succeeds_when_any_existing_user_credentials_are_passed() - { - await ReadMeta("normal-all", "user1", "pa$$1"); - await ReadMeta("normal-all", "user2", "pa$$2"); - } - - [Test] - public async Task reading_all_access_normal_stream_meta_succeeds_when_admin_user_credentials_are_passed() - { - await ReadMeta("normal-all", "adm", "admpa$$"); - } -} diff --git a/src/EventStore.Core.Tests/ClientAPI/Security/read_stream_security.cs b/src/EventStore.Core.Tests/ClientAPI/Security/read_stream_security.cs deleted file mode 100644 index 277c586fbb..0000000000 --- a/src/EventStore.Core.Tests/ClientAPI/Security/read_stream_security.cs +++ /dev/null @@ -1,122 +0,0 @@ -using System.Threading.Tasks; -using EventStore.ClientAPI.Exceptions; -using NUnit.Framework; - -namespace EventStore.Core.Tests.ClientAPI.Security; - -[Category("ClientAPI"), Category("LongRunning"), Category("Network")] -[TestFixture(typeof(LogFormat.V2), typeof(string))] -public class read_stream_security : AuthenticationTestBase -{ - [Test] - public async Task reading_stream_with_not_existing_credentials_is_not_authenticated() - { - await AssertEx.ThrowsAsync(() => ReadEvent("read-stream", "badlogin", "badpass")); - await AssertEx.ThrowsAsync(() => ReadStreamForward("read-stream", "badlogin", "badpass")); - await AssertEx.ThrowsAsync(() => ReadStreamBackward("read-stream", "badlogin", "badpass")); - } - - [Test] - public async Task reading_stream_with_no_credentials_is_denied() - { - await AssertEx.ThrowsAsync(() => ReadEvent("read-stream", null, null)); - await AssertEx.ThrowsAsync(() => ReadStreamForward("read-stream", null, null)); - await AssertEx.ThrowsAsync(() => ReadStreamBackward("read-stream", null, null)); - } - - [Test] - public async Task reading_stream_with_not_authorized_user_credentials_is_denied() - { - await AssertEx.ThrowsAsync(() => ReadEvent("read-stream", "user2", "pa$$2")); - await AssertEx.ThrowsAsync(() => ReadStreamForward("read-stream", "user2", "pa$$2")); - await AssertEx.ThrowsAsync(() => ReadStreamBackward("read-stream", "user2", "pa$$2")); - } - - [Test] - public async Task reading_stream_with_authorized_user_credentials_succeeds() - { - await ReadEvent("read-stream", "user1", "pa$$1"); - await ReadStreamForward("read-stream", "user1", "pa$$1"); - await ReadStreamBackward("read-stream", "user1", "pa$$1"); - } - - [Test] - public async Task reading_stream_with_admin_user_credentials_succeeds() - { - await ReadEvent("read-stream", "adm", "admpa$$"); - await ReadStreamForward("read-stream", "adm", "admpa$$"); - await ReadStreamBackward("read-stream", "adm", "admpa$$"); - } - - - [Test] - public async Task reading_no_acl_stream_succeeds_when_no_credentials_are_passed() - { - await ReadEvent("noacl-stream", null, null); - await ReadStreamForward("noacl-stream", null, null); - await ReadStreamBackward("noacl-stream", null, null); - } - - [Test] - public async Task reading_no_acl_stream_is_not_authenticated_when_not_existing_credentials_are_passed() - { - await AssertEx.ThrowsAsync(() => ReadEvent("noacl-stream", "badlogin", "badpass")); - await AssertEx.ThrowsAsync(() => ReadStreamForward("noacl-stream", "badlogin", "badpass")); - await AssertEx.ThrowsAsync(() => ReadStreamBackward("noacl-stream", "badlogin", "badpass")); - } - - [Test] - public async Task reading_no_acl_stream_succeeds_when_any_existing_user_credentials_are_passed() - { - await ReadEvent("noacl-stream", "user1", "pa$$1"); - await ReadStreamForward("noacl-stream", "user1", "pa$$1"); - await ReadStreamBackward("noacl-stream", "user1", "pa$$1"); - await ReadEvent("noacl-stream", "user2", "pa$$2"); - await ReadStreamForward("noacl-stream", "user2", "pa$$2"); - await ReadStreamBackward("noacl-stream", "user2", "pa$$2"); - } - - [Test] - public async Task reading_no_acl_stream_succeeds_when_admin_user_credentials_are_passed() - { - await ReadEvent("noacl-stream", "adm", "admpa$$"); - await ReadStreamForward("noacl-stream", "adm", "admpa$$"); - await ReadStreamBackward("noacl-stream", "adm", "admpa$$"); - } - - - [Test] - public async Task reading_all_access_normal_stream_succeeds_when_no_credentials_are_passed() - { - await ReadEvent("normal-all", null, null); - await ReadStreamForward("normal-all", null, null); - await ReadStreamBackward("normal-all", null, null); - } - - [Test] - public async Task reading_all_access_normal_stream_is_not_authenticated_when_not_existing_credentials_are_passed() - { - await AssertEx.ThrowsAsync(() => ReadEvent("normal-all", "badlogin", "badpass")); - await AssertEx.ThrowsAsync(() => ReadStreamForward("normal-all", "badlogin", "badpass")); - await AssertEx.ThrowsAsync(() => ReadStreamBackward("normal-all", "badlogin", "badpass")); - } - - [Test] - public async Task reading_all_access_normal_stream_succeeds_when_any_existing_user_credentials_are_passed() - { - await ReadEvent("normal-all", "user1", "pa$$1"); - await ReadStreamForward("normal-all", "user1", "pa$$1"); - await ReadStreamBackward("normal-all", "user1", "pa$$1"); - await ReadEvent("normal-all", "user2", "pa$$2"); - await ReadStreamForward("normal-all", "user2", "pa$$2"); - await ReadStreamBackward("normal-all", "user2", "pa$$2"); - } - - [Test] - public async Task reading_all_access_normal_stream_succeeds_when_admin_user_credentials_are_passed() - { - await ReadEvent("normal-all", "adm", "admpa$$"); - await ReadStreamForward("normal-all", "adm", "admpa$$"); - await ReadStreamBackward("normal-all", "adm", "admpa$$"); - } -} diff --git a/src/EventStore.Core.Tests/ClientAPI/Security/stream_security_inheritance.cs b/src/EventStore.Core.Tests/ClientAPI/Security/stream_security_inheritance.cs deleted file mode 100644 index d1a395ef9f..0000000000 --- a/src/EventStore.Core.Tests/ClientAPI/Security/stream_security_inheritance.cs +++ /dev/null @@ -1,126 +0,0 @@ -using System.Threading.Tasks; -using EventStore.ClientAPI; -using EventStore.ClientAPI.Exceptions; -using EventStore.ClientAPI.SystemData; -using EventStore.Core.Services; -using NUnit.Framework; - -namespace EventStore.Core.Tests.ClientAPI.Security; - -[Category("ClientAPI"), Category("LongRunning"), Category("Network")] -[TestFixture(typeof(LogFormat.V2), typeof(string))] -public class stream_security_inheritance : AuthenticationTestBase -{ - [OneTimeSetUp] - public override async Task TestFixtureSetUp() - { - await base.TestFixtureSetUp(); - - var settings = new SystemSettings(userStreamAcl: new StreamAcl(null, "user1", null, null, null), - systemStreamAcl: new StreamAcl(null, "user1", null, null, null)); - await Connection.SetSystemSettingsAsync(settings, new UserCredentials("adm", "admpa$$")); - - await Connection.SetStreamMetadataAsync("user-no-acl", ExpectedVersion.NoStream, - StreamMetadata.Build(), new UserCredentials("adm", "admpa$$")); - await Connection.SetStreamMetadataAsync("user-w-diff", ExpectedVersion.NoStream, - StreamMetadata.Build().SetWriteRole("user2"), new UserCredentials("adm", "admpa$$")); - await Connection.SetStreamMetadataAsync("user-w-multiple", ExpectedVersion.NoStream, - StreamMetadata.Build().SetWriteRoles(new[] { "user1", "user2" }), - new UserCredentials("adm", "admpa$$")) - ; - await Connection.SetStreamMetadataAsync("user-w-restricted", ExpectedVersion.NoStream, - StreamMetadata.Build().SetWriteRoles(new string[0]), new UserCredentials("adm", "admpa$$")); - await Connection.SetStreamMetadataAsync("user-w-all", ExpectedVersion.NoStream, - StreamMetadata.Build().SetWriteRole(SystemRoles.All), new UserCredentials("adm", "admpa$$")); - - await Connection.SetStreamMetadataAsync("user-r-restricted", ExpectedVersion.NoStream, - StreamMetadata.Build().SetReadRole("user1"), new UserCredentials("adm", "admpa$$")); - - await Connection.SetStreamMetadataAsync("$sys-no-acl", ExpectedVersion.NoStream, - StreamMetadata.Build(), new UserCredentials("adm", "admpa$$")); - await Connection.SetStreamMetadataAsync("$sys-w-diff", ExpectedVersion.NoStream, - StreamMetadata.Build().SetWriteRole("user2"), new UserCredentials("adm", "admpa$$")); - await Connection.SetStreamMetadataAsync("$sys-w-multiple", ExpectedVersion.NoStream, - StreamMetadata.Build().SetWriteRoles(new[] { "user1", "user2" }), - new UserCredentials("adm", "admpa$$")) - ; - await Connection.SetStreamMetadataAsync("$sys-w-restricted", ExpectedVersion.NoStream, - StreamMetadata.Build().SetWriteRoles(new string[0]), new UserCredentials("adm", "admpa$$")); - await Connection.SetStreamMetadataAsync("$sys-w-all", ExpectedVersion.NoStream, - StreamMetadata.Build().SetWriteRole(SystemRoles.All), new UserCredentials("adm", "admpa$$")); - } - - [Test] - public async Task acl_inheritance_is_working_properly_on_user_streams() - { - await AssertEx.ThrowsAsync(() => WriteStream("user-no-acl", null, null)); - await WriteStream("user-no-acl", "user1", "pa$$1"); - await AssertEx.ThrowsAsync(() => WriteStream("user-no-acl", "user2", "pa$$2")); - await WriteStream("user-no-acl", "adm", "admpa$$"); - - await AssertEx.ThrowsAsync(() => WriteStream("user-w-diff", null, null)); - await AssertEx.ThrowsAsync(() => WriteStream("user-w-diff", "user1", "pa$$1")); - await WriteStream("user-w-diff", "user2", "pa$$2"); - await WriteStream("user-w-diff", "adm", "admpa$$"); - - await AssertEx.ThrowsAsync(() => WriteStream("user-w-multiple", null, null)); - await WriteStream("user-w-multiple", "user1", "pa$$1"); - await WriteStream("user-w-multiple", "user2", "pa$$2"); - await WriteStream("user-w-multiple", "adm", "admpa$$"); - - await AssertEx.ThrowsAsync(() => WriteStream("user-w-restricted", null, null)); - await AssertEx.ThrowsAsync(() => WriteStream("user-w-restricted", "user1", "pa$$1")); - await AssertEx.ThrowsAsync(() => WriteStream("user-w-restricted", "user2", "pa$$2")); - await WriteStream("user-w-restricted", "adm", "admpa$$"); - - await WriteStream("user-w-all", null, null); - await WriteStream("user-w-all", "user1", "pa$$1"); - await WriteStream("user-w-all", "user2", "pa$$2"); - await WriteStream("user-w-all", "adm", "admpa$$"); - - - await ReadEvent("user-no-acl", null, null); - await ReadEvent("user-no-acl", "user1", "pa$$1"); - await ReadEvent("user-no-acl", "user2", "pa$$2"); - await ReadEvent("user-no-acl", "adm", "admpa$$"); - - await AssertEx.ThrowsAsync(() => ReadEvent("user-r-restricted", null, null)); - await ReadEvent("user-r-restricted", "user1", "pa$$1"); - await AssertEx.ThrowsAsync(() => ReadEvent("user-r-restricted", "user2", "pa$$2")); - await ReadEvent("user-r-restricted", "adm", "admpa$$"); - } - - [Test] - public async Task acl_inheritance_is_working_properly_on_system_streams() - { - await AssertEx.ThrowsAsync(() => WriteStream("$sys-no-acl", null, null)); - await WriteStream("$sys-no-acl", "user1", "pa$$1"); - await AssertEx.ThrowsAsync(() => WriteStream("$sys-no-acl", "user2", "pa$$2")); - await WriteStream("$sys-no-acl", "adm", "admpa$$"); - - await AssertEx.ThrowsAsync(() => WriteStream("$sys-w-diff", null, null)); - await AssertEx.ThrowsAsync(() => WriteStream("$sys-w-diff", "user1", "pa$$1")); - await WriteStream("$sys-w-diff", "user2", "pa$$2"); - await WriteStream("$sys-w-diff", "adm", "admpa$$"); - - await AssertEx.ThrowsAsync(() => WriteStream("$sys-w-multiple", null, null)); - await WriteStream("$sys-w-multiple", "user1", "pa$$1"); - await WriteStream("$sys-w-multiple", "user2", "pa$$2"); - await WriteStream("$sys-w-multiple", "adm", "admpa$$"); - - await AssertEx.ThrowsAsync(() => WriteStream("$sys-w-restricted", null, null)); - await AssertEx.ThrowsAsync(() => WriteStream("$sys-w-restricted", "user1", "pa$$1")); - await AssertEx.ThrowsAsync(() => WriteStream("$sys-w-restricted", "user2", "pa$$2")); - await WriteStream("$sys-w-restricted", "adm", "admpa$$"); - - await WriteStream("$sys-w-all", null, null); - await WriteStream("$sys-w-all", "user1", "pa$$1"); - await WriteStream("$sys-w-all", "user2", "pa$$2"); - await WriteStream("$sys-w-all", "adm", "admpa$$"); - - await AssertEx.ThrowsAsync(() => ReadEvent("$sys-no-acl", null, null)); - await AssertEx.ThrowsAsync(() => ReadEvent("$sys-no-acl", "user1", "pa$$1")); - await AssertEx.ThrowsAsync(() => ReadEvent("$sys-no-acl", "user2", "pa$$2")); - await ReadEvent("$sys-no-acl", "adm", "admpa$$"); - } -} diff --git a/src/EventStore.Core.Tests/ClientAPI/Security/subscribe_to_all_security.cs b/src/EventStore.Core.Tests/ClientAPI/Security/subscribe_to_all_security.cs deleted file mode 100644 index 00e4fd49f5..0000000000 --- a/src/EventStore.Core.Tests/ClientAPI/Security/subscribe_to_all_security.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Threading.Tasks; -using EventStore.ClientAPI.Exceptions; -using NUnit.Framework; - -namespace EventStore.Core.Tests.ClientAPI.Security; - -[Category("ClientAPI"), Category("LongRunning"), Category("Network")] -[TestFixture(typeof(LogFormat.V2), typeof(string))] -public class subscribe_to_all_security : AuthenticationTestBase -{ - [Test] - public async Task subscribing_to_all_with_not_existing_credentials_is_not_authenticated() - { - await AssertEx.ThrowsAsync(() => SubscribeToAll("badlogin", "badpass")); - } - - [Test] - public async Task subscribing_to_all_with_no_credentials_is_denied() - { - await AssertEx.ThrowsAsync(() => SubscribeToAll(null, null)); - } - - [Test] - public async Task subscribing_to_all_with_not_authorized_user_credentials_is_denied() - { - await AssertEx.ThrowsAsync(() => SubscribeToAll("user2", "pa$$2")); - } - - [Test] - public async Task subscribing_to_all_with_authorized_user_credentials_succeeds() - { - await SubscribeToAll("user1", "pa$$1"); - } - - [Test] - public async Task subscribing_to_all_with_admin_user_credentials_succeeds() - { - await SubscribeToAll("adm", "admpa$$"); - } -} diff --git a/src/EventStore.Core.Tests/ClientAPI/Security/subscribe_to_stream_security.cs b/src/EventStore.Core.Tests/ClientAPI/Security/subscribe_to_stream_security.cs deleted file mode 100644 index bc113bea71..0000000000 --- a/src/EventStore.Core.Tests/ClientAPI/Security/subscribe_to_stream_security.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System.Threading.Tasks; -using EventStore.ClientAPI.Exceptions; -using NUnit.Framework; - -namespace EventStore.Core.Tests.ClientAPI.Security; - -[Category("ClientAPI"), Category("LongRunning"), Category("Network")] -[TestFixture(typeof(LogFormat.V2), typeof(string))] -public class subscribe_to_stream_security : AuthenticationTestBase -{ - [Test] - public async Task subscribing_to_stream_with_not_existing_credentials_is_not_authenticated() - { - await AssertEx.ThrowsAsync(() => SubscribeToStream("read-stream", "badlogin", "badpass")); - } - - [Test] - public async Task subscribing_to_stream_with_no_credentials_is_denied() - { - await AssertEx.ThrowsAsync(() => SubscribeToStream("read-stream", null, null)); - } - - [Test] - public async Task subscribing_to_stream_with_not_authorized_user_credentials_is_denied() - { - await AssertEx.ThrowsAsync(() => SubscribeToStream("read-stream", "user2", "pa$$2")); - } - - [Test] - public async Task reading_stream_with_authorized_user_credentials_succeeds() - { - await SubscribeToStream("read-stream", "user1", "pa$$1"); - } - - [Test] - public async Task reading_stream_with_admin_user_credentials_succeeds() - { - await SubscribeToStream("read-stream", "adm", "admpa$$"); - } - - - [Test] - public async Task subscribing_to_no_acl_stream_succeeds_when_no_credentials_are_passed() - { - await SubscribeToStream("noacl-stream", null, null); - } - - [Test] - public async Task subscribing_to_no_acl_stream_is_not_authenticated_when_not_existing_credentials_are_passed() - { - await AssertEx.ThrowsAsync(() => SubscribeToStream("noacl-stream", "badlogin", "badpass")); - } - - [Test] - public async Task subscribing_to_no_acl_stream_succeeds_when_any_existing_user_credentials_are_passed() - { - await SubscribeToStream("noacl-stream", "user1", "pa$$1"); - await SubscribeToStream("noacl-stream", "user2", "pa$$2"); - } - - [Test] - public async Task subscribing_to_no_acl_stream_succeeds_when_admin_user_credentials_are_passed() - { - await SubscribeToStream("noacl-stream", "adm", "admpa$$"); - } - - - [Test] - public async Task subscribing_to_all_access_normal_stream_succeeds_when_no_credentials_are_passed() - { - await SubscribeToStream("normal-all", null, null); - } - - [Test] - public async Task - subscribing_to_all_access_normal_stream_is_not_authenticated_when_not_existing_credentials_are_passed() - { - await AssertEx.ThrowsAsync(() => SubscribeToStream("normal-all", "badlogin", "badpass")); - } - - [Test] - public async Task subscribing_to_all_access_normal_stream_succeeds_when_any_existing_user_credentials_are_passed() - { - await SubscribeToStream("normal-all", "user1", "pa$$1"); - await SubscribeToStream("normal-all", "user2", "pa$$2"); - } - - [Test] - public async Task subscribing_to_all_access_normal_streamm_succeeds_when_admin_user_credentials_are_passed() - { - await SubscribeToStream("normal-all", "adm", "admpa$$"); - } -} diff --git a/src/EventStore.Core.Tests/ClientAPI/Security/system_stream_security.cs b/src/EventStore.Core.Tests/ClientAPI/Security/system_stream_security.cs deleted file mode 100644 index b80f76090c..0000000000 --- a/src/EventStore.Core.Tests/ClientAPI/Security/system_stream_security.cs +++ /dev/null @@ -1,282 +0,0 @@ -using System.Threading.Tasks; -using EventStore.ClientAPI.Exceptions; -using EventStore.ClientAPI.SystemData; -using EventStore.Core.Services; -using NUnit.Framework; - -namespace EventStore.Core.Tests.ClientAPI.Security; - -[Category("ClientAPI"), Category("LongRunning"), Category("Network")] -[TestFixture(typeof(LogFormat.V2), typeof(string))] -public class system_stream_security : AuthenticationTestBase -{ - [Test] - public async Task operations_on_system_stream_with_no_acl_set_fail_for_non_admin() - { - await AssertEx.ThrowsAsync(() => ReadEvent("$system-no-acl", "user1", "pa$$1")); - await AssertEx.ThrowsAsync(() => ReadStreamForward("$system-no-acl", "user1", "pa$$1")); - await AssertEx.ThrowsAsync(() => ReadStreamBackward("$system-no-acl", "user1", "pa$$1")); - - await AssertEx.ThrowsAsync(() => WriteStream("$system-no-acl", "user1", "pa$$1")); - await AssertEx.ThrowsAsync(() => TransStart("$system-no-acl", "user1", "pa$$1")); - - if (LogFormatHelper.SupportsExplicitTransactions) - { - var transId = (await TransStart("$system-no-acl", "adm", "admpa$$")).TransactionId; - var trans = Connection.ContinueTransaction(transId, new UserCredentials("user1", "pa$$1")); - await AssertEx.ThrowsAsync(() => trans.WriteAsync()); - await AssertEx.ThrowsAsync(() => trans.CommitAsync()); - } - - await AssertEx.ThrowsAsync(() => ReadMeta("$system-no-acl", "user1", "pa$$1")); - await AssertEx.ThrowsAsync(() => WriteMeta("$system-no-acl", "user1", "pa$$1", null)); - - await AssertEx.ThrowsAsync(() => SubscribeToStream("$system-no-acl", "user1", "pa$$1")); - } - - [Test] - public async Task operations_on_system_stream_with_no_acl_set_succeed_for_admin() - { - await ReadEvent("$system-no-acl", "adm", "admpa$$"); - await ReadStreamForward("$system-no-acl", "adm", "admpa$$"); - await ReadStreamBackward("$system-no-acl", "adm", "admpa$$"); - - await WriteStream("$system-no-acl", "adm", "admpa$$"); - - if (LogFormatHelper.SupportsExplicitTransactions) - { - await TransStart("$system-no-acl", "adm", "admpa$$"); - } - - if (LogFormatHelper.SupportsExplicitTransactions) - { - var transId = (await TransStart("$system-no-acl", "adm", "admpa$$")).TransactionId; - var trans = Connection.ContinueTransaction(transId, new UserCredentials("adm", "admpa$$")); - await trans.WriteAsync(); - await trans.CommitAsync(); - } - - await ReadMeta("$system-no-acl", "adm", "admpa$$"); - await WriteMeta("$system-no-acl", "adm", "admpa$$", null); - - await SubscribeToStream("$system-no-acl", "adm", "admpa$$"); - } - - [Test] - public async Task operations_on_system_stream_with_acl_set_to_usual_user_fail_for_not_authorized_user() - { - await AssertEx.ThrowsAsync(() => ReadEvent("$system-acl", "user2", "pa$$2")); - await AssertEx.ThrowsAsync(() => ReadStreamForward("$system-acl", "user2", "pa$$2")); - await AssertEx.ThrowsAsync(() => ReadStreamBackward("$system-acl", "user2", "pa$$2")); - - await AssertEx.ThrowsAsync(() => WriteStream("$system-acl", "user2", "pa$$2")); - await AssertEx.ThrowsAsync(() => TransStart("$system-acl", "user2", "pa$$2")); - - if (LogFormatHelper.SupportsExplicitTransactions) - { - var transId = (await TransStart("$system-acl", "user1", "pa$$1")).TransactionId; - var trans = Connection.ContinueTransaction(transId, new UserCredentials("user2", "pa$$2")); - await AssertEx.ThrowsAsync(() => trans.WriteAsync()); - await AssertEx.ThrowsAsync(() => trans.CommitAsync()); - } - - await AssertEx.ThrowsAsync(() => ReadMeta("$system-acl", "user2", "pa$$2")); - await AssertEx.ThrowsAsync(() => WriteMeta("$system-acl", "user2", "pa$$2", "user1")); - - await AssertEx.ThrowsAsync(() => SubscribeToStream("$system-acl", "user2", "pa$$2")); - } - - [Test] - public async Task operations_on_system_stream_with_acl_set_to_usual_user_succeed_for_that_user() - { - await ReadEvent("$system-acl", "user1", "pa$$1"); - await ReadStreamForward("$system-acl", "user1", "pa$$1"); - await ReadStreamBackward("$system-acl", "user1", "pa$$1"); - - await WriteStream("$system-acl", "user1", "pa$$1"); - - if (LogFormatHelper.SupportsExplicitTransactions) - { - await TransStart("$system-acl", "user1", "pa$$1"); - } - - if (LogFormatHelper.SupportsExplicitTransactions) - { - var transId = (await TransStart("$system-acl", "adm", "admpa$$")).TransactionId; - var trans = Connection.ContinueTransaction(transId, new UserCredentials("user1", "pa$$1")); - await trans.WriteAsync(); - await trans.CommitAsync(); - } - - await ReadMeta("$system-acl", "user1", "pa$$1"); - await WriteMeta("$system-acl", "user1", "pa$$1", "user1"); - - await SubscribeToStream("$system-acl", "user1", "pa$$1"); - } - - [Test] - public async Task operations_on_system_stream_with_acl_set_to_usual_user_succeed_for_admin() - { - await ReadEvent("$system-acl", "adm", "admpa$$"); - await ReadStreamForward("$system-acl", "adm", "admpa$$"); - await ReadStreamBackward("$system-acl", "adm", "admpa$$"); - - await WriteStream("$system-acl", "adm", "admpa$$"); - - if (LogFormatHelper.SupportsExplicitTransactions) - { - await TransStart("$system-acl", "adm", "admpa$$"); - } - - if (LogFormatHelper.SupportsExplicitTransactions) - { - var transId = (await TransStart("$system-acl", "user1", "pa$$1")).TransactionId; - var trans = Connection.ContinueTransaction(transId, new UserCredentials("adm", "admpa$$")); - await trans.WriteAsync(); - await trans.CommitAsync(); - } - - await ReadMeta("$system-acl", "adm", "admpa$$"); - await WriteMeta("$system-acl", "adm", "admpa$$", "user1"); - - await SubscribeToStream("$system-acl", "adm", "admpa$$"); - } - - - [Test] - public async Task operations_on_system_stream_with_acl_set_to_admins_fail_for_usual_user() - { - await AssertEx.ThrowsAsync(() => ReadEvent("$system-adm", "user1", "pa$$1")); - await AssertEx.ThrowsAsync(() => ReadStreamForward("$system-adm", "user1", "pa$$1")); - await AssertEx.ThrowsAsync(() => ReadStreamBackward("$system-adm", "user1", "pa$$1")); - - await AssertEx.ThrowsAsync(() => WriteStream("$system-adm", "user1", "pa$$1")); - await AssertEx.ThrowsAsync(() => TransStart("$system-adm", "user1", "pa$$1")); - - if (LogFormatHelper.SupportsExplicitTransactions) - { - var transId = (await TransStart("$system-adm", "adm", "admpa$$")).TransactionId; - var trans = Connection.ContinueTransaction(transId, new UserCredentials("user1", "pa$$1")); - await AssertEx.ThrowsAsync(() => trans.WriteAsync()); - await AssertEx.ThrowsAsync(() => trans.CommitAsync()); - } - - await AssertEx.ThrowsAsync(() => ReadMeta("$system-adm", "user1", "pa$$1")); - await AssertEx.ThrowsAsync(() => WriteMeta("$system-adm", "user1", "pa$$1", SystemRoles.Admins)); - - await AssertEx.ThrowsAsync(() => SubscribeToStream("$system-adm", "user1", "pa$$1")); - } - - [Test] - public async Task operations_on_system_stream_with_acl_set_to_admins_succeed_for_admin() - { - await ReadEvent("$system-adm", "adm", "admpa$$"); - await ReadStreamForward("$system-adm", "adm", "admpa$$"); - await ReadStreamBackward("$system-adm", "adm", "admpa$$"); - - await WriteStream("$system-adm", "adm", "admpa$$"); - - if (LogFormatHelper.SupportsExplicitTransactions) - { - await TransStart("$system-adm", "adm", "admpa$$"); - } - - if (LogFormatHelper.SupportsExplicitTransactions) - { - var transId = (await TransStart("$system-adm", "adm", "admpa$$")).TransactionId; - var trans = Connection.ContinueTransaction(transId, new UserCredentials("adm", "admpa$$")); - await trans.WriteAsync(); - await trans.CommitAsync(); - } - - await ReadMeta("$system-adm", "adm", "admpa$$"); - await WriteMeta("$system-adm", "adm", "admpa$$", SystemRoles.Admins); - - await SubscribeToStream("$system-adm", "adm", "admpa$$"); - } - - - [Test] - public async Task operations_on_system_stream_with_acl_set_to_all_succeed_for_not_authenticated_user() - { - await ReadEvent("$system-all", null, null); - await ReadStreamForward("$system-all", null, null); - await ReadStreamBackward("$system-all", null, null); - - await WriteStream("$system-all", null, null); - - if (LogFormatHelper.SupportsExplicitTransactions) - { - await TransStart("$system-all", null, null); - } - - if (LogFormatHelper.SupportsExplicitTransactions) - { - var transId = (await TransStart("$system-all", null, null)).TransactionId; - var trans = Connection.ContinueTransaction(transId); - await trans.WriteAsync(); - await trans.CommitAsync(); - } - - await ReadMeta("$system-all", null, null); - await WriteMeta("$system-all", null, null, SystemRoles.All); - - await SubscribeToStream("$system-all", null, null); - } - - [Test] - public async Task operations_on_system_stream_with_acl_set_to_all_succeed_for_usual_user() - { - await ReadEvent("$system-all", "user1", "pa$$1"); - await ReadStreamForward("$system-all", "user1", "pa$$1"); - await ReadStreamBackward("$system-all", "user1", "pa$$1"); - - await WriteStream("$system-all", "user1", "pa$$1"); - - if (LogFormatHelper.SupportsExplicitTransactions) - { - await TransStart("$system-all", "user1", "pa$$1"); - } - - if (LogFormatHelper.SupportsExplicitTransactions) - { - var transId = (await TransStart("$system-all", "user1", "pa$$1")).TransactionId; - var trans = Connection.ContinueTransaction(transId, new UserCredentials("user1", "pa$$1")); - await trans.WriteAsync(); - await trans.CommitAsync(); - } - - await ReadMeta("$system-all", "user1", "pa$$1"); - await WriteMeta("$system-all", "user1", "pa$$1", SystemRoles.All); - - await SubscribeToStream("$system-all", "user1", "pa$$1"); - } - - [Test] - public async Task operations_on_system_stream_with_acl_set_to_all_succeed_for_admin() - { - await ReadEvent("$system-all", "adm", "admpa$$"); - await ReadStreamForward("$system-all", "adm", "admpa$$"); - await ReadStreamBackward("$system-all", "adm", "admpa$$"); - - await WriteStream("$system-all", "adm", "admpa$$"); - - if (LogFormatHelper.SupportsExplicitTransactions) - { - await TransStart("$system-all", "adm", "admpa$$"); - } - - if (LogFormatHelper.SupportsExplicitTransactions) - { - var transId = (await TransStart("$system-all", "adm", "admpa$$")).TransactionId; - var trans = Connection.ContinueTransaction(transId, new UserCredentials("adm", "admpa$$")); - await trans.WriteAsync(); - await trans.CommitAsync(); - } - - await ReadMeta("$system-all", "adm", "admpa$$"); - await WriteMeta("$system-all", "adm", "admpa$$", SystemRoles.All); - - await SubscribeToStream("$system-all", "adm", "admpa$$"); - } -} diff --git a/src/EventStore.Core.Tests/ClientAPI/Security/transactional_write_stream_security.cs b/src/EventStore.Core.Tests/ClientAPI/Security/transactional_write_stream_security.cs deleted file mode 100644 index 93cea90bdd..0000000000 --- a/src/EventStore.Core.Tests/ClientAPI/Security/transactional_write_stream_security.cs +++ /dev/null @@ -1,181 +0,0 @@ -using System.Threading.Tasks; -using EventStore.ClientAPI.Exceptions; -using EventStore.ClientAPI.SystemData; -using NUnit.Framework; - -namespace EventStore.Core.Tests.ClientAPI.Security; - -[Category("ClientAPI"), Category("LongRunning"), Category("Network")] -[TestFixture(typeof(LogFormat.V2), typeof(string))] -public class transactional_write_stream_security : AuthenticationTestBase -{ - [Test] - public async Task starting_transaction_with_not_existing_credentials_is_not_authenticated() - { - await AssertEx.ThrowsAsync(() => TransStart("write-stream", "badlogin", "badpass")); - } - - [Test] - public async Task starting_transaction_to_stream_with_no_credentials_is_denied() - { - await AssertEx.ThrowsAsync(() => TransStart("write-stream", null, null)); - } - - [Test] - public async Task starting_transaction_to_stream_with_not_authorized_user_credentials_is_denied() - { - await AssertEx.ThrowsAsync(() => TransStart("write-stream", "user2", "pa$$2")); - } - - [Test] - public async Task starting_transaction_to_stream_with_authorized_user_credentials_succeeds() - { - await TransStart("write-stream", "user1", "pa$$1"); - } - - [Test] - public async Task starting_transaction_to_stream_with_admin_user_credentials_succeeds() - { - await TransStart("write-stream", "adm", "admpa$$"); - } - - - [Test] - public async Task committing_transaction_with_not_existing_credentials_is_not_authenticated() - { - var t1 = await TransStart("write-stream", "user1", "pa$$1"); - await t1.WriteAsync(CreateEvents()); - var t2 = Connection.ContinueTransaction(t1.TransactionId, new UserCredentials("badlogin", "badpass")); - - await AssertEx.ThrowsAsync(() => t2.CommitAsync()); - } - - [Test] - public async Task committing_transaction_to_stream_with_no_credentials_is_denied() - { - var t1 = await TransStart("write-stream", "user1", "pa$$1"); - await t1.WriteAsync(); - var t2 = Connection.ContinueTransaction(t1.TransactionId); - await AssertEx.ThrowsAsync(() => t2.CommitAsync()); - } - - [Test] - public async Task committing_transaction_to_stream_with_not_authorized_user_credentials_is_denied() - { - var t1 = await TransStart("write-stream", "user1", "pa$$1"); - await t1.WriteAsync(); - var t2 = Connection.ContinueTransaction(t1.TransactionId, new UserCredentials("user2", "pa$$2")); - await AssertEx.ThrowsAsync(() => t2.CommitAsync()); - } - - [Test] - public async Task committing_transaction_to_stream_with_authorized_user_credentials_succeeds() - { - var transId = (await TransStart("write-stream", "user1", "pa$$1")).TransactionId; - var t2 = Connection.ContinueTransaction(transId, new UserCredentials("user1", "pa$$1")); - await t2.WriteAsync(); - await t2.CommitAsync(); - } - - [Test] - public async Task committing_transaction_to_stream_with_admin_user_credentials_succeeds() - { - var transId = (await TransStart("write-stream", "user1", "pa$$1")).TransactionId; - var t2 = Connection.ContinueTransaction(transId, new UserCredentials("adm", "admpa$$")); - await t2.WriteAsync(); - await t2.CommitAsync(); - } - - - [Test] - public void transaction_to_no_acl_stream_succeeds_when_no_credentials_are_passed() - { - ExpectNoException(async () => - { - var t = await TransStart("noacl-stream", null, null); - await t.WriteAsync(CreateEvents()); - await t.CommitAsync(); - }); - } - - [Test] - public async Task transaction_to_no_acl_stream_is_not_authenticated_when_not_existing_credentials_are_passed() - { - await AssertEx.ThrowsAsync(() => TransStart("noacl-stream", "badlogin", "badpass")); - } - - [Test] - public void transaction_to_no_acl_stream_succeeds_when_any_existing_user_credentials_are_passed() - { - ExpectNoException(async () => - { - var t = await TransStart("noacl-stream", "user1", "pa$$1"); - await t.WriteAsync(CreateEvents()); - await t.CommitAsync(); - }); - ExpectNoException(async () => - { - var t = await TransStart("noacl-stream", "user2", "pa$$2"); - await t.WriteAsync(CreateEvents()); - await t.CommitAsync(); - }); - } - - [Test] - public void transaction_to_no_acl_stream_succeeds_when_admin_user_credentials_are_passed() - { - ExpectNoException(async () => - { - var t = await TransStart("noacl-stream", "adm", "admpa$$"); - await t.WriteAsync(CreateEvents()); - await t.CommitAsync(); - }); - } - - - [Test] - public void transaction_to_all_access_normal_stream_succeeds_when_no_credentials_are_passed() - { - ExpectNoException(async () => - { - var t = await TransStart("normal-all", null, null); - await t.WriteAsync(CreateEvents()); - await t.CommitAsync(); - }); - } - - [Test] - public async Task - transaction_to_all_access_normal_stream_is_not_authenticated_when_not_existing_credentials_are_passed() - { - await AssertEx.ThrowsAsync(() => TransStart("normal-all", "badlogin", "badpass")); - } - - [Test] - public void transaction_to_all_access_normal_stream_succeeds_when_any_existing_user_credentials_are_passed() - { - ExpectNoException(async () => - { - var t = await TransStart("normal-all", "user1", "pa$$1"); - await t.WriteAsync(CreateEvents()); - await t.CommitAsync(); - }); - ExpectNoException(async () => - { - var t = await TransStart("normal-all", "user2", "pa$$2"); - await t.WriteAsync(CreateEvents()); - await t.CommitAsync(); - }); - } - - [Test] - public void transaction_to_all_access_normal_stream_succeeds_when_admin_user_credentials_are_passed() - { - ExpectNoException(async () => - { - var t = await TransStart("normal-all", "adm", "admpa$$"); - await t.WriteAsync(CreateEvents()); - await t.CommitAsync(); - }); - } -} diff --git a/src/EventStore.Core.Tests/ClientAPI/Security/write_stream_meta_security.cs b/src/EventStore.Core.Tests/ClientAPI/Security/write_stream_meta_security.cs deleted file mode 100644 index 68b1475159..0000000000 --- a/src/EventStore.Core.Tests/ClientAPI/Security/write_stream_meta_security.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System.Threading.Tasks; -using EventStore.ClientAPI.Exceptions; -using EventStore.Core.Services; -using NUnit.Framework; - -namespace EventStore.Core.Tests.ClientAPI.Security; - -[Category("ClientAPI"), Category("LongRunning"), Category("Network")] -[TestFixture(typeof(LogFormat.V2), typeof(string))] -public class write_stream_meta_security : AuthenticationTestBase -{ - [Test] - public async Task writing_meta_with_not_existing_credentials_is_not_authenticated() - { - await AssertEx.ThrowsAsync(() => WriteMeta("metawrite-stream", "badlogin", "badpass", "user1")); - } - - [Test] - public async Task writing_meta_to_stream_with_no_credentials_is_denied() - { - await AssertEx.ThrowsAsync(() => WriteMeta("metawrite-stream", null, null, "user1")); - } - - [Test] - public async Task writing_meta_to_stream_with_not_authorized_user_credentials_is_denied() - { - await AssertEx.ThrowsAsync(() => WriteMeta("metawrite-stream", "user2", "pa$$2", "user1")); - } - - [Test] - public async Task writing_meta_to_stream_with_authorized_user_credentials_succeeds() - { - await WriteMeta("metawrite-stream", "user1", "pa$$1", "user1"); - } - - [Test] - public async Task writing_meta_to_stream_with_admin_user_credentials_succeeds() - { - await WriteMeta("metawrite-stream", "adm", "admpa$$", "user1"); - } - - - [Test] - public async Task writing_meta_to_no_acl_stream_succeeds_when_no_credentials_are_passed() - { - await WriteMeta("noacl-stream", null, null, null); - } - - [Test] - public async Task writing_meta_to_no_acl_stream_is_not_authenticated_when_not_existing_credentials_are_passed() - { - await AssertEx.ThrowsAsync(() => WriteMeta("noacl-stream", "badlogin", "badpass", null)); - } - - [Test] - public async Task writing_meta_to_no_acl_stream_succeeds_when_any_existing_user_credentials_are_passed() - { - await WriteMeta("noacl-stream", "user1", "pa$$1", null); - await WriteMeta("noacl-stream", "user2", "pa$$2", null); - } - - [Test] - public async Task writing_meta_to_no_acl_stream_succeeds_when_admin_user_credentials_are_passed() - { - await WriteMeta("noacl-stream", "adm", "admpa$$", null); - } - - - [Test] - public async Task writing_meta_to_all_access_normal_stream_succeeds_when_no_credentials_are_passed() - { - await WriteMeta("normal-all", null, null, SystemRoles.All); - } - - [Test] - public async Task - writing_meta_to_all_access_normal_stream_is_not_authenticated_when_not_existing_credentials_are_passed() - { - await AssertEx.ThrowsAsync(() => WriteMeta("normal-all", "badlogin", "badpass", SystemRoles.All)); - } - - [Test] - public async Task writing_meta_to_all_access_normal_stream_succeeds_when_any_existing_user_credentials_are_passed() - { - await WriteMeta("normal-all", "user1", "pa$$1", SystemRoles.All); - await WriteMeta("normal-all", "user2", "pa$$2", SystemRoles.All); - } - - [Test] - public async Task writing_meta_to_all_access_normal_stream_succeeds_when_admin_user_credentials_are_passed() - { - await WriteMeta("normal-all", "adm", "admpa$$", SystemRoles.All); - } -} diff --git a/src/EventStore.Core.Tests/ClientAPI/Security/write_stream_security.cs b/src/EventStore.Core.Tests/ClientAPI/Security/write_stream_security.cs deleted file mode 100644 index 8581de126b..0000000000 --- a/src/EventStore.Core.Tests/ClientAPI/Security/write_stream_security.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System.Threading.Tasks; -using EventStore.ClientAPI.Exceptions; -using NUnit.Framework; - -namespace EventStore.Core.Tests.ClientAPI.Security; - -[Category("ClientAPI"), Category("LongRunning"), Category("Network")] -[TestFixture(typeof(LogFormat.V2), typeof(string))] -public class write_stream_security : AuthenticationTestBase -{ - [Test] - public async Task writing_to_all_is_never_allowed() - { - await AssertEx.ThrowsAsync(() => WriteStream("$all", null, null)); - await AssertEx.ThrowsAsync(() => WriteStream("$all", "user1", "pa$$1")); - await AssertEx.ThrowsAsync(() => WriteStream("$all", "adm", "admpa$$")); - } - - [Test] - public async Task writing_with_not_existing_credentials_is_not_authenticated() - { - await AssertEx.ThrowsAsync(() => WriteStream("write-stream", "badlogin", "badpass")); - } - - [Test] - public async Task writing_to_stream_with_no_credentials_is_denied() - { - await AssertEx.ThrowsAsync(() => WriteStream("write-stream", null, null)); - } - - [Test] - public async Task writing_to_stream_with_not_authorized_user_credentials_is_denied() - { - await AssertEx.ThrowsAsync(() => WriteStream("write-stream", "user2", "pa$$2")); - } - - [Test] - public async Task writing_to_stream_with_authorized_user_credentials_succeeds() - { - await WriteStream("write-stream", "user1", "pa$$1"); - } - - [Test] - public async Task writing_to_stream_with_admin_user_credentials_succeeds() - { - await WriteStream("write-stream", "adm", "admpa$$"); - } - - - [Test] - public async Task writing_to_no_acl_stream_succeeds_when_no_credentials_are_passed() - { - await WriteStream("noacl-stream", null, null); - } - - [Test] - public async Task writing_to_no_acl_stream_is_not_authenticated_when_not_existing_credentials_are_passed() - { - await AssertEx.ThrowsAsync(() => WriteStream("noacl-stream", "badlogin", "badpass")); - } - - [Test] - public async Task writing_to_no_acl_stream_succeeds_when_any_existing_user_credentials_are_passed() - { - await WriteStream("noacl-stream", "user1", "pa$$1"); - await WriteStream("noacl-stream", "user2", "pa$$2"); - } - - [Test] - public async Task writing_to_no_acl_stream_succeeds_when_any_admin_user_credentials_are_passed() - { - await WriteStream("noacl-stream", "adm", "admpa$$"); - } - - - [Test] - public async Task writing_to_all_access_normal_stream_succeeds_when_no_credentials_are_passed() - { - await WriteStream("normal-all", null, null); - } - - [Test] - public async Task - writing_to_all_access_normal_stream_is_not_authenticated_when_not_existing_credentials_are_passed() - { - await AssertEx.ThrowsAsync(() => WriteStream("normal-all", "badlogin", "badpass")); - } - - [Test] - public async Task writing_to_all_access_normal_stream_succeeds_when_any_existing_user_credentials_are_passed() - { - await WriteStream("normal-all", "user1", "pa$$1"); - await WriteStream("normal-all", "user2", "pa$$2"); - } - - [Test] - public async Task writing_to_all_access_normal_stream_succeeds_when_any_admin_user_credentials_are_passed() - { - await WriteStream("normal-all", "adm", "admpa$$"); - } -} diff --git a/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/AuthenticationTestBase.cs b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/AuthenticationTestBase.cs new file mode 100644 index 0000000000..c8a08094af --- /dev/null +++ b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/AuthenticationTestBase.cs @@ -0,0 +1,471 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using EventStore.Client; +using EventStore.Client.Streams; +using EventStore.Core.Services; +using EventStore.Core.Services.Transport.Grpc; +using EventStore.Core.Services.UserManagement; +using EventStore.Core.Tests.Services.Transport.Grpc.StreamsTests; +using Google.Protobuf; +using Grpc.Core; +using NUnit.Framework; +using GrpcMetadata = EventStore.Core.Services.Transport.Grpc.Constants.Metadata; +using UsersClient = EventStore.Client.Users.Users.UsersClient; +using UsersCreateReq = EventStore.Client.Users.CreateReq; + +namespace EventStore.Core.Tests.Services.Transport.Grpc.Security; + +public enum SecurityOperation +{ + ReadEvent, + ReadForward, + ReadBackward, + Subscribe, + Write, + Tombstone, + ReadMetadata, + WriteMetadata +} + +public enum SecurityIdentity +{ + Anonymous, + Invalid, + UserOne, + UserTwo, + Admin +} + +public enum StreamAclKind +{ + None, + UserOne, + Admins, + All +} + +public enum StreamKind +{ + User, + System +} + +public readonly record struct StreamSecurityCase( + StreamKind StreamKind, + StreamAclKind Acl, + SecurityIdentity Identity, + StatusCode ExpectedStatus, + string TestName); + +public abstract class AuthenticationTestBase : GrpcSpecification +{ + protected const string UserOneName = "user1"; + protected const string UserTwoName = "user2"; + protected const string AdminName = "adm"; + private const string UserOnePassword = "pa$$1"; + private const string UserTwoPassword = "pa$$2"; + private const string AdminPassword = "admpa$$"; + private readonly SecurityIdentity? _defaultIdentity; + + protected AuthenticationTestBase(SecurityIdentity? defaultIdentity = null) + { + _defaultIdentity = defaultIdentity; + } + + protected static (string userName, string password) UserOneCredentials => (UserOneName, UserOnePassword); + protected static (string userName, string password) UserTwoCredentials => (UserTwoName, UserTwoPassword); + protected static (string userName, string password) NamedAdminCredentials => (AdminName, AdminPassword); + + protected override (string userName, string password) DefaultCredentials => + _defaultIdentity is { } identity ? CredentialsFor(identity) : default; + + protected override async Task Given() + { + await CreateUser(UserOneName, UserOnePassword); + await CreateUser(UserTwoName, UserTwoPassword); + await CreateUser(AdminName, AdminPassword, SystemRoles.Admins); + } + + protected override Task When() => Task.CompletedTask; + + protected CallOptions OptionsFor(SecurityIdentity identity) => identity switch + { + SecurityIdentity.Anonymous => GetCallOptions(), + SecurityIdentity.Invalid => GetCallOptions(("badlogin", "badpass")), + SecurityIdentity.UserOne => GetCallOptions(UserOneCredentials), + SecurityIdentity.UserTwo => GetCallOptions(UserTwoCredentials), + SecurityIdentity.Admin => GetCallOptions(NamedAdminCredentials), + _ => throw new ArgumentOutOfRangeException(nameof(identity), identity, null) + }; + + protected async Task CreateStreamWithAcl( + StreamKind streamKind, + StreamAclKind acl, + bool seed = true) + { + var prefix = streamKind == StreamKind.System ? "$" : string.Empty; + var streamName = $"{prefix}grpc-security-{Guid.NewGuid():N}"; + if (acl != StreamAclKind.None) + { + await SetStreamAcl(streamName, acl); + } + + if (seed) + { + await Append(streamName, OptionsFor(SecurityIdentity.Admin)); + } + + return streamName; + } + + protected async Task SetStreamAcl(string streamName, StreamAclKind acl) => + await SetStreamAcl(streamName, AclJson(acl)); + + protected async Task SetStreamAcl(string streamName, string aclJson) => + await Append(SystemStreams.MetastreamOf(streamName), OptionsFor(SecurityIdentity.Admin), + SystemEventTypes.StreamMetadata, aclJson); + + protected async Task SetSystemSettings(string settingsJson) => + await Append(SystemStreams.SettingsStream, OptionsFor(SecurityIdentity.Admin), "security-settings", settingsJson); + + protected async Task SetDefaultAcl(StreamKind streamKind, StreamAclKind acl) + { + var property = streamKind == StreamKind.System ? "$systemStreamAcl" : "$userStreamAcl"; + await SetSystemSettings($"{{\"{property}\":{AclBodyJson(acl)}}}"); + } + + protected Task ExecuteOperation( + SecurityOperation operation, + string streamName, + SecurityIdentity identity) => ExecuteOperation(operation, streamName, OptionsFor(identity)); + + protected Task ExecuteOperationWithDefault( + SecurityOperation operation, + string streamName) => ExecuteOperation(operation, streamName, GetCallOptions()); + + protected async Task ExecuteAllOperation( + bool subscribe, + SecurityIdentity identity, + bool backwards = false) => + await CaptureStatus(() => ReadAll(OptionsFor(identity), subscribe, backwards)); + + protected async Task ExecuteAllOperationWithDefault( + bool subscribe, + bool backwards = false) => + await CaptureStatus(() => ReadAll(GetCallOptions(), subscribe, backwards)); + + protected Task ExecuteBatchAppend(string streamName, SecurityIdentity identity) => + ExecuteBatchAppend(streamName, OptionsFor(identity)); + + protected Task ExecuteBatchAppendWithDefault(string streamName) => + ExecuteBatchAppend(streamName, GetCallOptions()); + + private async Task ExecuteBatchAppend(string streamName, CallOptions callOptions) + { + try + { + using var call = StreamsClient.BatchAppend(callOptions); + var correlationId = Uuid.NewUuid(); + await call.RequestStream.WriteAsync(new BatchAppendReq + { + Options = new BatchAppendReq.Types.Options + { + Any = new Google.Protobuf.WellKnownTypes.Empty(), + StreamIdentifier = StreamIdentifier(streamName) + }, + CorrelationId = correlationId.ToDto(), + IsFinal = true, + ProposedMessages = { ProposedBatchMessage(), ProposedBatchMessage() } + }); + await call.RequestStream.CompleteAsync(); + if (!await call.ResponseStream.MoveNext()) + { + return StatusCode.Unknown; + } + + var response = call.ResponseStream.Current; + return response.ResultCase == BatchAppendResp.ResultOneofCase.Success + ? StatusCode.OK + : (StatusCode)response.Error.Code; + } + catch (RpcException ex) + { + return ex.StatusCode; + } + } + + protected async Task AssertAllStreamOperations( + string streamName, + SecurityIdentity identity, + StatusCode expectedStatus) + { + foreach (var operation in AllStreamOperations()) + { + var status = await ExecuteOperation(operation, streamName, identity); + Assert.AreEqual(expectedStatus, status, operation.ToString()); + } + } + + protected async Task AssertAllStreamOperations( + StreamKind streamKind, + StreamAclKind acl, + SecurityIdentity identity, + StatusCode expectedStatus) + { + foreach (var operation in AllStreamOperations()) + { + var streamName = await CreateStreamWithAcl(streamKind, acl); + var status = await ExecuteOperation(operation, streamName, identity); + Assert.AreEqual(expectedStatus, status, operation.ToString()); + } + } + + protected async Task AssertAllStreamOperationsWithDefault( + string streamName, + StatusCode expectedStatus) + { + foreach (var operation in AllStreamOperations()) + { + var status = await ExecuteOperationWithDefault(operation, streamName); + Assert.AreEqual(expectedStatus, status, operation.ToString()); + } + } + + protected static void AssertStatus(StatusCode expectedStatus, StatusCode actualStatus) => + Assert.AreEqual(expectedStatus, actualStatus); + + protected static string AclJson(string role) => + $"{{\"$acl\":{AclBodyJson(role)}}}"; + + protected static string WriteAclJson(params string[] roles) + { + var values = string.Join(',', Array.ConvertAll(roles, role => $"\"{role}\"")); + return $"{{\"$acl\":{{\"$w\":[{values}]}}}}"; + } + + private async Task ExecuteOperation( + SecurityOperation operation, + string streamName, + CallOptions callOptions) => operation switch + { + SecurityOperation.ReadEvent => await CaptureStatus(() => ReadStream(streamName, callOptions, false, false, true)), + SecurityOperation.ReadForward => await CaptureStatus(() => ReadStream(streamName, callOptions, false, false, false)), + SecurityOperation.ReadBackward => await CaptureStatus(() => ReadStream(streamName, callOptions, false, true, false)), + SecurityOperation.Subscribe => await CaptureStatus(() => ReadStream(streamName, callOptions, true, false, false)), + SecurityOperation.Write => await CaptureStatus(() => Append(streamName, callOptions)), + SecurityOperation.Tombstone => await CaptureStatus(() => Tombstone(streamName, callOptions)), + SecurityOperation.ReadMetadata => await CaptureStatus(() => + ReadStream(SystemStreams.MetastreamOf(streamName), callOptions, false, true, false)), + SecurityOperation.WriteMetadata => await CaptureStatus(() => Append( + SystemStreams.MetastreamOf(streamName), callOptions, SystemEventTypes.StreamMetadata, "{}")), + _ => throw new ArgumentOutOfRangeException(nameof(operation), operation, null) + }; + + private async Task CreateUser(string loginName, string password, params string[] groups) + { + var users = new UsersClient(Channel); + var request = new UsersCreateReq + { + Options = new UsersCreateReq.Types.Options + { + FullName = loginName, + LoginName = loginName, + Password = password, + Groups = { groups } + } + }; + await users.CreateAsync(request, GetCallOptions(AdminCredentials)); + } + + private async Task Append( + string streamName, + CallOptions callOptions, + string eventType = "security-event", + string data = "{}") + { + using var call = StreamsClient.Append(callOptions); + await call.RequestStream.WriteAsync(new AppendReq + { + Options = new AppendReq.Types.Options + { + Any = new Empty(), + StreamIdentifier = StreamIdentifier(streamName) + } + }); + await call.RequestStream.WriteAsync(new AppendReq + { + ProposedMessage = new AppendReq.Types.ProposedMessage + { + Id = Uuid.NewUuid().ToDto(), + Data = ByteString.CopyFromUtf8(data), + Metadata = + { + { GrpcMetadata.ContentType, GrpcMetadata.ContentTypes.ApplicationJson }, + { GrpcMetadata.Type, eventType } + } + } + }); + await call.RequestStream.CompleteAsync(); + await call.ResponseAsync; + } + + private async Task ReadStream( + string streamName, + CallOptions callOptions, + bool subscribe, + bool backwards, + bool singleEvent) + { + var readBackwards = backwards || singleEvent; + var streamOptions = new ReadReq.Types.Options.Types.StreamOptions + { + StreamIdentifier = StreamIdentifier(streamName) + }; + if (singleEvent) + { + streamOptions.End = new Empty(); + } + else if (backwards) + { + streamOptions.End = new Empty(); + } + else + { + streamOptions.Start = new Empty(); + } + + var options = new ReadReq.Types.Options + { + Stream = streamOptions, + ReadDirection = readBackwards + ? ReadReq.Types.Options.Types.ReadDirection.Backwards + : ReadReq.Types.Options.Types.ReadDirection.Forwards, + NoFilter = new Empty(), + UuidOption = new ReadReq.Types.Options.Types.UUIDOption { Structured = new Empty() } + }; + if (subscribe) + { + options.Subscription = new ReadReq.Types.Options.Types.SubscriptionOptions(); + } + else + { + options.Count = 1; + } + + using var call = StreamsClient.Read(new ReadReq { Options = options }, callOptions); + await call.ResponseStream.MoveNext(); + } + + private async Task ReadAll(CallOptions callOptions, bool subscribe, bool backwards) + { + var allOptions = new ReadReq.Types.Options.Types.AllOptions(); + if (backwards) + { + allOptions.End = new Empty(); + } + else + { + allOptions.Start = new Empty(); + } + + var options = new ReadReq.Types.Options + { + All = allOptions, + ReadDirection = backwards + ? ReadReq.Types.Options.Types.ReadDirection.Backwards + : ReadReq.Types.Options.Types.ReadDirection.Forwards, + NoFilter = new Empty(), + UuidOption = new ReadReq.Types.Options.Types.UUIDOption { Structured = new Empty() } + }; + if (subscribe) + { + options.Subscription = new ReadReq.Types.Options.Types.SubscriptionOptions(); + } + else + { + options.Count = 1; + } + + using var call = StreamsClient.Read(new ReadReq { Options = options }, callOptions); + await call.ResponseStream.MoveNext(); + } + + private async Task Tombstone(string streamName, CallOptions callOptions) + { + using var call = StreamsClient.TombstoneAsync(new TombstoneReq + { + Options = new TombstoneReq.Types.Options + { + Any = new Empty(), + StreamIdentifier = StreamIdentifier(streamName) + } + }, callOptions); + await call.ResponseAsync; + } + + private static async Task CaptureStatus(Func action) + { + try + { + await action(); + return StatusCode.OK; + } + catch (RpcException ex) + { + return ex.StatusCode; + } + } + + private static IEnumerable AllStreamOperations() + { + yield return SecurityOperation.ReadEvent; + yield return SecurityOperation.ReadForward; + yield return SecurityOperation.ReadBackward; + yield return SecurityOperation.Write; + yield return SecurityOperation.ReadMetadata; + yield return SecurityOperation.WriteMetadata; + yield return SecurityOperation.Subscribe; + yield return SecurityOperation.Tombstone; + } + + private static (string userName, string password) CredentialsFor(SecurityIdentity identity) => identity switch + { + SecurityIdentity.UserOne => UserOneCredentials, + SecurityIdentity.UserTwo => UserTwoCredentials, + SecurityIdentity.Admin => NamedAdminCredentials, + SecurityIdentity.Invalid => ("badlogin", "badpass"), + SecurityIdentity.Anonymous => default, + _ => throw new ArgumentOutOfRangeException(nameof(identity), identity, null) + }; + + private static StreamIdentifier StreamIdentifier(string streamName) => new() + { + StreamName = ByteString.CopyFromUtf8(streamName) + }; + + private static BatchAppendReq.Types.ProposedMessage ProposedBatchMessage() => new() + { + Id = Uuid.NewUuid().ToDto(), + Data = ByteString.CopyFromUtf8("{}"), + Metadata = + { + { GrpcMetadata.ContentType, GrpcMetadata.ContentTypes.ApplicationJson }, + { GrpcMetadata.Type, "security-event" } + } + }; + + private static string AclJson(StreamAclKind acl) => $"{{\"$acl\":{AclBodyJson(acl)}}}"; + + private static string AclBodyJson(StreamAclKind acl) => acl switch + { + StreamAclKind.UserOne => AclBodyJson(UserOneName), + StreamAclKind.Admins => AclBodyJson(SystemRoles.Admins), + StreamAclKind.All => AclBodyJson(SystemRoles.All), + StreamAclKind.None => "{}", + _ => throw new ArgumentOutOfRangeException(nameof(acl), acl, null) + }; + + private static string AclBodyJson(string role) => + $"{{\"$r\":\"{role}\",\"$w\":\"{role}\",\"$d\":\"{role}\",\"$mr\":\"{role}\",\"$mw\":\"{role}\"}}"; +} diff --git a/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/all_stream_with_no_acl_security.cs b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/all_stream_with_no_acl_security.cs new file mode 100644 index 0000000000..77ece34354 --- /dev/null +++ b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/all_stream_with_no_acl_security.cs @@ -0,0 +1,72 @@ +using System.Threading.Tasks; +using EventStore.Core.Services; +using Grpc.Core; +using NUnit.Framework; + +namespace EventStore.Core.Tests.Services.Transport.Grpc.Security; + +[NonParallelizable] +[TestFixture(typeof(LogFormat.V2), typeof(string))] +public class all_stream_with_no_acl_security : AuthenticationTestBase +{ + protected override async Task Given() + { + await base.Given(); + await SetStreamAcl(SystemStreams.AllStream, "{}"); + } + + [Test] + public async Task write_to_all_is_never_allowed() + { + await AssertOperation(SecurityOperation.Write, SecurityIdentity.Anonymous, StatusCode.PermissionDenied); + await AssertOperation(SecurityOperation.Write, SecurityIdentity.UserOne, StatusCode.PermissionDenied); + await AssertOperation(SecurityOperation.Write, SecurityIdentity.Admin, StatusCode.PermissionDenied); + } + + [Test] + public async Task delete_of_all_is_never_allowed() + { + await AssertOperation(SecurityOperation.Tombstone, SecurityIdentity.Anonymous, StatusCode.PermissionDenied); + await AssertOperation(SecurityOperation.Tombstone, SecurityIdentity.UserOne, StatusCode.PermissionDenied); + await AssertOperation(SecurityOperation.Tombstone, SecurityIdentity.Admin, StatusCode.PermissionDenied); + } + + [Test] + public async Task reading_and_subscribing_is_not_allowed_when_no_credentials_are_passed() => + await AssertReadAndSubscribe(SecurityIdentity.Anonymous, StatusCode.PermissionDenied); + + [Test] + public async Task reading_and_subscribing_is_not_allowed_for_usual_user() => + await AssertReadAndSubscribe(SecurityIdentity.UserOne, StatusCode.PermissionDenied); + + [Test] + public async Task reading_and_subscribing_is_allowed_for_admin_user() => + await AssertReadAndSubscribe(SecurityIdentity.Admin, StatusCode.OK); + + [Test] + public async Task meta_write_is_not_allowed_when_no_credentials_are_passed() => + await AssertOperation(SecurityOperation.WriteMetadata, SecurityIdentity.Anonymous, StatusCode.PermissionDenied); + + [Test] + public async Task meta_write_is_not_allowed_for_usual_user() => + await AssertOperation(SecurityOperation.WriteMetadata, SecurityIdentity.UserOne, StatusCode.PermissionDenied); + + [Test] + public async Task meta_write_is_allowed_for_admin_user() => + await AssertOperation(SecurityOperation.WriteMetadata, SecurityIdentity.Admin, StatusCode.OK); + + private async Task AssertReadAndSubscribe(SecurityIdentity identity, StatusCode expectedStatus) + { + await AssertOperation(SecurityOperation.ReadEvent, identity, expectedStatus); + AssertStatus(expectedStatus, await ExecuteAllOperation(false, identity)); + AssertStatus(expectedStatus, await ExecuteAllOperation(false, identity, backwards: true)); + await AssertOperation(SecurityOperation.ReadMetadata, identity, expectedStatus); + AssertStatus(expectedStatus, await ExecuteAllOperation(true, identity)); + } + + private async Task AssertOperation( + SecurityOperation operation, + SecurityIdentity identity, + StatusCode expectedStatus) => + AssertStatus(expectedStatus, await ExecuteOperation(operation, SystemStreams.AllStream, identity)); +} diff --git a/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/authorized_default_credentials_security.cs b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/authorized_default_credentials_security.cs new file mode 100644 index 0000000000..fd78c1ff6f --- /dev/null +++ b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/authorized_default_credentials_security.cs @@ -0,0 +1,101 @@ +using System; +using System.Threading.Tasks; +using EventStore.Core.Services; +using Grpc.Core; +using NUnit.Framework; + +namespace EventStore.Core.Tests.Services.Transport.Grpc.Security; + +[NonParallelizable] +[TestFixture(typeof(LogFormat.V2), typeof(string))] +public class authorized_default_credentials_security : AuthenticationTestBase +{ + public authorized_default_credentials_security() : base(SecurityIdentity.UserOne) + { + } + + [Test] + public async Task all_operations_succeeds_when_passing_no_explicit_credentials() + { + var streams = await CreateProtectedStreams(); + + AssertStatus(StatusCode.OK, await ExecuteAllOperationWithDefault(false)); + AssertStatus(StatusCode.OK, await ExecuteAllOperationWithDefault(false, backwards: true)); + await AssertDefaultOperation(SecurityOperation.ReadEvent, streams.Read, StatusCode.OK); + await AssertDefaultOperation(SecurityOperation.ReadForward, streams.Read, StatusCode.OK); + await AssertDefaultOperation(SecurityOperation.ReadBackward, streams.Read, StatusCode.OK); + await AssertDefaultOperation(SecurityOperation.Write, streams.Write, StatusCode.OK); + AssertStatus(StatusCode.OK, await ExecuteBatchAppendWithDefault(streams.Write)); + await AssertDefaultOperation(SecurityOperation.ReadMetadata, streams.MetadataRead, StatusCode.OK); + await AssertDefaultOperation(SecurityOperation.WriteMetadata, streams.MetadataWrite, StatusCode.OK); + await AssertDefaultOperation(SecurityOperation.Subscribe, streams.Read, StatusCode.OK); + AssertStatus(StatusCode.OK, await ExecuteAllOperationWithDefault(true)); + } + + [Test] + public async Task all_operations_are_not_authenticated_when_overriden_with_not_existing_credentials() + { + var streams = await CreateProtectedStreams(); + await AssertExplicitOperations(streams, SecurityIdentity.Invalid, StatusCode.Unauthenticated); + } + + [Test] + public async Task all_operations_are_not_authorized_when_overriden_with_not_authorized_credentials() + { + var streams = await CreateProtectedStreams(); + await AssertExplicitOperations(streams, SecurityIdentity.UserTwo, StatusCode.PermissionDenied); + } + + private async Task CreateProtectedStreams() + { + var suffix = Guid.NewGuid().ToString("N"); + var streams = new ProtectedStreams( + $"read-stream-{suffix}", + $"write-stream-{suffix}", + $"metaread-stream-{suffix}", + $"metawrite-stream-{suffix}"); + await SetStreamAcl(streams.Read, $"{{\"$acl\":{{\"$r\":\"{UserOneName}\"}}}}"); + await SetStreamAcl(streams.Write, $"{{\"$acl\":{{\"$w\":\"{UserOneName}\"}}}}"); + await SetStreamAcl(streams.MetadataRead, $"{{\"$acl\":{{\"$mr\":\"{UserOneName}\"}}}}"); + await SetStreamAcl(streams.MetadataWrite, $"{{\"$acl\":{{\"$mw\":\"{UserOneName}\"}}}}"); + await SetStreamAcl(SystemStreams.AllStream, $"{{\"$acl\":{{\"$r\":\"{UserOneName}\"}}}}"); + return streams; + } + + private async Task AssertExplicitOperations( + ProtectedStreams streams, + SecurityIdentity identity, + StatusCode expectedStatus) + { + AssertStatus(expectedStatus, await ExecuteAllOperation(false, identity)); + AssertStatus(expectedStatus, await ExecuteAllOperation(false, identity, backwards: true)); + await AssertOperation(SecurityOperation.ReadEvent, streams.Read, identity, expectedStatus); + await AssertOperation(SecurityOperation.ReadForward, streams.Read, identity, expectedStatus); + await AssertOperation(SecurityOperation.ReadBackward, streams.Read, identity, expectedStatus); + await AssertOperation(SecurityOperation.Write, streams.Write, identity, expectedStatus); + AssertStatus(expectedStatus, await ExecuteBatchAppend(streams.Write, identity)); + await AssertOperation(SecurityOperation.ReadMetadata, streams.MetadataRead, identity, expectedStatus); + await AssertOperation(SecurityOperation.WriteMetadata, streams.MetadataWrite, identity, expectedStatus); + await AssertOperation(SecurityOperation.Subscribe, streams.Read, identity, expectedStatus); + AssertStatus(expectedStatus, await ExecuteAllOperation(true, identity)); + } + + private async Task AssertDefaultOperation( + SecurityOperation operation, + string streamName, + StatusCode expectedStatus) => + AssertStatus(expectedStatus, await ExecuteOperationWithDefault(operation, streamName)); + + private async Task AssertOperation( + SecurityOperation operation, + string streamName, + SecurityIdentity identity, + StatusCode expectedStatus) => + AssertStatus(expectedStatus, await ExecuteOperation(operation, streamName, identity)); + + private readonly record struct ProtectedStreams( + string Read, + string Write, + string MetadataRead, + string MetadataWrite); +} diff --git a/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/delete_stream_security.cs b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/delete_stream_security.cs new file mode 100644 index 0000000000..3910d4994a --- /dev/null +++ b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/delete_stream_security.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using EventStore.Core.Services; +using Grpc.Core; +using NUnit.Framework; + +namespace EventStore.Core.Tests.Services.Transport.Grpc.Security; + +[NonParallelizable] +[TestFixture(typeof(LogFormat.V2), typeof(string))] +public class delete_stream_security : AuthenticationTestBase +{ + public static IEnumerable DeleteCases() + { + yield return Case(StreamKind.User, StreamAclKind.None, SecurityIdentity.Anonymous, StatusCode.OK, + "deleting_normal_no_acl_stream_with_no_user_is_allowed"); + yield return Case(StreamKind.User, StreamAclKind.None, SecurityIdentity.UserOne, StatusCode.OK, + "deleting_normal_no_acl_stream_with_existing_user_is_allowed"); + yield return Case(StreamKind.User, StreamAclKind.None, SecurityIdentity.Admin, StatusCode.OK, + "deleting_normal_no_acl_stream_with_admin_user_is_allowed"); + yield return Case(StreamKind.User, StreamAclKind.UserOne, SecurityIdentity.Anonymous, StatusCode.PermissionDenied, + "deleting_normal_user_stream_with_no_user_is_not_allowed"); + yield return Case(StreamKind.User, StreamAclKind.UserOne, SecurityIdentity.UserTwo, StatusCode.PermissionDenied, + "deleting_normal_user_stream_with_not_authorized_user_is_not_allowed"); + yield return Case(StreamKind.User, StreamAclKind.UserOne, SecurityIdentity.UserOne, StatusCode.OK, + "deleting_normal_user_stream_with_authorized_user_is_allowed"); + yield return Case(StreamKind.User, StreamAclKind.UserOne, SecurityIdentity.Admin, StatusCode.OK, + "deleting_normal_user_stream_with_admin_user_is_allowed"); + yield return Case(StreamKind.User, StreamAclKind.Admins, SecurityIdentity.Anonymous, StatusCode.PermissionDenied, + "deleting_normal_admin_stream_with_no_user_is_not_allowed"); + yield return Case(StreamKind.User, StreamAclKind.Admins, SecurityIdentity.UserOne, StatusCode.PermissionDenied, + "deleting_normal_admin_stream_with_existing_user_is_not_allowed"); + yield return Case(StreamKind.User, StreamAclKind.Admins, SecurityIdentity.Admin, StatusCode.OK, + "deleting_normal_admin_stream_with_admin_user_is_allowed"); + yield return Case(StreamKind.User, StreamAclKind.All, SecurityIdentity.Anonymous, StatusCode.OK, + "deleting_normal_all_stream_with_no_user_is_allowed"); + yield return Case(StreamKind.User, StreamAclKind.All, SecurityIdentity.UserOne, StatusCode.OK, + "deleting_normal_all_stream_with_existing_user_is_allowed"); + yield return Case(StreamKind.User, StreamAclKind.All, SecurityIdentity.Admin, StatusCode.OK, + "deleting_normal_all_stream_with_admin_user_is_allowed"); + yield return Case(StreamKind.System, StreamAclKind.None, SecurityIdentity.Anonymous, StatusCode.PermissionDenied, + "deleting_system_no_acl_stream_with_no_user_is_not_allowed"); + yield return Case(StreamKind.System, StreamAclKind.None, SecurityIdentity.UserOne, StatusCode.PermissionDenied, + "deleting_system_no_acl_stream_with_existing_user_is_not_allowed"); + yield return Case(StreamKind.System, StreamAclKind.None, SecurityIdentity.Admin, StatusCode.OK, + "deleting_system_no_acl_stream_with_admin_user_is_allowed"); + yield return Case(StreamKind.System, StreamAclKind.UserOne, SecurityIdentity.Anonymous, StatusCode.PermissionDenied, + "deleting_system_user_stream_with_no_user_is_not_allowed"); + yield return Case(StreamKind.System, StreamAclKind.UserOne, SecurityIdentity.UserTwo, StatusCode.PermissionDenied, + "deleting_system_user_stream_with_not_authorized_user_is_not_allowed"); + yield return Case(StreamKind.System, StreamAclKind.UserOne, SecurityIdentity.UserOne, StatusCode.OK, + "deleting_system_user_stream_with_authorized_user_is_allowed"); + yield return Case(StreamKind.System, StreamAclKind.UserOne, SecurityIdentity.Admin, StatusCode.OK, + "deleting_system_user_stream_with_admin_user_is_allowed"); + yield return Case(StreamKind.System, StreamAclKind.Admins, SecurityIdentity.Anonymous, StatusCode.PermissionDenied, + "deleting_system_admin_stream_with_no_user_is_not_allowed"); + yield return Case(StreamKind.System, StreamAclKind.Admins, SecurityIdentity.UserOne, StatusCode.PermissionDenied, + "deleting_system_admin_stream_with_existing_user_is_not_allowed"); + yield return Case(StreamKind.System, StreamAclKind.Admins, SecurityIdentity.Admin, StatusCode.OK, + "deleting_system_admin_stream_with_admin_user_is_allowed"); + yield return Case(StreamKind.System, StreamAclKind.All, SecurityIdentity.Anonymous, StatusCode.OK, + "deleting_system_all_stream_with_no_user_is_allowed"); + yield return Case(StreamKind.System, StreamAclKind.All, SecurityIdentity.UserOne, StatusCode.OK, + "deleting_system_all_stream_with_existing_user_is_allowed"); + yield return Case(StreamKind.System, StreamAclKind.All, SecurityIdentity.Admin, StatusCode.OK, + "deleting_system_all_stream_with_admin_user_is_allowed"); + } + + [Test] + public async Task delete_of_all_is_never_allowed() + { + AssertStatus(StatusCode.PermissionDenied, + await ExecuteOperation(SecurityOperation.Tombstone, SystemStreams.AllStream, SecurityIdentity.Anonymous)); + AssertStatus(StatusCode.PermissionDenied, + await ExecuteOperation(SecurityOperation.Tombstone, SystemStreams.AllStream, SecurityIdentity.UserOne)); + AssertStatus(StatusCode.PermissionDenied, + await ExecuteOperation(SecurityOperation.Tombstone, SystemStreams.AllStream, SecurityIdentity.Admin)); + } + + [TestCaseSource(nameof(DeleteCases))] + public async Task deleting_stream_obeys_delete_acl( + StreamKind streamKind, + StreamAclKind acl, + SecurityIdentity identity, + StatusCode expectedStatus) + { + var streamName = await CreateStreamWithDeleteAcl(streamKind, acl); + AssertStatus(expectedStatus, + await ExecuteOperation(SecurityOperation.Tombstone, streamName, identity)); + } + + private async Task CreateStreamWithDeleteAcl(StreamKind streamKind, StreamAclKind acl) + { + var prefix = streamKind == StreamKind.System ? "$" : string.Empty; + var streamName = $"{prefix}grpc-delete-security-{Guid.NewGuid():N}"; + var metadata = acl switch + { + StreamAclKind.None => "{}", + StreamAclKind.UserOne => DeleteAclJson(UserOneName), + StreamAclKind.Admins => DeleteAclJson(SystemRoles.Admins), + StreamAclKind.All => DeleteAclJson(SystemRoles.All), + _ => throw new ArgumentOutOfRangeException(nameof(acl), acl, null) + }; + await SetStreamAcl(streamName, metadata); + return streamName; + } + + private static TestCaseData Case( + StreamKind streamKind, + StreamAclKind acl, + SecurityIdentity identity, + StatusCode expectedStatus, + string testName) => + new TestCaseData(streamKind, acl, identity, expectedStatus).SetName(testName); + + private static string DeleteAclJson(string role) => $"{{\"$acl\":{{\"$d\":\"{role}\"}}}}"; +} diff --git a/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/multiple_role_security.cs b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/multiple_role_security.cs new file mode 100644 index 0000000000..c7ac246f28 --- /dev/null +++ b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/multiple_role_security.cs @@ -0,0 +1,46 @@ +using System.Threading.Tasks; +using Grpc.Core; +using NUnit.Framework; + +namespace EventStore.Core.Tests.Services.Transport.Grpc.Security; + +[NonParallelizable] +[TestFixture(typeof(LogFormat.V2), typeof(string))] +public class multiple_role_security : AuthenticationTestBase +{ + protected override async Task Given() + { + await base.Given(); + await SetSystemSettings( + "{\"$userStreamAcl\":{\"$r\":[\"user1\",\"user2\"],\"$w\":[\"$admins\",\"user1\"],\"$d\":[\"user1\",\"$all\"]}}"); + } + + [Test] + public async Task multiple_roles_are_handled_correctly() + { + await AssertOperation(SecurityOperation.ReadEvent, "usr-stream", SecurityIdentity.Anonymous, + StatusCode.PermissionDenied); + await AssertOperation(SecurityOperation.ReadEvent, "usr-stream", SecurityIdentity.UserOne, StatusCode.OK); + await AssertOperation(SecurityOperation.ReadEvent, "usr-stream", SecurityIdentity.UserTwo, StatusCode.OK); + await AssertOperation(SecurityOperation.ReadEvent, "usr-stream", SecurityIdentity.Admin, StatusCode.OK); + + await AssertOperation(SecurityOperation.Write, "usr-stream", SecurityIdentity.Anonymous, + StatusCode.PermissionDenied); + await AssertOperation(SecurityOperation.Write, "usr-stream", SecurityIdentity.UserOne, StatusCode.OK); + await AssertOperation(SecurityOperation.Write, "usr-stream", SecurityIdentity.UserTwo, + StatusCode.PermissionDenied); + await AssertOperation(SecurityOperation.Write, "usr-stream", SecurityIdentity.Admin, StatusCode.OK); + + await AssertOperation(SecurityOperation.Tombstone, "usr-stream1", SecurityIdentity.Anonymous, StatusCode.OK); + await AssertOperation(SecurityOperation.Tombstone, "usr-stream2", SecurityIdentity.UserOne, StatusCode.OK); + await AssertOperation(SecurityOperation.Tombstone, "usr-stream3", SecurityIdentity.UserTwo, StatusCode.OK); + await AssertOperation(SecurityOperation.Tombstone, "usr-stream4", SecurityIdentity.Admin, StatusCode.OK); + } + + private async Task AssertOperation( + SecurityOperation operation, + string streamName, + SecurityIdentity identity, + StatusCode expectedStatus) => + AssertStatus(expectedStatus, await ExecuteOperation(operation, streamName, identity)); +} diff --git a/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/overriden_system_stream_security.cs b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/overriden_system_stream_security.cs new file mode 100644 index 0000000000..4963be3cc1 --- /dev/null +++ b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/overriden_system_stream_security.cs @@ -0,0 +1,41 @@ +using System.Threading.Tasks; +using Grpc.Core; +using NUnit.Framework; + +namespace EventStore.Core.Tests.Services.Transport.Grpc.Security; + +[NonParallelizable] +[TestFixture(typeof(LogFormat.V2), typeof(string))] +public class overriden_system_stream_security : AuthenticationTestBase +{ + protected override async Task Given() + { + await base.Given(); + await SetDefaultAcl(StreamKind.System, StreamAclKind.UserOne); + } + + [Test] + public async Task operations_on_system_stream_succeed_for_authorized_user() => + await AssertOperations("$sys-authorized-user", SecurityIdentity.UserOne, StatusCode.OK); + + [Test] + public async Task operations_on_system_stream_fail_for_not_authorized_user() => + await AssertOperations("$sys-not-authorized-user", SecurityIdentity.UserTwo, StatusCode.PermissionDenied); + + [Test] + public async Task operations_on_system_stream_fail_for_anonymous_user() => + await AssertOperations("$sys-anonymous-user", SecurityIdentity.Anonymous, StatusCode.PermissionDenied); + + [Test] + public async Task operations_on_system_stream_succeed_for_admin() => + await AssertOperations("$sys-admin", SecurityIdentity.Admin, StatusCode.OK); + + private async Task AssertOperations( + string streamName, + SecurityIdentity identity, + StatusCode expectedStatus) + { + AssertStatus(expectedStatus, await ExecuteBatchAppend(streamName, identity)); + await AssertAllStreamOperations(streamName, identity, expectedStatus); + } +} diff --git a/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/overriden_system_stream_security_for_all.cs b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/overriden_system_stream_security_for_all.cs new file mode 100644 index 0000000000..e7ba96b8b8 --- /dev/null +++ b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/overriden_system_stream_security_for_all.cs @@ -0,0 +1,34 @@ +using System.Threading.Tasks; +using Grpc.Core; +using NUnit.Framework; + +namespace EventStore.Core.Tests.Services.Transport.Grpc.Security; + +[NonParallelizable] +[TestFixture(typeof(LogFormat.V2), typeof(string))] +public class overriden_system_stream_security_for_all : AuthenticationTestBase +{ + protected override async Task Given() + { + await base.Given(); + await SetDefaultAcl(StreamKind.System, StreamAclKind.All); + } + + [Test] + public async Task operations_on_system_stream_succeeds_for_user() => + await AssertOperations("$sys-authorized-user", SecurityIdentity.UserOne); + + [Test] + public async Task operations_on_system_stream_fail_for_anonymous_user() => + await AssertOperations("$sys-anonymous-user", SecurityIdentity.Anonymous); + + [Test] + public async Task operations_on_system_stream_succeed_for_admin() => + await AssertOperations("$sys-admin", SecurityIdentity.Admin); + + private async Task AssertOperations(string streamName, SecurityIdentity identity) + { + AssertStatus(StatusCode.OK, await ExecuteBatchAppend(streamName, identity)); + await AssertAllStreamOperations(streamName, identity, StatusCode.OK); + } +} diff --git a/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/overriden_user_stream_security.cs b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/overriden_user_stream_security.cs new file mode 100644 index 0000000000..c62bab35e5 --- /dev/null +++ b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/overriden_user_stream_security.cs @@ -0,0 +1,41 @@ +using System.Threading.Tasks; +using Grpc.Core; +using NUnit.Framework; + +namespace EventStore.Core.Tests.Services.Transport.Grpc.Security; + +[NonParallelizable] +[TestFixture(typeof(LogFormat.V2), typeof(string))] +public class overriden_user_stream_security : AuthenticationTestBase +{ + protected override async Task Given() + { + await base.Given(); + await SetDefaultAcl(StreamKind.User, StreamAclKind.UserOne); + } + + [Test] + public async Task operations_on_user_stream_succeeds_for_authorized_user() => + await AssertOperations("user-authorized-user", SecurityIdentity.UserOne, StatusCode.OK); + + [Test] + public async Task operations_on_user_stream_fail_for_not_authorized_user() => + await AssertOperations("user-not-authorized", SecurityIdentity.UserTwo, StatusCode.PermissionDenied); + + [Test] + public async Task operations_on_user_stream_fail_for_anonymous_user() => + await AssertOperations("user-anonymous-user", SecurityIdentity.Anonymous, StatusCode.PermissionDenied); + + [Test] + public async Task operations_on_user_stream_succeed_for_admin() => + await AssertOperations("user-admin", SecurityIdentity.Admin, StatusCode.OK); + + private async Task AssertOperations( + string streamName, + SecurityIdentity identity, + StatusCode expectedStatus) + { + AssertStatus(expectedStatus, await ExecuteBatchAppend(streamName, identity)); + await AssertAllStreamOperations(streamName, identity, expectedStatus); + } +} diff --git a/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/read_all_security.cs b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/read_all_security.cs new file mode 100644 index 0000000000..c1f62dcef9 --- /dev/null +++ b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/read_all_security.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using EventStore.Core.Services; +using Grpc.Core; +using NUnit.Framework; + +namespace EventStore.Core.Tests.Services.Transport.Grpc.Security; + +[NonParallelizable] +[TestFixture(typeof(LogFormat.V2), typeof(string))] +public class read_all_security : AuthenticationTestBase +{ + public static IEnumerable Cases() + { + yield return Case(SecurityIdentity.Invalid, StatusCode.Unauthenticated, + "reading_all_with_not_existing_credentials_is_not_authenticated"); + yield return Case(SecurityIdentity.Anonymous, StatusCode.PermissionDenied, + "reading_all_with_no_credentials_is_denied"); + yield return Case(SecurityIdentity.UserTwo, StatusCode.PermissionDenied, + "reading_all_with_not_authorized_user_credentials_is_denied"); + yield return Case(SecurityIdentity.UserOne, StatusCode.OK, + "reading_all_with_authorized_user_credentials_succeeds"); + yield return Case(SecurityIdentity.Admin, StatusCode.OK, + "reading_all_with_admin_credentials_succeeds"); + } + + [TestCaseSource(nameof(Cases))] + public async Task enforces_read_all_security(SecurityIdentity identity, StatusCode expectedStatus) + { + await SetStreamAcl(SystemStreams.AllStream, StreamAclKind.UserOne); + + AssertStatus(expectedStatus, await ExecuteAllOperation(false, identity)); + AssertStatus(expectedStatus, await ExecuteAllOperation(false, identity, backwards: true)); + } + + private static TestCaseData Case(SecurityIdentity identity, StatusCode status, string name) => + new TestCaseData(identity, status).SetName(name); +} diff --git a/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/read_stream_meta_security.cs b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/read_stream_meta_security.cs new file mode 100644 index 0000000000..77f24e9bcb --- /dev/null +++ b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/read_stream_meta_security.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Grpc.Core; +using NUnit.Framework; + +namespace EventStore.Core.Tests.Services.Transport.Grpc.Security; + +[NonParallelizable] +[TestFixture(typeof(LogFormat.V2), typeof(string))] +public class read_stream_meta_security : AuthenticationTestBase +{ + public static IEnumerable Cases() + { + yield return Case(StreamAclKind.UserOne, StatusCode.Unauthenticated, + "reading_stream_meta_with_not_existing_credentials_is_not_authenticated", SecurityIdentity.Invalid); + yield return Case(StreamAclKind.UserOne, StatusCode.PermissionDenied, + "reading_stream_meta_with_no_credentials_is_denied", SecurityIdentity.Anonymous); + yield return Case(StreamAclKind.UserOne, StatusCode.PermissionDenied, + "reading_stream_meta_with_not_authorized_user_credentials_is_denied", SecurityIdentity.UserTwo); + yield return Case(StreamAclKind.UserOne, StatusCode.OK, + "reading_stream_meta_with_authorized_user_credentials_succeeds", SecurityIdentity.UserOne); + yield return Case(StreamAclKind.UserOne, StatusCode.OK, + "reading_stream_meta_with_admin_user_credentials_succeeds", SecurityIdentity.Admin); + + yield return Case(StreamAclKind.None, StatusCode.OK, + "reading_no_acl_stream_meta_succeeds_when_no_credentials_are_passed", SecurityIdentity.Anonymous); + yield return Case(StreamAclKind.None, StatusCode.Unauthenticated, + "reading_no_acl_stream_meta_is_not_authenticated_when_not_existing_credentials_are_passed", + SecurityIdentity.Invalid); + yield return Case(StreamAclKind.None, StatusCode.OK, + "reading_no_acl_stream_meta_succeeds_when_any_existing_user_credentials_are_passed", + SecurityIdentity.UserOne, SecurityIdentity.UserTwo); + yield return Case(StreamAclKind.None, StatusCode.OK, + "reading_no_acl_stream_meta_succeeds_when_admin_user_credentials_are_passed", SecurityIdentity.Admin); + + yield return Case(StreamAclKind.All, StatusCode.OK, + "reading_all_access_normal_stream_meta_succeeds_when_no_credentials_are_passed", + SecurityIdentity.Anonymous); + yield return Case(StreamAclKind.All, StatusCode.Unauthenticated, + "reading_all_access_normal_stream_meta_is_not_authenticated_when_not_existing_credentials_are_passed", + SecurityIdentity.Invalid); + yield return Case(StreamAclKind.All, StatusCode.OK, + "reading_all_access_normal_stream_meta_succeeds_when_any_existing_user_credentials_are_passed", + SecurityIdentity.UserOne, SecurityIdentity.UserTwo); + yield return Case(StreamAclKind.All, StatusCode.OK, + "reading_all_access_normal_stream_meta_succeeds_when_admin_user_credentials_are_passed", + SecurityIdentity.Admin); + } + + [TestCaseSource(nameof(Cases))] + public async Task enforces_stream_metadata_read_security( + StreamAclKind acl, + StatusCode expectedStatus, + SecurityIdentity[] identities) + { + foreach (var identity in identities) + { + var streamName = await CreateStreamWithAcl(StreamKind.User, acl); + AssertStatus(expectedStatus, + await ExecuteOperation(SecurityOperation.ReadMetadata, streamName, identity)); + } + } + + private static TestCaseData Case( + StreamAclKind acl, + StatusCode status, + string name, + params SecurityIdentity[] identities) => + new TestCaseData(acl, status, identities).SetName(name); +} diff --git a/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/read_stream_security.cs b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/read_stream_security.cs new file mode 100644 index 0000000000..536e73d318 --- /dev/null +++ b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/read_stream_security.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Grpc.Core; +using NUnit.Framework; + +namespace EventStore.Core.Tests.Services.Transport.Grpc.Security; + +[NonParallelizable] +[TestFixture(typeof(LogFormat.V2), typeof(string))] +public class read_stream_security : AuthenticationTestBase +{ + public static IEnumerable Cases() + { + yield return Case(StreamAclKind.UserOne, StatusCode.Unauthenticated, + "reading_stream_with_not_existing_credentials_is_not_authenticated", SecurityIdentity.Invalid); + yield return Case(StreamAclKind.UserOne, StatusCode.PermissionDenied, + "reading_stream_with_no_credentials_is_denied", SecurityIdentity.Anonymous); + yield return Case(StreamAclKind.UserOne, StatusCode.PermissionDenied, + "reading_stream_with_not_authorized_user_credentials_is_denied", SecurityIdentity.UserTwo); + yield return Case(StreamAclKind.UserOne, StatusCode.OK, + "reading_stream_with_authorized_user_credentials_succeeds", SecurityIdentity.UserOne); + yield return Case(StreamAclKind.UserOne, StatusCode.OK, + "reading_stream_with_admin_user_credentials_succeeds", SecurityIdentity.Admin); + + yield return Case(StreamAclKind.None, StatusCode.OK, + "reading_no_acl_stream_succeeds_when_no_credentials_are_passed", SecurityIdentity.Anonymous); + yield return Case(StreamAclKind.None, StatusCode.Unauthenticated, + "reading_no_acl_stream_is_not_authenticated_when_not_existing_credentials_are_passed", + SecurityIdentity.Invalid); + yield return Case(StreamAclKind.None, StatusCode.OK, + "reading_no_acl_stream_succeeds_when_any_existing_user_credentials_are_passed", + SecurityIdentity.UserOne, SecurityIdentity.UserTwo); + yield return Case(StreamAclKind.None, StatusCode.OK, + "reading_no_acl_stream_succeeds_when_admin_user_credentials_are_passed", SecurityIdentity.Admin); + + yield return Case(StreamAclKind.All, StatusCode.OK, + "reading_all_access_normal_stream_succeeds_when_no_credentials_are_passed", SecurityIdentity.Anonymous); + yield return Case(StreamAclKind.All, StatusCode.Unauthenticated, + "reading_all_access_normal_stream_is_not_authenticated_when_not_existing_credentials_are_passed", + SecurityIdentity.Invalid); + yield return Case(StreamAclKind.All, StatusCode.OK, + "reading_all_access_normal_stream_succeeds_when_any_existing_user_credentials_are_passed", + SecurityIdentity.UserOne, SecurityIdentity.UserTwo); + yield return Case(StreamAclKind.All, StatusCode.OK, + "reading_all_access_normal_stream_succeeds_when_admin_user_credentials_are_passed", SecurityIdentity.Admin); + } + + [TestCaseSource(nameof(Cases))] + public async Task enforces_stream_read_security( + StreamAclKind acl, + StatusCode expectedStatus, + SecurityIdentity[] identities) + { + foreach (var identity in identities) + { + var streamName = await CreateStreamWithAcl(StreamKind.User, acl); + AssertStatus(expectedStatus, await ExecuteOperation(SecurityOperation.ReadEvent, streamName, identity)); + AssertStatus(expectedStatus, await ExecuteOperation(SecurityOperation.ReadForward, streamName, identity)); + AssertStatus(expectedStatus, await ExecuteOperation(SecurityOperation.ReadBackward, streamName, identity)); + } + } + + private static TestCaseData Case( + StreamAclKind acl, + StatusCode status, + string name, + params SecurityIdentity[] identities) => + new TestCaseData(acl, status, identities).SetName(name); +} diff --git a/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/stream_security_inheritance.cs b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/stream_security_inheritance.cs new file mode 100644 index 0000000000..0ffa513369 --- /dev/null +++ b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/stream_security_inheritance.cs @@ -0,0 +1,120 @@ +using System; +using System.Threading.Tasks; +using EventStore.Core.Services; +using Grpc.Core; +using NUnit.Framework; + +namespace EventStore.Core.Tests.Services.Transport.Grpc.Security; + +[NonParallelizable] +[TestFixture(typeof(LogFormat.V2), typeof(string))] +public class stream_security_inheritance : AuthenticationTestBase +{ + [Test] + public async Task acl_inheritance_is_working_properly_on_user_streams() + { + await SetWriteDefaults(); + + var inherited = StreamName(); + await AssertOperation(inherited, SecurityOperation.Write, StatusCode.PermissionDenied, + SecurityIdentity.Anonymous, SecurityIdentity.UserTwo); + await AssertOperation(inherited, SecurityOperation.Write, StatusCode.OK, + SecurityIdentity.UserOne, SecurityIdentity.Admin); + + var differentRole = StreamName(); + await SetStreamAcl(differentRole, WriteAclJson(UserTwoName)); + await AssertOperation(differentRole, SecurityOperation.Write, StatusCode.PermissionDenied, + SecurityIdentity.Anonymous, SecurityIdentity.UserOne); + await AssertOperation(differentRole, SecurityOperation.Write, StatusCode.OK, + SecurityIdentity.UserTwo, SecurityIdentity.Admin); + + var multipleRoles = StreamName(); + await SetStreamAcl(multipleRoles, WriteAclJson(UserOneName, UserTwoName)); + await AssertOperation(multipleRoles, SecurityOperation.Write, StatusCode.PermissionDenied, + SecurityIdentity.Anonymous); + await AssertOperation(multipleRoles, SecurityOperation.Write, StatusCode.OK, + SecurityIdentity.UserOne, SecurityIdentity.UserTwo, SecurityIdentity.Admin); + + var restricted = StreamName(); + await SetStreamAcl(restricted, WriteAclJson()); + await AssertOperation(restricted, SecurityOperation.Write, StatusCode.PermissionDenied, + SecurityIdentity.Anonymous, SecurityIdentity.UserOne, SecurityIdentity.UserTwo); + await AssertOperation(restricted, SecurityOperation.Write, StatusCode.OK, SecurityIdentity.Admin); + + var all = StreamName(); + await SetStreamAcl(all, WriteAclJson(SystemRoles.All)); + await AssertOperation(all, SecurityOperation.Write, StatusCode.OK, + SecurityIdentity.Anonymous, SecurityIdentity.UserOne, SecurityIdentity.UserTwo, SecurityIdentity.Admin); + + await AssertOperation(inherited, SecurityOperation.ReadEvent, StatusCode.OK, + SecurityIdentity.Anonymous, SecurityIdentity.UserOne, SecurityIdentity.UserTwo, SecurityIdentity.Admin); + + var readRestricted = StreamName(); + await SetStreamAcl(readRestricted, $"{{\"$acl\":{{\"$r\":\"{UserOneName}\"}}}}"); + await AssertOperation(readRestricted, SecurityOperation.Write, StatusCode.OK, SecurityIdentity.UserOne); + await AssertOperation(readRestricted, SecurityOperation.ReadEvent, StatusCode.PermissionDenied, + SecurityIdentity.Anonymous, SecurityIdentity.UserTwo); + await AssertOperation(readRestricted, SecurityOperation.ReadEvent, StatusCode.OK, + SecurityIdentity.UserOne, SecurityIdentity.Admin); + } + + [Test] + public async Task acl_inheritance_is_working_properly_on_system_streams() + { + await SetWriteDefaults(); + + var inherited = StreamName(system: true); + await AssertOperation(inherited, SecurityOperation.Write, StatusCode.PermissionDenied, + SecurityIdentity.Anonymous, SecurityIdentity.UserTwo); + await AssertOperation(inherited, SecurityOperation.Write, StatusCode.OK, + SecurityIdentity.UserOne, SecurityIdentity.Admin); + + var differentRole = StreamName(system: true); + await SetStreamAcl(differentRole, WriteAclJson(UserTwoName)); + await AssertOperation(differentRole, SecurityOperation.Write, StatusCode.PermissionDenied, + SecurityIdentity.Anonymous, SecurityIdentity.UserOne); + await AssertOperation(differentRole, SecurityOperation.Write, StatusCode.OK, + SecurityIdentity.UserTwo, SecurityIdentity.Admin); + + var multipleRoles = StreamName(system: true); + await SetStreamAcl(multipleRoles, WriteAclJson(UserOneName, UserTwoName)); + await AssertOperation(multipleRoles, SecurityOperation.Write, StatusCode.PermissionDenied, + SecurityIdentity.Anonymous); + await AssertOperation(multipleRoles, SecurityOperation.Write, StatusCode.OK, + SecurityIdentity.UserOne, SecurityIdentity.UserTwo, SecurityIdentity.Admin); + + var restricted = StreamName(system: true); + await SetStreamAcl(restricted, WriteAclJson()); + await AssertOperation(restricted, SecurityOperation.Write, StatusCode.PermissionDenied, + SecurityIdentity.Anonymous, SecurityIdentity.UserOne, SecurityIdentity.UserTwo); + await AssertOperation(restricted, SecurityOperation.Write, StatusCode.OK, SecurityIdentity.Admin); + + var all = StreamName(system: true); + await SetStreamAcl(all, WriteAclJson(SystemRoles.All)); + await AssertOperation(all, SecurityOperation.Write, StatusCode.OK, + SecurityIdentity.Anonymous, SecurityIdentity.UserOne, SecurityIdentity.UserTwo, SecurityIdentity.Admin); + + await AssertOperation(inherited, SecurityOperation.ReadEvent, StatusCode.PermissionDenied, + SecurityIdentity.Anonymous, SecurityIdentity.UserOne, SecurityIdentity.UserTwo); + await AssertOperation(inherited, SecurityOperation.ReadEvent, StatusCode.OK, SecurityIdentity.Admin); + } + + private async Task SetWriteDefaults() => + await SetSystemSettings( + $"{{\"$userStreamAcl\":{{\"$w\":\"{UserOneName}\"}},\"$systemStreamAcl\":{{\"$w\":\"{UserOneName}\"}}}}"); + + private async Task AssertOperation( + string streamName, + SecurityOperation operation, + StatusCode expectedStatus, + params SecurityIdentity[] identities) + { + foreach (var identity in identities) + { + AssertStatus(expectedStatus, await ExecuteOperation(operation, streamName, identity)); + } + } + + private static string StreamName(bool system = false) => + $"{(system ? "$" : string.Empty)}grpc-security-inheritance-{Guid.NewGuid():N}"; +} diff --git a/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/subscribe_to_all_security.cs b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/subscribe_to_all_security.cs new file mode 100644 index 0000000000..a75eca41ac --- /dev/null +++ b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/subscribe_to_all_security.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using EventStore.Core.Services; +using Grpc.Core; +using NUnit.Framework; + +namespace EventStore.Core.Tests.Services.Transport.Grpc.Security; + +[NonParallelizable] +[TestFixture(typeof(LogFormat.V2), typeof(string))] +public class subscribe_to_all_security : AuthenticationTestBase +{ + public static IEnumerable Cases() + { + yield return Case(SecurityIdentity.Invalid, StatusCode.Unauthenticated, + "subscribing_to_all_with_not_existing_credentials_is_not_authenticated"); + yield return Case(SecurityIdentity.Anonymous, StatusCode.PermissionDenied, + "subscribing_to_all_with_no_credentials_is_denied"); + yield return Case(SecurityIdentity.UserTwo, StatusCode.PermissionDenied, + "subscribing_to_all_with_not_authorized_user_credentials_is_denied"); + yield return Case(SecurityIdentity.UserOne, StatusCode.OK, + "subscribing_to_all_with_authorized_user_credentials_succeeds"); + yield return Case(SecurityIdentity.Admin, StatusCode.OK, + "subscribing_to_all_with_admin_user_credentials_succeeds"); + } + + [TestCaseSource(nameof(Cases))] + public async Task enforces_subscribe_to_all_security(SecurityIdentity identity, StatusCode expectedStatus) + { + await SetStreamAcl(SystemStreams.AllStream, StreamAclKind.UserOne); + AssertStatus(expectedStatus, await ExecuteAllOperation(true, identity)); + } + + private static TestCaseData Case(SecurityIdentity identity, StatusCode status, string name) => + new TestCaseData(identity, status).SetName(name); +} diff --git a/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/subscribe_to_stream_security.cs b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/subscribe_to_stream_security.cs new file mode 100644 index 0000000000..e52661ca48 --- /dev/null +++ b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/subscribe_to_stream_security.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Grpc.Core; +using NUnit.Framework; + +namespace EventStore.Core.Tests.Services.Transport.Grpc.Security; + +[NonParallelizable] +[TestFixture(typeof(LogFormat.V2), typeof(string))] +public class subscribe_to_stream_security : AuthenticationTestBase +{ + public static IEnumerable Cases() + { + yield return Case(StreamAclKind.UserOne, StatusCode.Unauthenticated, + "subscribing_to_stream_with_not_existing_credentials_is_not_authenticated", SecurityIdentity.Invalid); + yield return Case(StreamAclKind.UserOne, StatusCode.PermissionDenied, + "subscribing_to_stream_with_no_credentials_is_denied", SecurityIdentity.Anonymous); + yield return Case(StreamAclKind.UserOne, StatusCode.PermissionDenied, + "subscribing_to_stream_with_not_authorized_user_credentials_is_denied", SecurityIdentity.UserTwo); + yield return Case(StreamAclKind.UserOne, StatusCode.OK, + "reading_stream_with_authorized_user_credentials_succeeds", SecurityIdentity.UserOne); + yield return Case(StreamAclKind.UserOne, StatusCode.OK, + "reading_stream_with_admin_user_credentials_succeeds", SecurityIdentity.Admin); + + yield return Case(StreamAclKind.None, StatusCode.OK, + "subscribing_to_no_acl_stream_succeeds_when_no_credentials_are_passed", SecurityIdentity.Anonymous); + yield return Case(StreamAclKind.None, StatusCode.Unauthenticated, + "subscribing_to_no_acl_stream_is_not_authenticated_when_not_existing_credentials_are_passed", + SecurityIdentity.Invalid); + yield return Case(StreamAclKind.None, StatusCode.OK, + "subscribing_to_no_acl_stream_succeeds_when_any_existing_user_credentials_are_passed", + SecurityIdentity.UserOne, SecurityIdentity.UserTwo); + yield return Case(StreamAclKind.None, StatusCode.OK, + "subscribing_to_no_acl_stream_succeeds_when_admin_user_credentials_are_passed", SecurityIdentity.Admin); + + yield return Case(StreamAclKind.All, StatusCode.OK, + "subscribing_to_all_access_normal_stream_succeeds_when_no_credentials_are_passed", + SecurityIdentity.Anonymous); + yield return Case(StreamAclKind.All, StatusCode.Unauthenticated, + "subscribing_to_all_access_normal_stream_is_not_authenticated_when_not_existing_credentials_are_passed", + SecurityIdentity.Invalid); + yield return Case(StreamAclKind.All, StatusCode.OK, + "subscribing_to_all_access_normal_stream_succeeds_when_any_existing_user_credentials_are_passed", + SecurityIdentity.UserOne, SecurityIdentity.UserTwo); + yield return Case(StreamAclKind.All, StatusCode.OK, + "subscribing_to_all_access_normal_streamm_succeeds_when_admin_user_credentials_are_passed", + SecurityIdentity.Admin); + } + + [TestCaseSource(nameof(Cases))] + public async Task enforces_stream_subscription_security( + StreamAclKind acl, + StatusCode expectedStatus, + SecurityIdentity[] identities) + { + foreach (var identity in identities) + { + var streamName = await CreateStreamWithAcl(StreamKind.User, acl); + AssertStatus(expectedStatus, await ExecuteOperation(SecurityOperation.Subscribe, streamName, identity)); + } + } + + private static TestCaseData Case( + StreamAclKind acl, + StatusCode status, + string name, + params SecurityIdentity[] identities) => + new TestCaseData(acl, status, identities).SetName(name); +} diff --git a/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/system_stream_security.cs b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/system_stream_security.cs new file mode 100644 index 0000000000..aacf6d15a9 --- /dev/null +++ b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/system_stream_security.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Grpc.Core; +using NUnit.Framework; + +namespace EventStore.Core.Tests.Services.Transport.Grpc.Security; + +[NonParallelizable] +[TestFixture(typeof(LogFormat.V2), typeof(string))] +public class system_stream_security : AuthenticationTestBase +{ + public static IEnumerable Cases() + { + yield return Case(StreamAclKind.None, StatusCode.PermissionDenied, + "operations_on_system_stream_with_no_acl_set_fail_for_non_admin", + SecurityIdentity.Anonymous, SecurityIdentity.UserOne, SecurityIdentity.UserTwo); + yield return Case(StreamAclKind.None, StatusCode.OK, + "operations_on_system_stream_with_no_acl_set_succeed_for_admin", SecurityIdentity.Admin); + + yield return Case(StreamAclKind.UserOne, StatusCode.PermissionDenied, + "operations_on_system_stream_with_acl_set_to_usual_user_fail_for_not_authorized_user", + SecurityIdentity.Anonymous, SecurityIdentity.UserTwo); + yield return Case(StreamAclKind.UserOne, StatusCode.OK, + "operations_on_system_stream_with_acl_set_to_usual_user_succeed_for_that_user", SecurityIdentity.UserOne); + yield return Case(StreamAclKind.UserOne, StatusCode.OK, + "operations_on_system_stream_with_acl_set_to_usual_user_succeed_for_admin", SecurityIdentity.Admin); + + yield return Case(StreamAclKind.Admins, StatusCode.PermissionDenied, + "operations_on_system_stream_with_acl_set_to_admins_fail_for_usual_user", + SecurityIdentity.Anonymous, SecurityIdentity.UserOne, SecurityIdentity.UserTwo); + yield return Case(StreamAclKind.Admins, StatusCode.OK, + "operations_on_system_stream_with_acl_set_to_admins_succeed_for_admin", SecurityIdentity.Admin); + + yield return Case(StreamAclKind.All, StatusCode.OK, + "operations_on_system_stream_with_acl_set_to_all_succeed_for_not_authenticated_user", + SecurityIdentity.Anonymous); + yield return Case(StreamAclKind.All, StatusCode.OK, + "operations_on_system_stream_with_acl_set_to_all_succeed_for_usual_user", + SecurityIdentity.UserOne, SecurityIdentity.UserTwo); + yield return Case(StreamAclKind.All, StatusCode.OK, + "operations_on_system_stream_with_acl_set_to_all_succeed_for_admin", SecurityIdentity.Admin); + + foreach (var acl in new[] + { + StreamAclKind.None, + StreamAclKind.UserOne, + StreamAclKind.Admins, + StreamAclKind.All + }) + { + yield return Case(acl, StatusCode.Unauthenticated, + $"operations_on_system_stream_with_{acl.ToString().ToLowerInvariant()}_acl_reject_invalid_credentials", + SecurityIdentity.Invalid); + } + } + + [TestCaseSource(nameof(Cases))] + public async Task enforces_system_stream_security( + StreamAclKind acl, + StatusCode expectedStatus, + SecurityIdentity[] identities) + { + foreach (var identity in identities) + { + await AssertAllStreamOperations(StreamKind.System, acl, identity, expectedStatus); + } + } + + private static TestCaseData Case( + StreamAclKind acl, + StatusCode status, + string name, + params SecurityIdentity[] identities) => + new TestCaseData(acl, status, identities).SetName(name); +} diff --git a/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/transactional_write_stream_security.cs b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/transactional_write_stream_security.cs new file mode 100644 index 0000000000..323c28a330 --- /dev/null +++ b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/transactional_write_stream_security.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Grpc.Core; +using NUnit.Framework; + +namespace EventStore.Core.Tests.Services.Transport.Grpc.Security; + +[NonParallelizable] +[TestFixture(typeof(LogFormat.V2), typeof(string))] +public class transactional_write_stream_security : AuthenticationTestBase +{ + public static IEnumerable BatchAppendCases() + { + yield return Case(StreamAclKind.UserOne, SecurityIdentity.Invalid, StatusCode.Unauthenticated, + "batch_append_with_not_existing_credentials_is_not_authenticated"); + yield return Case(StreamAclKind.UserOne, SecurityIdentity.Anonymous, StatusCode.PermissionDenied, + "batch_append_to_stream_with_no_credentials_is_denied"); + yield return Case(StreamAclKind.UserOne, SecurityIdentity.UserTwo, StatusCode.PermissionDenied, + "batch_append_to_stream_with_not_authorized_user_credentials_is_denied"); + yield return Case(StreamAclKind.UserOne, SecurityIdentity.UserOne, StatusCode.OK, + "batch_append_to_stream_with_authorized_user_credentials_succeeds"); + yield return Case(StreamAclKind.UserOne, SecurityIdentity.Admin, StatusCode.OK, + "batch_append_to_stream_with_admin_user_credentials_succeeds"); + + yield return Case(StreamAclKind.None, SecurityIdentity.Anonymous, StatusCode.OK, + "batch_append_to_no_acl_stream_succeeds_when_no_credentials_are_passed"); + yield return Case(StreamAclKind.None, SecurityIdentity.Invalid, StatusCode.Unauthenticated, + "batch_append_to_no_acl_stream_is_not_authenticated_when_not_existing_credentials_are_passed"); + yield return Case(StreamAclKind.None, SecurityIdentity.UserOne, StatusCode.OK, + "batch_append_to_no_acl_stream_succeeds_for_user_one"); + yield return Case(StreamAclKind.None, SecurityIdentity.UserTwo, StatusCode.OK, + "batch_append_to_no_acl_stream_succeeds_for_user_two"); + yield return Case(StreamAclKind.None, SecurityIdentity.Admin, StatusCode.OK, + "batch_append_to_no_acl_stream_succeeds_for_admin"); + + yield return Case(StreamAclKind.All, SecurityIdentity.Anonymous, StatusCode.OK, + "batch_append_to_all_access_normal_stream_succeeds_when_no_credentials_are_passed"); + yield return Case(StreamAclKind.All, SecurityIdentity.Invalid, StatusCode.Unauthenticated, + "batch_append_to_all_access_normal_stream_is_not_authenticated_when_not_existing_credentials_are_passed"); + yield return Case(StreamAclKind.All, SecurityIdentity.UserOne, StatusCode.OK, + "batch_append_to_all_access_normal_stream_succeeds_for_user_one"); + yield return Case(StreamAclKind.All, SecurityIdentity.UserTwo, StatusCode.OK, + "batch_append_to_all_access_normal_stream_succeeds_for_user_two"); + yield return Case(StreamAclKind.All, SecurityIdentity.Admin, StatusCode.OK, + "batch_append_to_all_access_normal_stream_succeeds_for_admin"); + } + + [Test] + public void public_grpc_streams_service_has_no_explicit_transaction_lifecycle_rpcs() + { + var methodNames = EventStore.Client.Streams.Streams.Descriptor.Methods + .Select(method => method.Name) + .ToArray(); + + Assert.Multiple(() => + { + Assert.That(methodNames, Does.Contain("BatchAppend")); + Assert.That(methodNames.Where(name => name.Contains("transaction", StringComparison.OrdinalIgnoreCase)), + Is.Empty); + }); + } + + [TestCaseSource(nameof(BatchAppendCases))] + public async Task enforces_multi_event_batch_append_security( + StreamAclKind acl, + SecurityIdentity identity, + StatusCode expectedStatus) + { + var streamName = await CreateStreamWithAcl(StreamKind.User, acl, seed: false); + AssertStatus(expectedStatus, await ExecuteBatchAppend(streamName, identity)); + } + + private static TestCaseData Case( + StreamAclKind acl, + SecurityIdentity identity, + StatusCode status, + string name) => + new TestCaseData(acl, identity, status).SetName(name); +} diff --git a/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/write_stream_meta_security.cs b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/write_stream_meta_security.cs new file mode 100644 index 0000000000..bb546f8931 --- /dev/null +++ b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/write_stream_meta_security.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Grpc.Core; +using NUnit.Framework; + +namespace EventStore.Core.Tests.Services.Transport.Grpc.Security; + +[NonParallelizable] +[TestFixture(typeof(LogFormat.V2), typeof(string))] +public class write_stream_meta_security : AuthenticationTestBase +{ + public static IEnumerable Cases() + { + yield return Case(StreamAclKind.UserOne, StatusCode.Unauthenticated, + "writing_meta_with_not_existing_credentials_is_not_authenticated", SecurityIdentity.Invalid); + yield return Case(StreamAclKind.UserOne, StatusCode.PermissionDenied, + "writing_meta_to_stream_with_no_credentials_is_denied", SecurityIdentity.Anonymous); + yield return Case(StreamAclKind.UserOne, StatusCode.PermissionDenied, + "writing_meta_to_stream_with_not_authorized_user_credentials_is_denied", SecurityIdentity.UserTwo); + yield return Case(StreamAclKind.UserOne, StatusCode.OK, + "writing_meta_to_stream_with_authorized_user_credentials_succeeds", SecurityIdentity.UserOne); + yield return Case(StreamAclKind.UserOne, StatusCode.OK, + "writing_meta_to_stream_with_admin_user_credentials_succeeds", SecurityIdentity.Admin); + + yield return Case(StreamAclKind.None, StatusCode.OK, + "writing_meta_to_no_acl_stream_succeeds_when_no_credentials_are_passed", SecurityIdentity.Anonymous); + yield return Case(StreamAclKind.None, StatusCode.Unauthenticated, + "writing_meta_to_no_acl_stream_is_not_authenticated_when_not_existing_credentials_are_passed", + SecurityIdentity.Invalid); + yield return Case(StreamAclKind.None, StatusCode.OK, + "writing_meta_to_no_acl_stream_succeeds_when_any_existing_user_credentials_are_passed", + SecurityIdentity.UserOne, SecurityIdentity.UserTwo); + yield return Case(StreamAclKind.None, StatusCode.OK, + "writing_meta_to_no_acl_stream_succeeds_when_admin_user_credentials_are_passed", SecurityIdentity.Admin); + + yield return Case(StreamAclKind.All, StatusCode.OK, + "writing_meta_to_all_access_normal_stream_succeeds_when_no_credentials_are_passed", + SecurityIdentity.Anonymous); + yield return Case(StreamAclKind.All, StatusCode.Unauthenticated, + "writing_meta_to_all_access_normal_stream_is_not_authenticated_when_not_existing_credentials_are_passed", + SecurityIdentity.Invalid); + yield return Case(StreamAclKind.All, StatusCode.OK, + "writing_meta_to_all_access_normal_stream_succeeds_when_any_existing_user_credentials_are_passed", + SecurityIdentity.UserOne, SecurityIdentity.UserTwo); + yield return Case(StreamAclKind.All, StatusCode.OK, + "writing_meta_to_all_access_normal_stream_succeeds_when_admin_user_credentials_are_passed", + SecurityIdentity.Admin); + } + + [TestCaseSource(nameof(Cases))] + public async Task enforces_stream_metadata_write_security( + StreamAclKind acl, + StatusCode expectedStatus, + SecurityIdentity[] identities) + { + foreach (var identity in identities) + { + var streamName = await CreateStreamWithAcl(StreamKind.User, acl, seed: false); + AssertStatus(expectedStatus, + await ExecuteOperation(SecurityOperation.WriteMetadata, streamName, identity)); + } + } + + private static TestCaseData Case( + StreamAclKind acl, + StatusCode status, + string name, + params SecurityIdentity[] identities) => + new TestCaseData(acl, status, identities).SetName(name); +} diff --git a/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/write_stream_security.cs b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/write_stream_security.cs new file mode 100644 index 0000000000..0b52463d3b --- /dev/null +++ b/src/EventStore.Core.Tests/Services/Transport/Grpc/Security/write_stream_security.cs @@ -0,0 +1,85 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using EventStore.Core.Services; +using Grpc.Core; +using NUnit.Framework; + +namespace EventStore.Core.Tests.Services.Transport.Grpc.Security; + +[NonParallelizable] +[TestFixture(typeof(LogFormat.V2), typeof(string))] +public class write_stream_security : AuthenticationTestBase +{ + public static IEnumerable Cases() + { + yield return Case(StreamAclKind.UserOne, StatusCode.Unauthenticated, + "writing_with_not_existing_credentials_is_not_authenticated", SecurityIdentity.Invalid); + yield return Case(StreamAclKind.UserOne, StatusCode.PermissionDenied, + "writing_to_stream_with_no_credentials_is_denied", SecurityIdentity.Anonymous); + yield return Case(StreamAclKind.UserOne, StatusCode.PermissionDenied, + "writing_to_stream_with_not_authorized_user_credentials_is_denied", SecurityIdentity.UserTwo); + yield return Case(StreamAclKind.UserOne, StatusCode.OK, + "writing_to_stream_with_authorized_user_credentials_succeeds", SecurityIdentity.UserOne); + yield return Case(StreamAclKind.UserOne, StatusCode.OK, + "writing_to_stream_with_admin_user_credentials_succeeds", SecurityIdentity.Admin); + + yield return Case(StreamAclKind.None, StatusCode.OK, + "writing_to_no_acl_stream_succeeds_when_no_credentials_are_passed", SecurityIdentity.Anonymous); + yield return Case(StreamAclKind.None, StatusCode.Unauthenticated, + "writing_to_no_acl_stream_is_not_authenticated_when_not_existing_credentials_are_passed", + SecurityIdentity.Invalid); + yield return Case(StreamAclKind.None, StatusCode.OK, + "writing_to_no_acl_stream_succeeds_when_any_existing_user_credentials_are_passed", + SecurityIdentity.UserOne, SecurityIdentity.UserTwo); + yield return Case(StreamAclKind.None, StatusCode.OK, + "writing_to_no_acl_stream_succeeds_when_any_admin_user_credentials_are_passed", SecurityIdentity.Admin); + + yield return Case(StreamAclKind.All, StatusCode.OK, + "writing_to_all_access_normal_stream_succeeds_when_no_credentials_are_passed", SecurityIdentity.Anonymous); + yield return Case(StreamAclKind.All, StatusCode.Unauthenticated, + "writing_to_all_access_normal_stream_is_not_authenticated_when_not_existing_credentials_are_passed", + SecurityIdentity.Invalid); + yield return Case(StreamAclKind.All, StatusCode.OK, + "writing_to_all_access_normal_stream_succeeds_when_any_existing_user_credentials_are_passed", + SecurityIdentity.UserOne, SecurityIdentity.UserTwo); + yield return Case(StreamAclKind.All, StatusCode.OK, + "writing_to_all_access_normal_stream_succeeds_when_any_admin_user_credentials_are_passed", + SecurityIdentity.Admin); + } + + [Test] + public async Task writing_to_all_is_never_allowed() + { + foreach (var identity in new[] + { + SecurityIdentity.Anonymous, + SecurityIdentity.UserOne, + SecurityIdentity.UserTwo, + SecurityIdentity.Admin + }) + { + AssertStatus(StatusCode.PermissionDenied, + await ExecuteOperation(SecurityOperation.Write, SystemStreams.AllStream, identity)); + } + } + + [TestCaseSource(nameof(Cases))] + public async Task enforces_stream_write_security( + StreamAclKind acl, + StatusCode expectedStatus, + SecurityIdentity[] identities) + { + foreach (var identity in identities) + { + var streamName = await CreateStreamWithAcl(StreamKind.User, acl, seed: false); + AssertStatus(expectedStatus, await ExecuteOperation(SecurityOperation.Write, streamName, identity)); + } + } + + private static TestCaseData Case( + StreamAclKind acl, + StatusCode status, + string name, + params SecurityIdentity[] identities) => + new TestCaseData(acl, status, identities).SetName(name); +}