Monday, 5 November 2007

Setting up different Authentication models in WSS 3.0 - Part 1



Setting up different Authentication models in WSS3.0
Currently WSS 3.0 supports following authentication models for intranet, extranet and Internet environments for their partner applications.

1. Windows
2. Forms

Forms authentication is preferred over windows authentication for extranet environments. The WSS 3.0 supports following Authentication providers.

1. Active Directory Membership Provider
2. SQL Membership provider
3. Single Sign On - SSO

In this article I have made an attempt to depict Authentication using Active Directory membership provider. And another challenging functionality implemented is, if for some reason, user can not logon, the page would display the reason along with logon details like account locked, expiry date and disabled features as shown in screen shot below:



Hence I have divided this article as 3 parts.
Part 1: Develop and Implement Custom Login for Active Directory
Part 2: Develop and Implement Custom Change Password for Active Directory

Part 1: Develop and Implement Custom Login for Active Directory


In order to implement this part, firstly I would need to create a class library which would override Login page of sharepoint provided page.

3. From Visual Studio, create a class library and name the project as SharePoint.Authentication.Custom.ActiveDirectory.

4. Since I need to deploy onto SharePoint, I have thought that, its better idea to put this dll into GAC. So, from the properties of the Project, under Signing tab, create a new snk and check delay sign.



5. Rename the class1.cs to CustomLoginPage.cs
6. Add a reference from .Net tab, select Windows SharePoint Services as shown below:



7. Add a reference from .Net tab and browse to Microsoft.SharePoint.ApplicationPages.dll located in C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\CONFIG\BIN\



8. Add a reference from COM tab, Active DS Library.



9. Add a reference from .Net tab, System.Web, System.DirectoryServices

10. In class CustomLoginPage.cs, add the following entries in the namespace section
Using Microsoft.SharePoint;
Using ActiveDs;
11. Now, we are ready to start making some changes to the class file.
12. Inherit the class from Microsoft.SharePoint.ApplicationPages.LoginPage class
13. Since we are interested to know logon details on error, I am gonna override on error event of Login page.as shown below



protected override void OnLoad(EventArgs e)
{
this.login.LoginError += new EventHandler(login_LoginError);
base.OnLoad(e);
}

void login_LoginError(object sender, EventArgs e)
{ }

14. In the above event handler, I would need to call login details if user can not logon. Hence for the simplicity, I have created a simple class and then populated data. Below show is the class.

public class ADResult
{
public ADResult() { }

private bool _locked;

public bool Locked
{
get { return _locked; }
set { _locked = value; }
}

private bool _disabled;

public bool Disabled
{
get { return _disabled; }
set { _disabled = value; }
}

private string _lastLogon;

public string LastLogon
{
get { return _lastLogon; }
set { _lastLogon = value; }
}

private int _expiresIn;

public int ExpiresIn
{
get { return _expiresIn; }
set { _expiresIn = value; }
}

private string _errorMessage;

public string ErrorMessage
{
get { return _errorMessage; }
set { _errorMessage = value; }
}

private bool _valid;

public bool Valid
{
get { return _valid; }
set { _valid = value; }
}

}

15. Next step is create a method GetLoginDetails() which actually would query Active Directory and return the ADResult object as shown below:

Also, You might wonder on one thing, why i have used SPSecurity.RunXXX(delegate()), this is because the code will not run until it is trusted in Sharepoint, hence it is required to elevate code security level.


private ADResult GetLoginDetails(string username)
{
ADResult adResult = new ADResult();
try
{
string strLoginName = username;
int iPosition = strLoginName.IndexOf("\\") + 1;
strLoginName = strLoginName.Substring(iPosition);

DirectoryEntry entry = new DirectoryEntry(ADPATH); //ADPATH is path of Active dir
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = "(SAMAccountName=" + strLoginName + ")";

Microsoft.SharePoint.SPSecurity.RunWithElevatedPrivileges(delegate()
{
SearchResult result = search.FindOne();
entry = result.GetDirectoryEntry();
});

try
{
Microsoft.SharePoint.SPSecurity.RunWithElevatedPrivileges(delegate()
{
object o = entry.InvokeGet("IsAccountLocked");

if (o != null)
{
bool locked = (bool)o;
adResult.Locked = locked;
};

bool isDisabled;
isDisabled = ((int)entry.Properties["userAccountControl"].Value & (int)ADS_USER_FLAG.ADS_UF_ACCOUNTDISABLE) != 0;
adResult.Disabled = isDisabled;

object lastlogon = entry.InvokeGet("LastLogin");
if (lastlogon != null)
{
DateTime LastLogon = (DateTime)lastlogon;
adResult.LastLogon = "Last Logon was at : " + LastLogon.ToLongDateString() + " " + LastLogon.ToShortTimeString();
}


LargeInteger liAcctPwdChange = entry.Properties["pwdLastSet"].Value as LargeInteger;

// Convert the highorder/loworder parts of the property pulled to a long.
long dateAcctPwdChange = (((long)(liAcctPwdChange.HighPart) << 32) + (long)liAcctPwdChange.LowPart);

// Convert FileTime to DateTime and get what today's date is.
DateTime dtNow = DateTime.Now;
DateTime dtAcctPwdChange = DateTime.FromFileTime(dateAcctPwdChange);
string strAcctPwdChange = DateTime.FromFileTime(dateAcctPwdChange).ToString();
string strAcctPwdExpires = DateTime.FromFileTime(dateAcctPwdChange).ToString();

// Calculate the difference between the date the pasword was changed, and what day it is now and display the # of days.
TimeSpan time;
time = dtNow - dtAcctPwdChange;

adResult.ExpiresIn = time.Days;
adResult.Valid = true;
}
);
}
catch (Exception ex)
{
adResult.ErrorMessage = ex.Message;
}
}
catch (Exception e)
{
adResult.ErrorMessage = e.Message;
}

return adResult;
}

16. That’s it, and finally I would need to build UI from the object returned. Hence I have created BuildLoginDetails(ADResult reault) method to build UI as shown below:


private string BuildLoginDetails(ADResult adResult)
{

if (!adResult.Valid)
{
return string.Empty;
}

StringBuilder sb = new StringBuilder();

if (adResult.Disabled)
{
sb.Append("Account Disabled");
}
else
{
if (adResult.Locked)
{
sb.Append("Account Locked");
}

if (adResult.ExpiresIn == 0 && !adResult.Locked)
{
sb.Append("Password Expired: ");
sb.Append("Change Password");
}
else if (!adResult.Locked)
{
sb.Append(String.Format("
Password expires in {0} days", adResult.ExpiresIn));
}

sb.Append("
" + adResult.LastLogon + "");
}

return sb.ToString();
}


17. From the string returned from the above method, can be displayed on UI with a simple label in the event handler as shown below:



void login_LoginError(object sender, EventArgs e)
{
System.Web.UI.WebControls.Label lblDetails = new System.Web.UI.WebControls.Label();
lblDetails.Text = this.BuildLoginDetails(this.GetLoginDetails(this.login.UserName));
this.login.Controls.Add(lblDetails);
}


18. Now, build the class library, and drop into GAC.
19. Finally, Login page should be created. Navigate to C:\Program files\Common files\Microsoft shared\web service extensions\12\layouts folder . Copy Login page and paste it as CustomLogin.aspx.. Couple of changes needed on the CustomLogin.aspx.
No one is , The aspx page should inherit from CustomLoginPage.cs which we had created earlier.

<%@ Page Language="C#" Inherits="SharePoint.Authentication.Custom.ActiveDirectory.CustomLoginPage" MasterPageFile="~masterUrl/default.master" %>


Second is, add an assembly entry to use the dll we had created. As



<%@ Assembly Name=" SharePoint.Authentication.Custom.ActiveDirectory, Version=1.0.0.0, Culture=neutral, PublicKeyToken=12bc1ebe5d50d609"%>



in your case the PublicKeyToken will be different, make sure you copy right token from GAC or by using good old friend .Net Reflector

Third is, Importing namespaces. Add below shown entries.


<%@ Import Namespace="ActiveDs" %>


20. We have now developed CustomLogin page. Next part is customising SharePoint Application to use the Custom Login page.
21. The following steps provide modification needed to Web.config file for using ASP.NET forms authentication to use an Active Directory service membership provider.

1.Open web.config file from the path: c:\Inetpub\wwwroot\web.config and add below shown entries under section of web.config file


<connectionStrings>
<add name="ADConnectionString"
connectionString=
" LDAP://ptr.headquarters.com/DC=ptr,DC=headquarters,DC=com" />
</connectionStrings>

2. And add below shown membership provider under section


<membership defaultProvider="ADirectoryMembershipProvider">
<providers>
<add connectionStringName="ADConnectionString" name="ADirectoryMembershipProvider"
type="System.Web.Security.ActiveDirectoryMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
enableSearchMethods="true" attributeMapUsername="sAMAccountName"
/>
</providers>
</membership>

4. From the Central Administration page of WSS 3.0, navigate to Application management.
5. Create New Intranet application from the link Create/Extend New Web Application as shown in fig below:






6. Now, Extend this Intranet application to external users by clicking on Application Management\Create new or extend web application link from the Central Administration Page.
7. The details should be provided in order to extend the intranet application is shown fig below:




8. Click OK to continue to extend web application.
9. Navigate to Application Management\Authentication Providers and select Extranet Zone and provide details as show in fig below:



10. Click Ok to continue.
11. open IIS Manager, select the Web site choosen for authentication, go to properties by Right click. And then to ASP.Net tab. Click on Edit Configuration settings and move to Authentication tab. On this screen, modify Login.aspx to CustomLogin.aspx as show in screen shot.




by doing so, we are instructing the Membership provider to use CustomLogin.aspx. and the rest IIS.

22. From the browser, enter the URL of extranet web application, which should display the login screen as shown below:



for some reasons, user can not logon, the following UI would be displayed.



I think this article make sense to implement and delve challenges.

ufff.. thts it..!
In next part, will explain how to implement change password, until then bye..







1 comment:

Unknown said...

Great and useful explanation!
Could you please show us a.s.a.p. the next part about "how to implement change password"??