Partager via


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>DOMAINusername</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

Comments

  • Anonymous
    February 19, 2007
    Recently lots of Microsoft partners and customers are interest in the Form Server/Form Services in MOSS

  • Anonymous
    August 29, 2007
    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

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

  • Anonymous
    June 19, 2009
    PingBack from http://mydebtconsolidator.info/story.php?id=8636

  • Anonymous
    November 02, 2010
    Hi there How does using the web service proxy work when connecting to existing Sharepoint web services? will it work better than using SSO? Kind Regards Riccardo

  • Anonymous
    November 02, 2010
    Hi there How does using the web service proxy work when connecting to existing Sharepoint web services? will it work better than using SSO? Kind Regards Riccardo

  • Anonymous
    February 13, 2014
    In your post you put a URL for a web service after you set the useFormProxy = "true".  If you use a known service that is fine but what if you just want to have a user connect to a form overriding the lists permissions for data pulled from a dropdown?  I am stumped!