In a .NET 3.5 Winforms app, after a user provides a username and password, I set a custom principal in the CurrentPrincipal
property like this:
My.User.CurrentPrincipal = Service.GetPrincipal(username)
This is done in a method that is called using Invoke, because the originating thread is not the UI thread:
Invoke(New Action(AddressOf doLogin))
But when I click on a button in the Winforms application, the CurrentPrincipal property has been reverted to its default, the current Windows user.
Dim lPrincipal = My.User.CurrentPrincipal ' not my custom principal
Apparently, using Invoke while setting the principal does not solve the problem. Is there another way to set the CurrentPrincipal
property for all threads in the application?
Sourcecode to reproduce the problem:
Imports System.Security.Principal
Imports System.Threading
Public Class Form1
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim lThread As New Thread(AddressOf doLogin)
lThread.Start()
End Sub
Private Sub doLogin()
Invoke(New Action(AddressOf setPrincipal))
End Sub
Private Sub setPrincipal()
My.User.CurrentPrincipal = New CustomPrincipal
MsgBox(My.User.CurrentPrincipal.Identity.AuthenticationType) ' Custom
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
MsgBox(My.User.CurrentPrincipal.Identity.AuthenticationType) ' Default
End Sub
End Class
Public Class CustomPrincipal
Implements IPrincipal
Public ReadOnly Property Identity() As IIdentity Implements IPrincipal.Identity
Get
Return New CustomIdentity()
End Get
End Property
Public Function IsInRole(ByVal role As String) As Boolean Implements IPrincipal.IsInRole
Return True
End Function
End Class
Public Class CustomIdentity
Implements IIdentity
Public ReadOnly Property AuthenticationType() As String Implements IIdentity.AuthenticationType
Get
Return "Custom"
End Get
End Property
Public ReadOnly Property IsAuthenticated() As Boolean Implements IIdentity.IsAuthenticated
Get
Return True
End Get
End Property
Public ReadOnly Property Name() As String Implements IIdentity.Name
Get
Return "CustomName"
End Get
End Property
End Class
Instead of Thread.CurrentPrincipal
(My.User.CurrentPrincipal
), use AppDomain.SetThreadPrincipal
:
AppDomain.CurrentDomain.SetThreadPrincipal(principal)
You are doing battle with something that's called 'call context' in the .NET framework. I need to wave my hands a bit at it because I don't understand it completely. The basic premise is that the current principal is a Big Deal in .NET security. It makes sandboxing work, helping to isolate code so it cannot do anything unsafe. Code running in a browser, a phone, a plug-in, that sort of thing.
The current principal is associated with a thread, Thread.CurrentPrincipal property. That makes Control.Begin/Invoke() tricky, some sort of plugin could hijack the rights of the program's main thread by using it to run code on that thread. The call context is a counter-measure against this. Your code is updating the principal of the call context, not the thread. After the invoked call is complete that falls in the bit bucket, it doesn't flow back to the originating thread in the case of Control.Invoke().
Finding a workaround for this boggles me right now. It really has to be code that originates from your main thread that sets the property, nothing that gets invoked. I can only think of a silly fix, using a one millisecond Timer:
Private Sub setPrincipal()
Timer1.Enabled = True
End Sub
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Timer1.Enabled = False
My.User.CurrentPrincipal = New CustomPrincipal
MsgBox(My.User.CurrentPrincipal.Identity.AuthenticationType) ' Custom
End Sub
It does work. Beware the possible race.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With