Step 2: Add the Code for the Custom Web Part
The custom federated search results Web Part described in this walkthrough includes a user interface (UI) for the user to enter user credentials. The Web Part then passes these credentials to the federated results data source.
The sample Web Part described in this walkthrough works for federated locations that are configured to use the following per-user authentication types:
Basic authentication
Digest authentication
NTLM authentication
The credentials are encrypted and then stored in cookies. The cookies exist only during the user's browsing session; when the browser is closed, the cookies expire, and they are no longer available. You can modify this sample to extend the cookie expiration so that the values are persisted.
Important
When you pass the credentials in a cookie between the browser and the server, we recommend that you use Secure Sockets Layer (SSL) to secure the communication between the client browser and the Web server. For more information, see "Security Considerations" in Step 3: Deploy the Custom Web Part [Search Server 2008].
You can download the complete code for the custom federated search results Web Part sample from the Custom Federated Results Web Part Sample release tab, on the Microsoft Search Server 2008 SDK Samples resource page of the MSDN Code Gallery.
Procedures
To modify the code in PerUserAuthWebPart
Add the following namespace directives near the top of the code in the PerUserAuthWebPart.cs file.
using System.Xml.Serialization; using System.Xml.XPath; using System.Net; using System.Web.Security; using Microsoft.Office.Server.Search.WebControls; using Microsoft.Office.Server.Search.Administration;
In the following code line, replace WebControl with FederatedResultsWebPart and IPostBackEventHandler:
public class PerUserAuthWebPart : FederatedResultsWebPart, IPostBackEventHandler
Add the following line of code above the class declaration for PerUserAuthWebPart.
[XmlRoot(Namespace = "CustomFederatedResultsSample")]
Add the following code below the class declaration.
private TextBox UsernameTextBox = new TextBox(); private TextBox PasswordTextBox = new TextBox(); private Button LogOnButton = new Button(); private Button LogOutButton = new Button(); private string Username = ""; private string Domain = ""; private string Password = ""; private ICredentials Creds; XPathNavigator Results; Table controlsTable; Label statusLabel = new Label(); TableRow LoginControlsRow;
Override the OnLoad, OnPreRender, and CreateChildControls methods by using the following code.
protected override void OnLoad(EventArgs e) { try { this.ShowMessages = false; base.OnLoad(e); } catch (Exception ex) { string x = ex.Message.ToString(); } } protected override void OnPreRender(EventArgs e) { base.OnPreRender(e); base.CreateChildControls(); } protected override void CreateChildControls() { this.CreateLogonControls(); }
Add the following code for the CreateLogonControls method, which creates and loads the controls for the custom interface to request the user's credentials.
protected void CreateLogonControls() { controlsTable = new Table(); controlsTable.Width = Unit.Percentage(100); controlsTable.Attributes.Add("cellspacing", "1"); controlsTable.Attributes.Add("cellpadding", "1"); LoginControlsRow = new TableRow(); TableRow LogoutControlsRow = new TableRow(); TableCell cell = new TableCell(); this.UsernameTextBox = new TextBox(); this.UsernameTextBox.ID = "UserName"; UsernameTextBox.Visible = true; UsernameTextBox.EnableViewState = true; cell.Controls.Add(UsernameTextBox); LoginControlsRow.Controls.Add(cell); cell = new TableCell(); PasswordTextBox = new TextBox(); this.PasswordTextBox.ID = "Password"; PasswordTextBox.TextMode = TextBoxMode.Password; PasswordTextBox.Visible = true; PasswordTextBox.EnableViewState = true; cell.Controls.Add(PasswordTextBox); LoginControlsRow.Controls.Add(cell); cell = new TableCell(); this.LogOnButton = new Button(); LogOnButton.Text = "Logon"; LogOnButton.Visible = true; cell.Controls.Add(LogOnButton); LoginControlsRow.Controls.Add(cell); LoginControlsRow.Width = Unit.Percentage(100); cell = new TableCell(); statusLabel.Text = ctrl; cell.Controls.Add(statusLabel); TableRow statusRow = new TableRow(); statusRow.Cells.Add(cell); cell = new TableCell(); this.LogOutButton = new Button(); LogOutButton.Text = "Logout"; LogOutButton.Visible = true; cell.Controls.Add(LogOutButton); statusRow.Cells.Add(cell); controlsTable.Rows.Add(statusRow); controlsTable.Rows.Add(LoginControlsRow); this.Controls.Add(controlsTable); }
Override the ConfigureDataSourceProperties method by using the following code.
protected override void ConfigureDataSourceProperties() { base.ConfigureDataSourceProperties(); FederatedResultsDatasource fedrds = this.DataSource as FederatedResultsDatasource; string locName = ""; LocationConfiguration locationConfig = null; if (fedrds.Location != null) { locName = fedrds.Location; LocationConfigurationCollection locations = SearchContext.Current.LocationConfigurations; locations.TryGetLocationConfigurationByInternalName(locName, out locationConfig); } try { if (Page.Request.Cookies["Username"] != null) { Username = DecryptedCookieData(Page.Request.Cookies["Username"].Value); } if (Page.Request.Cookies["Domain"] != null) { Domain = DecryptedCookieData(Page.Request.Cookies["Domain"].Value); } if (Page.Request.Cookies["Password"] != null) { Password = DecryptedCookieData(Page.Request.Cookies["Password"].Value); } } catch (Exception e) { string exception = e.Message; } if (Page.IsPostBack) { if (GetPostBackControlText() == "Logout") { Username = ""; Password = ""; Domain = ""; } else if (GetPostBackControlText() == "Logon") { if (UsernameTextBox.Text.Contains(@"\")) { string[] domainUsername = UsernameTextBox.Text.Split('\\'); Domain = domainUsername[0]; Username = domainUsername[1]; } else { Username = UsernameTextBox.Text; } Password = PasswordTextBox.Text; } } Page.Response.Cookies["Username"].Value = EncryptedCookieData(Username,"Username"); Page.Response.Cookies["Password"].Value = EncryptedCookieData(Password, "Password"); Page.Response.Cookies["Domain"].Value = EncryptedCookieData(Domain, "Domain"); if (fedrds.Location != null) { Creds = new NetworkCredential(Username, Password, Domain); } if (fedrds.UserCredentials.ContainsKey(locName)) { fedrds.UserCredentials.Remove(locName); } fedrds.UserCredentials.Add(locName, Creds); }
Create methods to encrypt and decrypt the cookie data by using the following code.
private string EncryptedCookieData(string cookieValue,string cookieName) { string returnString = ""; FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, cookieName, DateTime.Now, DateTime.Now.AddMinutes(30), false, cookieValue, "/"); returnString = FormsAuthentication.Encrypt(ticket); return returnString; } private string DecryptedCookieData(string cookie) { string returnString = ""; FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookie); returnString = ticket.UserData; return returnString; }
Add the following code for the GetPostBackControlText method, which returns a string indicating whether the postback was triggered by the Logon button-click event, the Logout button-click event, or by neither.
private string GetPostBackControlText() { Control control = null; Control c = null; foreach (string controlName in Page.Request.Form) { c = Page.FindControl(controlName); if (c is System.Web.UI.WebControls.Button) { control = c; break; } } if (control != null) { return ((Button)(control)).Text; } else { return ""; } }
Override the GetXPathNavigator method to display or hide the Login/Logout controls based on whether results were returned or not, by using the following code.
protected override XPathNavigator GetXPathNavigator(string viewPath) { Results = base.GetXPathNavigator(viewPath); if (Results == null) /* Login failed, or no credentials were entered, so Login controls should be displayed. */ { LogOutButton.Visible = false; LoginControlsRow.Visible = true; } else /* Login succeeded, so hide login controls, and show Logout button. */ { LogOutButton.Visible = true; LoginControlsRow.Visible = false; } return Results; }
Modifying the Code to Support Cookie-Based Forms Authentication
The sample Web Part described in this walkthrough does not work for federated locations that are configured to use forms authentication. You may want to modify the sample to support cookie-based forms authentication. To do so, modify the code so that it does the following:
Adds a property to the Web Part that allows the site administrator to specify the URL for the federated location's forms authentication login page.
Requests the login page for the federated location.
Displays the login form in the UI.
Generates a forms authentication ticket by using the credentials the user enters into the login form in the UI.
Passes the forms authentication ticket in the request to the federated location, by using an ICredentials object.
Stores the forms authentication ticket in an encrypted cookie or using a Single Sign-On (SSO) provider.
Important
If you pass the credentials in a cookie between the browser and the server, we recommend that you use SSL to secure the communication between the client browser and the Web server. For more information, see "Security Considerations" in Step 3: Deploy the Custom Web Part [Search Server 2008].
Next Steps
Step 3: Deploy the Custom Web Part [Search Server 2008]
See Also
Concepts
Step 1: Set Up the Project for the Custom Web Part [Search Server 2008]
Creating a Custom Federated Search Web Part with a Credentials UI [Search Server 2008]
Federated Search Overview [Search Server 2008]