From 6512faf00a31913e99ba5a61056d40578e79d9a9 Mon Sep 17 00:00:00 2001 From: Stefan Ossendorf Date: Mon, 23 Dec 2024 18:15:21 +0100 Subject: [PATCH 1/7] Set application context user to an unauthenticated claims principal when FlowSecurityPrincipalFromClient is enabled. Fixes #4410 --- Source/Csla/Server/DataPortal.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Csla/Server/DataPortal.cs b/Source/Csla/Server/DataPortal.cs index 3b19eb3d83..2cc5939ebd 100644 --- a/Source/Csla/Server/DataPortal.cs +++ b/Source/Csla/Server/DataPortal.cs @@ -7,6 +7,7 @@ //----------------------------------------------------------------------- using System.Diagnostics.CodeAnalysis; +using System.Security.Claims; using System.Security.Principal; using Csla.Configuration; using Csla.Properties; @@ -774,7 +775,7 @@ private void ClearContext(DataPortalContext context) if (!context.IsRemotePortal) return; _applicationContext.Clear(); if (SecurityOptions.FlowSecurityPrincipalFromClient) - _applicationContext.User = null; + _applicationContext.User = new ClaimsPrincipal(new ClaimsIdentity()); } #endregion From 8eeb1dabc6664b6aced82e3323e77182ee0fd66a Mon Sep 17 00:00:00 2001 From: Stefan Ossendorf Date: Tue, 24 Dec 2024 12:40:26 +0100 Subject: [PATCH 2/7] Remove null-check in ApplicationContextManagerHttpContext Revert change in DataPortal Fixes #4410 --- Source/Csla.AspNetCore/ApplicationContextManagerHttpContext.cs | 2 -- Source/Csla/Server/DataPortal.cs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Source/Csla.AspNetCore/ApplicationContextManagerHttpContext.cs b/Source/Csla.AspNetCore/ApplicationContextManagerHttpContext.cs index a6a85c295c..1ee08d46e6 100644 --- a/Source/Csla.AspNetCore/ApplicationContextManagerHttpContext.cs +++ b/Source/Csla.AspNetCore/ApplicationContextManagerHttpContext.cs @@ -118,11 +118,9 @@ public System.Security.Principal.IPrincipal GetUser() /// Sets the current principal. /// /// Principal object. - /// is . /// The underlying is . public void SetUser(System.Security.Principal.IPrincipal principal) { - ArgumentNullException.ThrowIfNull(principal); ThrowIfHttpContextIsNull(); HttpContext.User = (ClaimsPrincipal)principal; } diff --git a/Source/Csla/Server/DataPortal.cs b/Source/Csla/Server/DataPortal.cs index 2cc5939ebd..3483a85ef3 100644 --- a/Source/Csla/Server/DataPortal.cs +++ b/Source/Csla/Server/DataPortal.cs @@ -775,7 +775,7 @@ private void ClearContext(DataPortalContext context) if (!context.IsRemotePortal) return; _applicationContext.Clear(); if (SecurityOptions.FlowSecurityPrincipalFromClient) - _applicationContext.User = new ClaimsPrincipal(new ClaimsIdentity()); + _applicationContext.User = null; } #endregion From 1019f8b507ab5705e42b8be75f411b5192281d96 Mon Sep 17 00:00:00 2001 From: Stefan Ossendorf Date: Mon, 30 Dec 2024 23:11:57 +0100 Subject: [PATCH 3/7] Add test to test for not null cleanup in DataPortal --- .../ApplicationContextManagerHttpContext.cs | 2 + .../ApplicationContextManagerUnitTests.cs | 12 +++- .../Csla.TestHelpers/TestDIContextFactory.cs | 3 +- .../Csla.test/DataPortal/DataPortalTests.cs | 58 +++++++++++++++++++ Source/Csla/Server/DataPortal.cs | 2 +- 5 files changed, 73 insertions(+), 4 deletions(-) diff --git a/Source/Csla.AspNetCore/ApplicationContextManagerHttpContext.cs b/Source/Csla.AspNetCore/ApplicationContextManagerHttpContext.cs index 1ee08d46e6..a6a85c295c 100644 --- a/Source/Csla.AspNetCore/ApplicationContextManagerHttpContext.cs +++ b/Source/Csla.AspNetCore/ApplicationContextManagerHttpContext.cs @@ -118,9 +118,11 @@ public System.Security.Principal.IPrincipal GetUser() /// Sets the current principal. /// /// Principal object. + /// is . /// The underlying is . public void SetUser(System.Security.Principal.IPrincipal principal) { + ArgumentNullException.ThrowIfNull(principal); ThrowIfHttpContextIsNull(); HttpContext.User = (ClaimsPrincipal)principal; } diff --git a/Source/Csla.TestHelpers/ApplicationContextManagerUnitTests.cs b/Source/Csla.TestHelpers/ApplicationContextManagerUnitTests.cs index d73a39c35b..012b9b9ebb 100644 --- a/Source/Csla.TestHelpers/ApplicationContextManagerUnitTests.cs +++ b/Source/Csla.TestHelpers/ApplicationContextManagerUnitTests.cs @@ -1,9 +1,19 @@ -namespace Csla.TestHelpers +using System.Security.Principal; + +namespace Csla.TestHelpers { public class ApplicationContextManagerUnitTests : Core.ApplicationContextManagerAsyncLocal { + public IPrincipal LastSetUserPrincipal { get; private set; } + public Guid InstanceId { get; private set; } = Guid.NewGuid(); public DateTime CreatedAt { get; private set; } = DateTime.Now; + + public override void SetUser(IPrincipal principal) + { + LastSetUserPrincipal = principal; + base.SetUser(principal); + } } } diff --git a/Source/Csla.TestHelpers/TestDIContextFactory.cs b/Source/Csla.TestHelpers/TestDIContextFactory.cs index ff9afba1e1..6a7f9b6084 100644 --- a/Source/Csla.TestHelpers/TestDIContextFactory.cs +++ b/Source/Csla.TestHelpers/TestDIContextFactory.cs @@ -74,10 +74,9 @@ public static TestDIContext CreateContext(Action customCslaOptions, var services = new ServiceCollection(); // Add Csla - //services.AddSingleton(); services.TryAddSingleton(); - services.AddCsla(customCslaOptions); services.AddSingleton(); + services.AddCsla(customCslaOptions); serviceProvider = services.BuildServiceProvider(); diff --git a/Source/Csla.test/DataPortal/DataPortalTests.cs b/Source/Csla.test/DataPortal/DataPortalTests.cs index c247113518..31acadc73b 100644 --- a/Source/Csla.test/DataPortal/DataPortalTests.cs +++ b/Source/Csla.test/DataPortal/DataPortalTests.cs @@ -15,6 +15,10 @@ using Csla.Rules; using Csla.Core; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Csla.Testing.Business.DataPortal; +using Csla.Server; +using System.Security.Principal; +using FluentAssertions.Execution; namespace Csla.Test.DataPortal { @@ -311,6 +315,29 @@ public async Task WhenCreatingANewObjectThePortalMustWaitAfterCreateUntilTheObje } } + [TestMethod] + public async Task CleanupShouldSetThePrincipalToAnUnathenticatedOne() + { + // We have to use an extra DI context here to setup the TestableDataPortal and set necessary options + var diContext = TestDIContextFactory.CreateContext(options => + { + options.Services.AddTransient(); + options.Security(s => s.FlowSecurityPrincipalFromClient = true); + options.DataPortal(dpo => dpo.AddServerSideDataPortal()); + }); + + var applicationContext = diContext.CreateTestApplicationContext(); + var contextManager = (ApplicationContextManagerUnitTests)applicationContext.ContextManager; + var dp = diContext.ServiceProvider.GetRequiredService(); + _ = await dp.Create(typeof(TestBO), null, new DataPortalContext(applicationContext, applicationContext.Principal, true, "en-US", "en-US", new Core.ContextDictionary()), true); + + using (new AssertionScope()) + { + contextManager.LastSetUserPrincipal.Should().NotBeNull(); + contextManager.LastSetUserPrincipal.Should().Match(p => !p.Identity.IsAuthenticated); + } + } + private void ClientPortal_DataPortalInvoke(DataPortalEventArgs obj) { TestResults.Add("dpinvoke", "true"); @@ -344,6 +371,37 @@ private void DeleteSingle(int id) } + public class UserLoggingContextManagerDecorator : IContextManager + { + private readonly IContextManager _decorated; + public IPrincipal LastSetPrincipal + { + get; private set; + } + + public UserLoggingContextManagerDecorator(IContextManager contextManager) + { + _decorated = contextManager; + } + + public bool IsStatefulContext => _decorated.IsStatefulContext; + + public bool IsValid => _decorated.IsValid; + + public ApplicationContext ApplicationContext { get => _decorated.ApplicationContext; set => _decorated.ApplicationContext = value; } + + public IContextDictionary GetClientContext(ApplicationContext.ExecutionLocations executionLocation) => _decorated.GetClientContext(executionLocation); + public IContextDictionary GetLocalContext() => _decorated.GetLocalContext(); + public IPrincipal GetUser() => _decorated.GetUser(); + public void SetClientContext(IContextDictionary clientContext, ApplicationContext.ExecutionLocations executionLocation) => _decorated.SetClientContext(clientContext, executionLocation); + public void SetLocalContext(IContextDictionary localContext) => _decorated.SetLocalContext(localContext); + public void SetUser(IPrincipal principal) + { + LastSetPrincipal = principal; + _decorated.SetUser(principal); + } + } + [Serializable] public class EncapsulatedBusy : BusinessBase { diff --git a/Source/Csla/Server/DataPortal.cs b/Source/Csla/Server/DataPortal.cs index 3483a85ef3..2cc5939ebd 100644 --- a/Source/Csla/Server/DataPortal.cs +++ b/Source/Csla/Server/DataPortal.cs @@ -775,7 +775,7 @@ private void ClearContext(DataPortalContext context) if (!context.IsRemotePortal) return; _applicationContext.Clear(); if (SecurityOptions.FlowSecurityPrincipalFromClient) - _applicationContext.User = null; + _applicationContext.User = new ClaimsPrincipal(new ClaimsIdentity()); } #endregion From 62287a511fb1d4167dfbde38ff35d839d67a7e7a Mon Sep 17 00:00:00 2001 From: Stefan Ossendorf Date: Mon, 30 Dec 2024 23:20:42 +0100 Subject: [PATCH 4/7] Make the change less impactful to users --- Source/Csla/ApplicationContext.cs | 6 +++--- Source/Csla/Server/DataPortal.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Csla/ApplicationContext.cs b/Source/Csla/ApplicationContext.cs index a387fdcf52..aa00aef5ec 100644 --- a/Source/Csla/ApplicationContext.cs +++ b/Source/Csla/ApplicationContext.cs @@ -47,8 +47,8 @@ public ApplicationContext(ApplicationContextAccessor applicationContextAccessor) /// public ClaimsPrincipal Principal { - get { return (ClaimsPrincipal)ContextManager.GetUser(); } - set { ContextManager.SetUser(value); } + get { return (ClaimsPrincipal)User; } + set { User = value; } } /// @@ -64,7 +64,7 @@ public ClaimsPrincipal Principal public IPrincipal User { get { return ContextManager.GetUser(); } - set { ContextManager.SetUser(value); } + set { ContextManager.SetUser(value ?? new ClaimsPrincipal(new ClaimsIdentity())); } } /// diff --git a/Source/Csla/Server/DataPortal.cs b/Source/Csla/Server/DataPortal.cs index 2cc5939ebd..3483a85ef3 100644 --- a/Source/Csla/Server/DataPortal.cs +++ b/Source/Csla/Server/DataPortal.cs @@ -775,7 +775,7 @@ private void ClearContext(DataPortalContext context) if (!context.IsRemotePortal) return; _applicationContext.Clear(); if (SecurityOptions.FlowSecurityPrincipalFromClient) - _applicationContext.User = new ClaimsPrincipal(new ClaimsIdentity()); + _applicationContext.User = null; } #endregion From db05e6bcaadd7f85c8e8b0d3757f8a599fcb704b Mon Sep 17 00:00:00 2001 From: Stefan Ossendorf Date: Mon, 30 Dec 2024 23:21:47 +0100 Subject: [PATCH 5/7] Unused decorater removed --- .../Csla.test/DataPortal/DataPortalTests.cs | 31 ------------------- 1 file changed, 31 deletions(-) diff --git a/Source/Csla.test/DataPortal/DataPortalTests.cs b/Source/Csla.test/DataPortal/DataPortalTests.cs index 31acadc73b..5f79e2cb21 100644 --- a/Source/Csla.test/DataPortal/DataPortalTests.cs +++ b/Source/Csla.test/DataPortal/DataPortalTests.cs @@ -371,37 +371,6 @@ private void DeleteSingle(int id) } - public class UserLoggingContextManagerDecorator : IContextManager - { - private readonly IContextManager _decorated; - public IPrincipal LastSetPrincipal - { - get; private set; - } - - public UserLoggingContextManagerDecorator(IContextManager contextManager) - { - _decorated = contextManager; - } - - public bool IsStatefulContext => _decorated.IsStatefulContext; - - public bool IsValid => _decorated.IsValid; - - public ApplicationContext ApplicationContext { get => _decorated.ApplicationContext; set => _decorated.ApplicationContext = value; } - - public IContextDictionary GetClientContext(ApplicationContext.ExecutionLocations executionLocation) => _decorated.GetClientContext(executionLocation); - public IContextDictionary GetLocalContext() => _decorated.GetLocalContext(); - public IPrincipal GetUser() => _decorated.GetUser(); - public void SetClientContext(IContextDictionary clientContext, ApplicationContext.ExecutionLocations executionLocation) => _decorated.SetClientContext(clientContext, executionLocation); - public void SetLocalContext(IContextDictionary localContext) => _decorated.SetLocalContext(localContext); - public void SetUser(IPrincipal principal) - { - LastSetPrincipal = principal; - _decorated.SetUser(principal); - } - } - [Serializable] public class EncapsulatedBusy : BusinessBase { From 5b54d03cb41ce390d9d755a891eafb0b7772cff3 Mon Sep 17 00:00:00 2001 From: Stefan Ossendorf Date: Mon, 30 Dec 2024 23:27:08 +0100 Subject: [PATCH 6/7] Update migration doc --- docs/Upgrading to CSLA 9.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Upgrading to CSLA 9.md b/docs/Upgrading to CSLA 9.md index 6c50e6aec2..4180438c8a 100644 --- a/docs/Upgrading to CSLA 9.md +++ b/docs/Upgrading to CSLA 9.md @@ -191,6 +191,8 @@ Supporting nullable types means that some APIs have changed to support nullable * Type `Csla.Serialization.SerializationFormatterFactory` was removed. To get the serializer resolve an instance of type `ISerializationFormatter`. For example `ApplicationContext.GetRequiredService()`(from within a business object) * `Csla.Core.IParent` * `ApplyEditChild` and `RemoveChild` changed from `void` to `Task` return type +* `void Csla.Core.IContextManager.SetUser(IPrincipal principal)` does not accept `null` anymore + * This change only affects you when you use the `IContextManager` directly. If you use `ApplicationContext.User` CSLA will take care of translating it into a non-null IPrincipal. #### Using Timeout in `HttpProxy` and `HttpCompressionProxy` From c479d2dcbefe5e0d9f0e05dba10fb4018ebc92c0 Mon Sep 17 00:00:00 2001 From: Rockford lhotka Date: Mon, 30 Dec 2024 17:07:34 -0600 Subject: [PATCH 7/7] Fix CheckInnerExceptionsOnSave test --- Source/Csla.test/DPException/DataPortalExceptionTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Csla.test/DPException/DataPortalExceptionTests.cs b/Source/Csla.test/DPException/DataPortalExceptionTests.cs index 9f0672313d..f379c04791 100644 --- a/Source/Csla.test/DPException/DataPortalExceptionTests.cs +++ b/Source/Csla.test/DPException/DataPortalExceptionTests.cs @@ -71,7 +71,7 @@ public void CheckInnerExceptionsOnSave() #if (NETFRAMEWORK) Assert.AreEqual(".Net SqlClient Data Provider", exceptionSource); #else - Assert.AreEqual("Core .Net SqlClient Data Provider", exceptionSource); + Assert.AreEqual("Core Microsoft SqlClient Data Provider", exceptionSource); #endif //verify that the implemented method, DataPortal_OnDataPortal