Saturday, March 14, 2009

Secure WCF Services with Authentication Service

We can use WCF Authentication Service to authenticate users with ASP.NET membership provider. However, other WCF services are not protected by the authentication service out-of-the-box. That is, WCF services is not using ASP.NET forms authentication.

Fortunately, it is not hard to enable it. The magic point is set the HttpContext.Current.User in Global.asax
public class Global : System.Web.HttpApplication
{
// other methods snipped...
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
HttpCookie ticketCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
if (null == ticketCookie)
{
return;
}

FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(ticketCookie.Value);
if (null != ticket)
{
HttpContext.Current.User = new GenericPrincipal(new FormsIdentity(ticket), null);
}
}
}
In the service you want to protect, set the requirement mode to allowed or required.
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class PrimeService : IPrimeService
Then, throw in the following checking at the beginning of the method.
            if (!HttpContext.Current.User.Identity.IsAuthenticated)
{
throw new FaultException<SecurityAccessDeniedException>(new SecurityAccessDeniedException());
}
That would be good enough for Silverlight client. For .Net WCF client, you need to handle the HTTP cookies by yourself (Authentication Service is using the authentication ticket in cookies). Detail discussion can be found in the article in Shane's Shelf.

Tuesday, March 03, 2009

Silverlight Multi-part File Upload Form Post

Silverlight not actually very web friendly. It lacks of built-in support for the current HTML form post protocol. So, we have to write it our own. I have written an extension class and two serializer classes, one for normal form post (DataContractQueryStringSerializer) while the other for multipart upload form post (DataContractMultiPartSerializer).

If you only want to have the working code, just copy the code below. Detail explanation is available at Multi-Part Form Post in Shane's Shelf

  public static class Extensions
  {
      public static void PostFormAsync(this HttpWebRequest request, object parameters, AsyncCallback callback)
      {
          request.Method = "POST";
          request.ContentType = "application/x-www-form-urlencoded";
          request.BeginGetRequestStream(new AsyncCallback(asyncResult =>
          {
              Stream stream = request.EndGetRequestStream(asyncResult);
              DataContractQueryStringSerializer ser = new DataContractQueryStringSerializer();
              ser.WriteObject(stream, parameters);
              stream.Close();
              request.BeginGetResponse(callback, request);
          }), request);
      }

      public static void PostMultiPartAsync(this HttpWebRequest request, object parameters, AsyncCallback callback)
      {
          request.Method = "POST";
          string boundary = "---------------" + DateTime.Now.Ticks.ToString();
          request.ContentType = "multipart/form-data; boundary=" + boundary;
          request.BeginGetRequestStream(new AsyncCallback(asyncResult =>
          {
              Stream stream = request.EndGetRequestStream(asyncResult);

              DataContractMultiPartSerializer ser = new DataContractMultiPartSerializer(boundary);
              ser.WriteObject(stream, parameters);
              stream.Close();
              request.BeginGetResponse(callback, request);
          }), request);
      }
  }

  public class DataContractQueryStringSerializer
  {
      public void WriteObject(Stream stream, object data)
      {
          StreamWriter writer = new StreamWriter(stream);
          if (data != null)
          {
              if (data is Dictionary<string, string>)
              {
                  foreach (var entry in data as Dictionary<string, string>)
                  {
                      writer.Write("{0}={1}&", entry.Key, entry.Value);
                  }
              }
              else
              {
                  foreach (var prop in data.GetType().GetFields())
                  {
                      foreach (var attribute in prop.GetCustomAttributes(true))
                      {
                          if (attribute is DataMemberAttribute)
                          {
                              DataMemberAttribute member = attribute as DataMemberAttribute;
                              writer.Write("{0}={1}&", member.Name ?? prop.Name, prop.GetValue(data));
                          }
                      }
                  }
                  foreach (var prop in data.GetType().GetProperties())
                  {
                      if (prop.CanRead)
                      {
                          foreach (var attribute in prop.GetCustomAttributes(true))
                          {
                              if (attribute is DataMemberAttribute)
                              {
                                  DataMemberAttribute member = attribute as DataMemberAttribute;
                                  writer.Write("{0}={1}&", member.Name ?? prop.Name, prop.GetValue(data, null));
                              }
                          }
                      }
                  }
              }
              writer.Flush();
          }
      }
  }

  public class DataContractMultiPartSerializer
  {
      private string boundary;
      public DataContractMultiPartSerializer(string boundary)
      {
          this.boundary = boundary;
      }

      private void WriteEntry(StreamWriter writer, string key, object value)
      {
          if (value != null)
          {
              writer.Write("--");
              writer.WriteLine(boundary);
              if (value is FileInfo)
              {
                
                  FileInfo f = value as FileInfo;
                  writer.WriteLine(@"Content-Disposition: form-data; name=""{0}""; filename=""{1}""", key, f.Name);
                  writer.WriteLine("Content-Type: application/octet-stream");
                  writer.WriteLine("Content-Length: " + f.Length);
                  writer.WriteLine();
                  writer.Flush();
                  Stream output = writer.BaseStream;
                  Stream input = f.OpenRead();
                  byte[] buffer = new byte[4096];
                  for (int size = input.Read(buffer, 0, buffer.Length); size > 0; size = input.Read(buffer, 0, buffer.Length))
                  {
                      output.Write(buffer, 0, size);
                  }
                  output.Flush();
                  writer.WriteLine();
              }
              else
              {
                  writer.WriteLine(@"Content-Disposition: form-data; name=""{0}""", key);
                  writer.WriteLine();
                  writer.WriteLine(value.ToString());
              }
          }
      }

      public void WriteObject(Stream stream, object data)
      {
          StreamWriter writer = new StreamWriter(stream);
          if (data != null)
          {
              if (data is Dictionary<string, object>)
              {
                  foreach (var entry in data as Dictionary<string, object>)
                  {
                      WriteEntry(writer, entry.Key, entry.Value);
                  }
              }
              else
              {
                  foreach (var prop in data.GetType().GetFields())
                  {
                      foreach (var attribute in prop.GetCustomAttributes(true))
                      {
                          if (attribute is DataMemberAttribute)
                          {
                              DataMemberAttribute member = attribute as DataMemberAttribute;
                              WriteEntry(writer, member.Name ?? prop.Name, prop.GetValue(data));
                          }
                      }
                  }
                  foreach (var prop in data.GetType().GetProperties())
                  {
                      if (prop.CanRead)
                      {
                          foreach (var attribute in prop.GetCustomAttributes(true))
                          {
                              if (attribute is DataMemberAttribute)
                              {
                                  DataMemberAttribute member = attribute as DataMemberAttribute;
                                  WriteEntry(writer, member.Name ?? prop.Name, prop.GetValue(data, null));
                              }
                          }
                      }
                  }
              }
          }
          writer.Write("--");
          writer.Write(boundary);
          writer.WriteLine("--");
          writer.Flush();
      }
  }
The usage is as follows:
First a PHP file
<?php
print_r($_REQUEST);
$src = $_FILES['y']['tmp_name'];
$dest = "C:\\Windows\\Temp\\".$_FILES['y']['name'];
echo $src;
echo "\r\n";
echo $dest;
echo @copy($src, $dest);
?>
Then the Page control
    public partial class Page : UserControl
  {
      public Page()
      {
          InitializeComponent();
          // Create a request object
          HttpWebRequest request = (HttpWebRequest)WebRequest.Create(new Uri("http://localhost/rms/test.php"));
          OpenFileDialog dlg = new OpenFileDialog();
          if (dlg.ShowDialog().Value)
          {
              request.PostMultiPartAsync(new Dictionary<string, object> { { "x", "1" }, { "y", dlg.File } }, new AsyncCallback(asyncResult =>
              {
                  HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asyncResult);

                  Stream responseStream = response.GetResponseStream();
                  StreamReader reader = new StreamReader(responseStream);
                  this.Dispatcher.BeginInvoke(delegate
                  {
                      // output is a TextBlock
                      output.Text = reader.ReadToEnd();
                      response.Close();
                  });
              }));
          }
      }
  }
Since it is able to serialize data contract, you could actually replace
new Dictionary<string, object> { { "x", "1" }, { "y", dlg.File } }

with
new Point(){X=1, Y=2}

given the point class is like this:
    [DataContract]
  public class Point
  {
      [DataMember]
      public int X { get; set; }
      [DataMember(Name="y")]
      public int Y { get; set; }
  }