Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Copy permissions / authentication to child threads...?

Here's something very weird I had noticed.

I'm writing a CRM 2011 Silverlight extension and, well, all is fine on my local development instance. The application uses OData to communicate, and uses System.Threading.Tasks.Task a lot to perform all the operations in the background (FromAsync is a blessing).

However, I decided to test my application in CRM 2011 Online and found, to my surprise, that it would no longer work; I would receive a Security Exception when ending retrieve tasks.

Using Fiddler, I found that CRM is trying to redirect me to the Live login page, which didn't make much sense, considering I was already logged in.

After some more attempts, I found that the errors were because I was accessing the service from a different thread than the UI thread.

Here's a quick example:

    //this will work
    private void button1_Click(object sender, RoutedEventArgs e)
    {
        var query = ctx.AccountSet;
        query.BeginExecute((result) =>
        {
            textBox1.Text = query.EndExecute(result).First().Name;
        }, null);
    }

    //this will fail
    private void button2_Click(object sender, RoutedEventArgs e)
    {
        System.Threading.Tasks.Task.Factory.StartNew(RestAsync);
    }

    void RestAsync()
    {
        var query = ctx.AccountSet;
        var async = query.BeginExecute(null, null);
        var task = System.Threading.Tasks.Task.Factory.FromAsync<Account>(async, (result) =>
        {
            return query.EndExecute(result).First(); // <- Exception thrown here
        });
        textBox1.Dispatcher.BeginInvoke(() =>
        {
            textBox1.Text = task.Result.Name;
        });
    }

It seems almost obvious that I'm missing some fundamentals on how threads use permissions. Since using a separate thread is preferable in my case, is there any way to "copy" the permissions / authentication? Perhaps some sort of impersonation?

EDIT: In case anyone else is struggling with this, using other threads (or Task, as the case may be) is possible as long as query.BeginExecute(null, null); is executed on the UI thread. You need a way to retrieve the returned IAsyncResult back to the calling thread, but you can do that using a ManualResetEvent.

But I'd still like to know why the darned permissions / authentication isn't shared between the threads...

like image 619
Shaamaan Avatar asked May 09 '12 09:05

Shaamaan


1 Answers

I am not quite sure, is this will help. But I found a description from by Jeffrey Richter page 770

"Like console applications, ASP.NET Web Form and XML Web Service applications allow any thread to do whatever it wants. When a thread pool thread starts to process a client’s request, it can assume the client’s culture (System.Globalization.CultureInfo), allowing the Web server to return culture-specific formatting for numbers, dates, and times.5 In addition, the Web server can assume the client’s identity (System.Security.Principal. IPrincipal) so that the server can access only the resources that the client is allowed to access. When a thread pool thread spawns an asynchronous operation, it will be completed by another thread pool thread, which will be processing the result of an asynchronous operation. While this work is being performed on behalf of the original client request, the culture and identity information doesn’t flow to the new thread pool thread by default so any additional work done on behalf of the client is now not using the client’s culture and identity information. Ideally, we want the culture and identity information to flow to the other thread pool threads that are still doing work on behalf of the same client."

And here is his example, I hope this will help.

private static AsyncCallback SyncContextCallback(AsyncCallback callback) 
{
  SynchronizationContext sc = SynchronizationContext.Current;
  // If there is no SC, just return what was passed in
  if (sc == null) return callback;
  // Return a delegate that, when invoked, posts to the captured SC a method that
  // calls the original AsyncCallback passing it the IAsyncResult argument
  return asyncResult => sc.Post(result => callback((IAsyncResult)result), asyncResult);
}

protected override void OnMouseClick(MouseEventArgs e) {
  // The GUI thread initiates the asynchronous Web request
  Text = "Web request initiated";
  var webRequest = WebRequest.Create("http://Wintellect.com/");
  webRequest.BeginGetResponse(SyncContextCallback(ProcessWebResponse), webRequest);
  base.OnMouseClick(e);
}

private void ProcessWebResponse(IAsyncResult result) {
  // If we get here, this must be the GUI thread, it's OK to update the UI
  var webRequest = (WebRequest)result.AsyncState;
  using (var webResponse = webRequest.EndGetResponse(result)) {
      Text = "Content length: " + webResponse.ContentLength;
  }
}

And here is what I am using in my application

 public override void UpdateCanvas(object parameter)
 {
      Action<GraphPane> startToUpdate = StartToUpdate;
       GraphPane selectedPane = Canvas.HostingPane.PaneList.Find(p =>  p.Title.Text.Equals(defaultPanTitle));
       startToUpdate.BeginInvoke(selectedPane, FormSyncContext.SyncContextCallback(RefreshCanvas), selectedPane);
 }

 public static AsyncCallback SyncContextCallback(AsyncCallback callback)
 {
       // Capture the calling thread's SynchronizationContext-derived object
       SynchronizationContext sc = SynchronizationContext.Current;

       // If there is no SC, just return what was passed in
       if (sc == null) return callback;

       // Return a delegate that, when invoked, posts to the captured SC a method that
       // calls the original AsyncCallback passing it the IAsyncResult argument
       return asyncResult => sc.Post(result => callback((IAsyncResult)result), asyncResult);
 }
like image 100
Angelapper Avatar answered Nov 13 '22 11:11

Angelapper