5

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
Jan Willem B
  • 3,787
  • 1
  • 25
  • 39
  • BTW, in winforms the Shown event exhibits the same problem. I suspect it is invoked upon the completion of the Load event so that it is in a different call context. – Jeff Aug 02 '12 at 23:55

2 Answers2

12

Instead of Thread.CurrentPrincipal (My.User.CurrentPrincipal), use AppDomain.SetThreadPrincipal:

AppDomain.CurrentDomain.SetThreadPrincipal(principal)
Kent Boogaart
  • 175,602
  • 35
  • 392
  • 393
7

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.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • 1
    Compliments on the repro code btw, you made it very easy to help you. – Hans Passant Jan 04 '11 at 14:22
  • 1
    Illuminating! Thanks. Guess I just have to abandon the idea of using the CurrentPrincipal and keep a principal object handy somewhere in a variable that is accessible through the entire application. – Jan Willem B Jan 04 '11 at 20:22
  • At least in my case, it appears that setting the PrincipalPolicy for the AppDomain solved the issue: http://stackoverflow.com/a/4422123/300212 – Nick Spreitzer Jul 24 '12 at 21:33