Flow Thread.CurrentPrincipal with ExecutionContext (dotnet/corefx#34747)
authorMarco Rossignoli <marco.rossignoli@gmail.com>
Wed, 30 Jan 2019 12:48:28 +0000 (13:48 +0100)
committerStephen Toub <stoub@microsoft.com>
Wed, 30 Jan 2019 12:48:28 +0000 (07:48 -0500)
* flow Thread.CurrentPrincipal with ExecutionContext

* fix netfx test

* address PR feedback

* address PR feedback

* try to make test more reliable using StartNew(), removed from NetFramework

* address PR feedback

* nit: extraline

* add null set test

* nit: extraline, again...

* rename test

* apply Stephen fix

* nit: typos

* address PR feedback

* nit: update comment

Commit migrated from https://github.com/dotnet/corefx/commit/bab35ecc6f3f94215705d109d5094c1b3c461992

src/libraries/Common/tests/System/Threading/ThreadTestHelpers.cs
src/libraries/System.Threading.Thread/src/System/Threading/Thread.cs
src/libraries/System.Threading.Thread/tests/ThreadTests.cs

index 316fb1b..26e9fba 100644 (file)
@@ -107,6 +107,11 @@ namespace System.Threading.Tests
             waitForThread();
         }
 
+        public static void RunTestInBackgroundThread(Func<Task> test)
+        {
+            RunTestInBackgroundThread(() => test().Wait());
+        }
+
         public static void WaitForCondition(Func<bool> condition)
         {
             WaitForConditionWithCustomDelay(condition, () => Thread.Sleep(1));
index 0be1072..e9e654a 100644 (file)
@@ -15,12 +15,12 @@ namespace System.Threading
     {
         [ThreadStatic]
         private static Thread t_currentThread;
+        private static AsyncLocal<IPrincipal> s_asyncLocalPrincipal;
 
         private readonly RuntimeThread _runtimeThread;
         private Delegate _start;
         private CultureInfo _startCulture;
         private CultureInfo _startUICulture;
-        private IPrincipal _principal;
 
         private Thread(RuntimeThread runtimeThread)
         {
@@ -190,11 +190,23 @@ namespace System.Threading
         {
             get
             {
-                return CurrentThread._principal ?? (CurrentThread._principal = AppDomain.CurrentDomain.GetThreadPrincipal());
+                if (s_asyncLocalPrincipal is null)
+                {
+                    CurrentPrincipal = AppDomain.CurrentDomain.GetThreadPrincipal();
+                }
+                return s_asyncLocalPrincipal?.Value;
             }
             set
             {
-                CurrentThread._principal = value;
+                if (s_asyncLocalPrincipal is null)
+                {
+                    if (value is null)
+                    {
+                        return;
+                    }
+                    Interlocked.CompareExchange(ref s_asyncLocalPrincipal, new AsyncLocal<IPrincipal>(), null);
+                }
+                s_asyncLocalPrincipal.Value = value;
             }
         }
 
index bbc9d58..6d3a61d 100644 (file)
@@ -7,7 +7,9 @@ using System.Diagnostics;
 using System.Globalization;
 using System.Reflection;
 using System.Runtime.InteropServices;
+using System.Security.Claims;
 using System.Security.Principal;
+using System.Threading.Tasks;
 using System.Threading.Tests;
 using Xunit;
 
@@ -429,6 +431,97 @@ namespace System.Threading.Threads.Tests
         }
 
         [Fact]
+        public static void CurrentPrincipalContextFlowTest()
+        {
+            ThreadTestHelpers.RunTestInBackgroundThread(async () =>
+            {
+                Thread.CurrentPrincipal = new ClaimsPrincipal();
+
+                await Task.Run(async() => {
+
+                    Assert.IsType<ClaimsPrincipal>(Thread.CurrentPrincipal);
+
+                    await Task.Run(async() => 
+                    {
+                        Assert.IsType<ClaimsPrincipal>(Thread.CurrentPrincipal);
+
+                        Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity("name"), new string[0]);
+
+                        await Task.Run(() =>
+                        {
+                            Assert.IsType<GenericPrincipal>(Thread.CurrentPrincipal);
+                        });
+
+                        Assert.IsType<GenericPrincipal>(Thread.CurrentPrincipal);
+                    });
+
+                    Assert.IsType<ClaimsPrincipal>(Thread.CurrentPrincipal);
+                });
+
+                Assert.IsType<ClaimsPrincipal>(Thread.CurrentPrincipal);
+            });
+        }
+
+        [Fact]
+        public static void CurrentPrincipalContextFlowTest_NotFlow()
+        {
+            ThreadTestHelpers.RunTestInBackgroundThread(async () =>
+            {
+                Thread.CurrentPrincipal = new ClaimsPrincipal();
+
+                Task task;
+                using(ExecutionContext.SuppressFlow())
+                {
+                    Assert.True(ExecutionContext.IsFlowSuppressed());
+
+                    task = Task.Run(() => 
+                    {
+                        // Default PrincipalPolicy for netcoreapp is null
+                        if (PlatformDetection.IsNetCore)
+                        {
+                            Assert.Null(Thread.CurrentPrincipal);
+                        }
+                        else
+                        {
+                            Assert.IsType<GenericPrincipal>(Thread.CurrentPrincipal);
+                        }
+
+                        Assert.False(ExecutionContext.IsFlowSuppressed());
+                    });
+                }
+
+                Assert.False(ExecutionContext.IsFlowSuppressed());
+
+                await task;
+            });
+        }
+
+        [Fact]
+        public static void CurrentPrincipal_SetNull()
+        {
+            // We run test on remote process because we need to set same principal policy
+            // On netfx default principal policy is PrincipalPolicy.UnauthenticatedPrincipal
+            DummyClass.RemoteInvoke(() =>
+            {
+                AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.NoPrincipal);
+
+                Assert.Null(Thread.CurrentPrincipal);
+
+                Thread.CurrentPrincipal = null;
+                Assert.Null(Thread.CurrentPrincipal);
+
+                Thread.CurrentPrincipal = new ClaimsPrincipal();
+                Assert.IsType<ClaimsPrincipal>(Thread.CurrentPrincipal);
+
+                Thread.CurrentPrincipal = null;
+                Assert.Null(Thread.CurrentPrincipal);
+
+                Thread.CurrentPrincipal = new ClaimsPrincipal();
+                Assert.IsType<ClaimsPrincipal>(Thread.CurrentPrincipal);
+            }).Dispose();
+        }
+
+        [Fact]
         public static void CurrentThreadTest()
         {
             Thread otherThread = null;