Step 2: Add the Code for the Custom Web Part

Note

This topic describes functionality that is part of the Infrastructure Update for Microsoft Office Servers. To download the update, see Description of the SharePoint Server 2007 infrastructure update: July 15, 2008.

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.

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 Office SharePoint Server 2007 SDK Search Samples resource page of the MSDN Code Gallery.

Procedures

To modify the code in PerUserAuthWebPart

  1. 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;
    
  2. In the following code line, replace WebControl with FederatedResultsWebPart and IPostBackEventHandler:

    public class PerUserAuthWebPart : FederatedResultsWebPart, IPostBackEventHandler
    
  3. 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;
    
  4. 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();
    }
    
  5. 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);
    }
    
  6. 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);
    }
    
  7. 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;
    }
    
  8. 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 "";
        }
    }
    
  9. 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:

  1. Retrieves the URL specified for the federated location.

  2. Determines the forms logon page URL for the federated location.

  3. Requests the logon page for the federated location.

  4. Displays the logon form in the UI, so the user can enter their credentials.

  5. If the user confirms that the forms logon attempt was successful, retrieves the logon input elements and values.

  6. Passes the logon input elements and values, along with the logon page request method and URL to the federated location using the FormsCredentials object, as demonstrated in the following code snippet.

    Creds = new FormsCredentials(credsLogonURL, credsLogonURL, credsLogonInput, credsSecureLogonInput, credsLogonMethod, cookies);
    

    Note

    This code will only work if the CookieCollection passed in the cookies parameter of the FormsCredentials constructor does not contain any Cookie objects.

  7. Stores the forms logon input elements, along with the logon page request method and URL 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.

Next Step

Step 3: Deploy the Custom Web Part

See Also

Reference

FormsCredentials
FederatedResultsWebPart
FederatedResultsDatasource
FederatedResultsDatasourceView
SearchResultsBaseWebPart
SearchResultsBaseDatasource
SearchResultsBaseDatasourceView

Concepts

Step 1: Set Up the Project for the Custom Web Part
Creating a Custom Federated Search Web Part with a Credentials UI
Federated Search Overview