Welcome to MSDN Blogs Sign in | Join | Help

InfoPath Team Blog

Tips and tricks to get the most out of Microsoft InfoPath

News

  • For questions, comments, and feedback please use the public newsgroup: microsoft.public.infopath
    This is provided "AS IS" with no warranties, and confers no rights. Use of included script samples and forms are subject to the terms specified in the Terms of Use.
Advanced server-side authentication for data connections, part 3
This is the final segment of my three part series. In the first part of this series I introduced the concepts involved in three tier authentication. Then I covered single sign-on with some code. Now we'll go one step further…
 
Authorization using the Web service proxy
 
InfoPath Forms Services includes a Web service proxy which can forward SOAP requests to a Web service to enable authorization for a data query.  The premise is simple – the proxy runs under an account that is trusted by the Web service.   The Web service authenticates the trusted account.  In addition, the proxy sends the identity of the calling user in the SOAP header using a WS-Security UsernameToken.  The Web service can then use the provided identity to determine what data to return from the query.
Setting up a connection to use the proxy is straightforward.  First of all, the connection has to use settings from a UDC file on the server.   The UseFormsServiceProxy attribute on the ServiceUrl element in the UDC file determines whether InfoPath and Forms Services will forward the Web service request using the proxy:
 
<udc:ServiceUrl UseFormsServiceProxy="true">
  http://myserver/proxydemo/service.asmx
</udc:ServiceUrl>
 
Everything else on this end is automatic.  It's a little more interesting on the Web service end.

It's tempting to consider using the WSE 2.0 library to consume the UsernameToken from the Web service request.  WSE 2.0 has methods to automatically consume WS-Security headers.  However, the default behavior of WSE 2.0 is to attempt to logon using the credentials in the UsernameToken.  It is possible to override the default behavior by specifying an alternate UsernameTokenHandler.  However, there is a more straightforward approach.
First, declare an array of SoapUnknownHeader at class scope in your service:
 
public SoapUnknownHeader[] unknownHeaders;
 
Then declare a SoapHeader attribute for the web method:
 
[WebMethod]
[SoapHeader("unknownHeaders")]
public ExpenseInfo[] GetMyExpenses()
 
In your web method, look for the UserName element in the header array, which is returned as an XML Document.
 
if (null != unknownHeaders && unknownHeaders.Length > 0)
{
     // look for a userNameToken and return the username if present
     try
     {
          strUserName = unknownHeaders[0].Element.CreateNavigator().SelectSingleNode("//*[local-name(.)='Username']").Value;
     }
     catch (Exception)
     {
     }
}
 
In the example, I'm searching for a Username element anywhere in the headers.  To be more precise, the UserName is within a UsernameToken element, which is in turn under a Security header in the WS-Security namespace.  The entire SOAP header sent by the proxy looks like this:
 
<SOAP-ENV:Header.xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <wsse:Security.xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis/security-secext-1.0.xsd">
    <wsse:UsernameToken>
      <wsse:Username>DOMAIN\username</wsse:Username>
    </wsse:UsernameToken>
  </wsse:Security>
</SOAP-ENV:Header>
 
I've put the code to get the username within a utility function called GetUserName.  In the function, I look first for the usernametoken.  If this is not present, I get the current Windows user.  In either case, I strip off the domain portion of the identifier and return the unadorned username.
 
private string GetUserName(SoapUnknownHeader[] unknownHeaders)
{
  bool fUserFound = false;
  string strUserName = string.Empty;
  if (null != unknownHeaders && unknownHeaders.Length > 0)
  {
  // look for a userNameToken and return the username if present
    try
    {
      strUserName = unknownHeaders[0].Element.CreateNavigator().SelectSingleNode("//*[local-name(.)='Username']").Value;
    }
    catch (Exception)
    {
    }
    if (string.Empty != strUserName)
    {
      fUserFound = true;
    }
  }
  if (!fUserFound)
  {
    // return the current Windows identity
    strUserName = WindowsIdentity.GetCurrent().Name;
  }
  // trim off the domain string if present
  int nDomainStringIndex = strUserName.IndexOf('\\');
  if (nDomainStringIndex > 0)
  {
    strUserName = strUserName.Substring(nDomainStringIndex + 1);
  }
  return strUserName;
}
 
In order to use the identity to retrieve data, I use it as part of the query against my database.  Here's the entire Webmethod. The code assumes that you have an Expense database with a table called Expenses, and that one column of the table, called Employee, contains the Windows username of the employee who filed the expense.
 
[WebMethod]
[SoapHeader("unknownHeaders")]
public ExpenseInfo[] GetMyExpenses()
{
  ExpenseInfo[] oReturn = null;
  DataSet oDS = new DataSet();
  string strQuery = string.Format("SELECT ExpenseID, Description, Date, Amount FROM Expenses WHERE Employee = '{0}'", GetUserName(unknownHeaders));
  try
  {
    m_Connection.Open();
    m_Adapter = new SqlDataAdapter(strQuery, m_Connection);
    m_Adapter.Fill(oDS, "Expenses");
    int cRows = oDS.Tables["Expenses"].Rows.Count;
    oReturn = new ExpenseInfo[cRows];
    for (int nRowIterator = 0; nRowIterator < cRows; nRowIterator++)
    {
      oRow = oDS.Tables["Expenses"].Rows[nRowIterator];
      oReturn[nRowIterator] = new ExpenseInfo();
      oReturn[nRowIterator].Amount = oRow["Amount"].ToString();
      oReturn[nRowIterator].ExpenseID = oRow["ExpenseID"].ToString();
      oReturn[nRowIterator].Date = oRow["Date"].ToString();
      oReturn[nRowIterator].Description = oRow["Description"].ToString();
    }
  }
  catch (Exception ex)
  {
  }
  if (m_Connection.State != ConnectionState.Closed)
  {
    m_Connection.Close();
  }
  return oReturn;
}
 
- Nick
Program Manager
 
Posted: Monday, July 03, 2006 4:40 PM by infopath
Filed under:

Comments

Dipper Park said:

Recently lots of Microsoft partners and customers are interest in the Form Server/Form Services in MOSS

# February 20, 2007 2:15 AM

Lucian Foppa said:

Hi Nick

Thanks for this great post ;-) It works perfectly. But i must change:

   // return the current Windows identity

   strUserName = WindowsIdentity.GetCurrent().Name;

to:

   strUserName = User.Identity.Name;

I have two question.

Is there possible to change the account on the webservice-proxy, which get the data from the webservice-host? Now, the webservice-proxy uses the anonymous account to get the data from the webservice-host.

I have worked for more than 8 hours on a infopath-form. Now, i couldn't publish it. The designer-detective bring following error:

date-field unbound / Server was unable to process request. ---> Object reference not set to an instance of an object.

But i couldn't found a date-field in the form which is unbound. Do you know this problem? I find none solutions for that problem in the internet.

Thanks and best regards...

Lucian

# August 29, 2007 11:25 AM

Remember Sammy Jankis said:

Hi, It&#39;s been a while since I posted. As you may imagine, July wasn&#39;t the most popular month

# August 20, 2008 1:23 AM
Anonymous comments are disabled
Page view tracker