Thursday, February 25, 2010

Login into Sharepoint with old AD password

Recently I encountered with strange issue: users were able to login into Sharepoint with old password after their AD password was changed. Actually they were able to login both with old and new passwords. We use FBA with AD as users storage, i.e. users accounts and passwords are stored in Active Directory. So when user changes his password – he actually changes password in AD.

As membership provider we use OTB LdapMembershipProvider configured like this in web.config:

   1: <membership defaultProvider="fbaMembers">
   2:   <providers>
   3:     <add name="fbaMembers" type="Microsoft.Office.Server.Security.LdapMembershipProvider,
   4: Microsoft.Office.Server, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71E9BCE111E9429C"
   5: server="localhost" port="389" useSSL="false" userDNAttribute="distinguishedName"
   6: userNameAttribute="userPrincipalName" userContainer="..."
   7: userObjectClass="person" userFilter="(|(ObjectCategory=group)(ObjectClass=person))"
   8: scope="Subtree" otherRequiredUserAttributes="sn,givenname,cn" />
   9:   </providers>
  10: </membership>

By1st look at the issue I thought that this is a bug. I tried to restart IIS, reopen browser – but user still was able to login with both passwords. I was sure that user’s password was changed successfully as I checked it with “Run As…” command – program could be run only if I specified new password in “Run As…” credentials window. More strange thing is that after some time old password became unusable, i.e. only new password remains valid for login into Sharepoint site.

After some investigation I found the following KB article which says that this behavior is actually by design:

“Microsoft Windows Server 2003 Service Pack 1 (SP1) modifies NTLM network authentication behavior. After you install Windows Server 2003 SP1, domain users can use their old password to access the network for one hour after the password is changed.

The lifetime period of the old password can be configured by editing the registry on a domain controller. No restart is required for this registry change to take effect

Note The lifetime period is set in minutes. If this registry value is not set, the default lifetime period for an old password is 60 minutes”

I.e. LdapMembershipProvider treats old password as valid password for authentication during 1 hour by default after password was changed.

Sunday, February 21, 2010

The basics of navigation in Sharepoint

With this blog post I introduce several articles about navigation in Sharepoint. Here I would like to describe basic architecture of MOSS navigation. The understanding of these basics is required to customize navigation in Sharepoint.

Important point is that Sharepoint navigation is built on top of ASP.Net navigation. I.e. all that you know about ASP.Net navigation will help you in Sharepoint as well. As you know ASP.Net navigation architecture separates UI and navigation data. It is based on 3 main components:

  • navigation UI controls which display navigation items. In Sharepoint most widely used navigation control is AspMenu control defined in OTB master pages of publishing sites. As said here:

The AspMenu class that ships with WSS 3.0 (and by extension MOSS 2007) is nearly identical in behavior to ASP.NET 2.0’s Menu class (as the name implies). AspMenu derives from Menu and adds tweaks to work around a few reasonably well known annoyances and provides improved highlighting support.

  • navigation data source - intermediate abstraction layer between UI controls and navigation data returned by navigation providers. You can use ASP.Net SiteMapDataSource control or PortalSiteMapDataSource from MOSS API which inherits SiteMapDataSource. The main purpose of these controls – is to add additional configuration abilities for developers (e.g. specify starting node URL, starting offset, etc.) and bind to site map data and presents its view. I.e. SiteMapDataSource returns instance of SiteMapDataSourceView or SiteMapHierarchicalDataSourceView - it’s kind of “view model” for navigation;
  • navigation providers which return navigation data (SiteMapNode instances). In Sharepoint navigation data is stored in content database, so navigation providers fetch navigation nodes from db – this is important point to note. SPSiteMapProvider, SPContentMapProvider, and SPNavigationProvider are part of the WSS classes; PortalSiteMapProvider is part of the MOSS object model.

Here is a figure from msdn which explains how these components are used in OTB master page of publishing site:

schema

Three PortalSiteMapDataSource controls are embedded. Each maps to a definition for a provider within the web.config file of the site. The SiteMapDataSourceRoot control uses the CombinedNavSiteMapProvider control and is connected to the breadcrumb controls. As its name suggests, it combines the two other PortalSiteMapDataSource controls into one. The siteMapDataSource1 control provides the data for the top navigation bar, and the siteMapDS control provides the data for the vertical navigation control

In order to avoid terms mess I would like to notice that “top navigation bar” here is “global navigation” in terms of Sharepoint and “vertical navigation control” is “current navigation”.

So what happens when we change navigation in OTB “Site Navigation Settings” page of the Sharepoint site (Site Actions > Site Settings > Modify All Site Settings > Navigation)?

image

Sharepoint saves changes made on this page in content database. I.e. here you can affect only SiteMapNode instances returned by providers. All visual appearance settings are still available only from page which contains SiteMapDataSource and UI controls (master page). Notice that you can also change navigation settings programmatically using the following members of PublishingWeb class:

   1: public sealed class PublishingWeb
   2: {
   3:     ...
   4:     // Methods
   5:     public void ExcludeFromNavigation(bool useGlobal, Guid item);
   6:     public void IncludeInNavigation(bool useGlobal, Guid item);
   7:  
   8:     // Properties
   9:     public SPNavigationNodeCollection CurrentNavigationNodes { get; }
  10:     public SPNavigationNodeCollection GlobalNavigationNodes { get; }
  11:     public bool IncludeInCurrentNavigation { get; set; }
  12:     public bool IncludeInGlobalNavigation { get; set; }
  13:     public bool IncludePagesInNavigation { get; set; }
  14:     public bool IncludeSubSitesInNavigation { get; set; }
  15:     public bool InheritCurrentNavigation { get; set; }
  16:     public bool InheritGlobalNavigation { get; set; }
  17:     public AutomaticSortingMethod NavigationAutomaticSortingMethod { get; set; }
  18:     public OrderingMethod NavigationOrderingMethod { get; set; }
  19:     public bool NavigationShowSiblings { get; set; }
  20:     public bool NavigationSortAscending { get; set; }
  21:     ...
  22: }

Gary Lapointe implemented 2 stsadm commands which can be used for changing navigation settings using stsadm utility: gl-setnavigationnodes and gl-copynavigation. Internally these commands use the mentioned above members of PublishingWeb class. Details can be found in his blog.

That’s what I wanted to write about basics of Sharepoint navigation. Hope it will help you to increase understanding of internal Sharepoint mechanisms. Based on this knowledge you can create you own custom navigation providers in MOSS. I’m going to write separate post about it.

Update 2010-03-14. Also you can use SPWeb.Navigation property which returns instance of SPNavigation class with the following public interface:

   1: public sealed class SPNavigation
   2: {
   3:     // Fields
   4:     public SPNavigationNode GetNodeById(int id);
   5:     public SPNavigationNode GetNodeByUrl(string url);
   6:  
   7:     public SPNavigationNodeCollection GlobalNodes { get; }
   8:     public SPNavigationNode Home { get; }
   9:     public SPNavigationNodeCollection QuickLaunch { get; }
  10:     public SPNavigationNodeCollection TopNavigationBar { get; }
  11:     public bool UseShared { get; set; }
  12:     public SPWeb Web { get; }
  13: }

Thursday, February 18, 2010

UnauthorizedActionResult for ASP.Net MVC

Here is the simple class which represents action result for not authorized access:

   1: public class UnauthorizedActionResult : ActionResult
   2: {
   3:     public override void ExecuteResult(ControllerContext context)
   4:     {
   5:         context.HttpContext.Response.StatusCode =
   6:               (Int32)HttpStatusCode.Unauthorized;
   7:         context.HttpContext.Response.ContentType = "text/html";
   8:         using (var sw = new StreamWriter(context.HttpContext.Response.OutputStream, Encoding.UTF8))
   9:         {
  10:             using (var tw = new HtmlTextWriter(sw))
  11:             {
  12:                 tw.RenderBeginTag(HtmlTextWriterTag.Html);
  13:                 tw.RenderBeginTag(HtmlTextWriterTag.Head);
  14:                 
  15:                 tw.RenderBeginTag(HtmlTextWriterTag.Title);
  16:                 tw.Write(UnauthorizedActionResources.Titlte);
  17:                 tw.RenderEndTag();
  18:  
  19:                 tw.RenderEndTag();
  20:  
  21:                 tw.RenderBeginTag(HtmlTextWriterTag.Body);
  22:                 tw.Write(UnauthorizedActionResources.Text);
  23:                 tw.RenderEndTag();
  24:  
  25:                 tw.RenderEndTag();
  26:             }
  27:         }
  28:     }
  29: }

It can be used like this in your controller:

   1: public class AdminController : Controller
   2: {
   3:     private IUserSession userSession;
   4:  
   5:     public AdminController(IUserSession userSession)
   6:     {
   7:         this.userSession = userSession;
   8:     }
   9:  
  10:     [HttpGet]
  11:     public ActionResult Panel()
  12:     {
  13:         var user = this.userSession.GetCurrentUser();
  14:         if (user == null || !user.IsAdmin)
  15:         {
  16:             return new UnauthorizedActionResult();
  17:         }
  18:         return View("Panel");
  19:     }
  20: }

If this action result is returned ASP.Net MVC will redirect user on the view which corresponds to loginUrl attribute of forms tag in your web.config.

Also notice that if you will use another http response code (e.g. 403 Forbidden) IE will not show your custom error message if it has size less than 512 bytes – it will show its own “friendly error message”: http://stackoverflow.com/questions/1492444/ie-7-not-showing-my-custom-401-page/1492472.

Saturday, February 13, 2010

Speaking on SPb ALT.Net with overview of database migration tools

On this week (February, 11) I made an overview of database migration and versioning tools on SPb ALT.Net meeting. Materials and details can be found here. There were several tool concerned:

Lets briefly summarize advantages and disadvantages of the mentioned tools.

Tarantino.Net (used in http://code.google.com/p/codecampserver open source ASP.Net MVC project) – uses NHibernate schema export feature and SQL Compare tool for generating update scripts. Advantage of such approach is that developers don’t work directly with SQL when they need to synchronize modified model (expressed in c# code) with db schema as Tarantino will make it automatically. And this is really great feature. But in real world there can be situations when NHibernate schema export don’t provide exactly desired result by default and in this case some efforts should be made to fine tune it (e.g. if return to examples shown on meeting – NHibernate generated nullable column for decimal value type – but more desired behaviour was generating of not-nullable column with default value). Also NHibernate schema export affects those entities which are persisted with NHibernate and if you need to modify schema of table which is not included in ORM (or you use several ORM frameworks in your project) then you will need to do extra efforts also.

Migrator.Net supports code-first approach. Thus you have compile time checking and more control over resulting SQL schema as all migrations are written by developer via c# code. Migrator.Net has a good and intuitive API which helps to write migrations. But with this you will have more manual work as all database assets should be described in code one by one. Also it have some bugs (what program doesn’t?) – if return again to examples on meetings there was little confuse when Migrator.Net failed to downgrade db to version = 0 (version of database without schema) because it couldn’t drop table “Order” because Order is a keyword in SQL (yes we tried “[Order]” also without result :) ). Nevertheless Migrator.Net made an impression on me as nice and intuitive tool.

Subsonic has the same idea as Migrator.Net – it uses migrations written in c# code to change db schema. Subsonic has command line utility sonic.exe to perform migrations (for Tarantino.Net and Migrator.Net I used nant tasks). So it also has advantages of code-first approach. But for me its API became less intuitive comparing with Migrator (Table.AddColumn, DataService.GetSchema().AddColumn() or simple AddColumn() – what should I used when just want to add column? Only one of them worked in my sample migration – simple AddColumn(). And to investigate what went wrong I forced to use SQL profiler – because sonic.exe didn’t show necessary output diagnostics information). I saw the same confusing questions in forums also. Also it should be mentioned that current verion 3.0 doesn’t have sonic.exe utility in distributive package (the latest version which contains migration utility is 2.2 built on .Net 2.0). Of course mentioned disadvantages may be caused by my poor experience in Subsonic and there are other ways to do migrations in 3.0 version.

One important thing which was mentioned on meeting is that all mentioned tools are suitable for scenarios with “flat” database schema lifetime (i.e. when versions are increased in one direction). But in real life there are often scenarios when there are multiple branches of db schema (e.g. for different set of features or for different customers) which are developed in parallel. And these tools do not greatly help to address these situations – they help to migrate schema only within single branch. And additional efforts should be performed to migrate schemas from different branches.

As result of comparing mentioned tools I made a decision that advantages of Tarantino.Net is more valuable for me and I will use this tool for automation of db migrations in my work. Concluding I would also thanks all guys from SPb ALT.Net for organizing this meeting.

Wednesday, February 10, 2010

Authentication error when access AD of trusted domain in Sharepoint

Recently I faced with problem: we have 2 domains A and B with one way trust (domain A trusts domain B). Sharepoint installation belongs to domain A (Sharepoint system account is located in domain A). Users from domain B can login into Sharepoint site as domain A trusts domain B. Also we have code which makes some activities in AD:

  • creates new user
  • searches for users and groups
  • etc.

Code which creates and updates users and groups in AD successfully worked under account from trusted domain B (we use classes from System.DirectoryServices namespace). But search functionality failed with the following exception:

   1: The authentication mechanism is unknown.
   2: at System.DirectoryServices.DirectoryEntry.Bind(Boolean throwIfFail)
   3: at System.DirectoryServices.DirectoryEntry.Bind()

After investigation of problem I noticed that as DirectoryEntry class has possibility to specify credentials for accessing this entry (Username and Password properties) then there were no problems with creating and modifying as we explicitly specified credentials.

But for search we use DirectorySearcher class which has not such properties. Those entities which were returned by DirectorySearcher were binded using identity of current thread – and in the case when thread was impersonated by account of user from trusted domain B we had authentication error mentioned above.

Solution for this problem is to impersonate current thread with account which has access to AD of domain A on the moment when DirectoryEntry.Bind() is called. As I mentioned above in our environment Sharepoint system account belongs to domain A and it has access to its AD. So I used SPSecurity.RunWithElevatedPrivileges() for all these places where results of DirectorySearcher methods’ calls were used and after that error disappeared.

Notice that here SPSecurity.RunWithElevatedPrivileges() was used primarily for impersonation (i.e. I didn’t reopen SPSites and SPWebs under it). It simplified impersonation of current thread and allowed to avoid problems with re-impersonation on the initial account. If it is not suitable for you because of some reasons you can use WindowsIdentity.Impersonate() method for example.

Thursday, February 4, 2010

Fix “WPSC is undefined” javascript error in FBA web application in Sharepoint

Recently we encountered with strange error: after reconfiguration of Sharepoint application on the farm with 2 WFEs we got a javascript error “WPSC is undefined”. It was strange that this error appeared only for Internet zone with FBA. Although on Default zone with windows authentication there were no any errors. And it was not very clear what exactly configuration change caused this error.

After brief investigation of “WPSC is undefined” error I found that WPSC is defined in ie55up.js script. Several people wrote that they just added this script into theirs page and error disappeared. But it was not the way for us as this script existed in resulting html source (with several other core Sharepoint scripts):

   1: <script src="/_layouts/1035/init.js?rev=Fe8dn%2BZZbvb0W8rvYQjCLg%3D%3D"></script>
   2: <script type="text/javascript" language="javascript"
   3: src="/_layouts/1035/core.js?rev=e9C8lOHH8ryn9GXIlvHzqg%3D%3D" defer></script>
   4: <script type="text/javascript" language="javascript"
   5: src="/_layouts/1035/ie55up.js?rev=Ni7%2Fj2ZV%2FzCvd09XYSSWvA%3D%3D"></script>
   6: <script type="text/javascript" language="javascript"
   7: src="/_layouts/1035/search.js?rev=fo2j0xkK9jgYuNMH2DwaRg%3D%3D" defer></script>

I turned on script debugging in IE8 developer tools and it showed that there were errors during loading of several of these scripts (init.js and ie55up.js). It showed “Invalid character at position 1” error. I looked on the exact response of the server and result was surprising: instead of content of .js files server returned html page with the following error message:

   1: Value cannot be null. 
   2: Parameter name: value
   3:  at System.String.EndsWith(String value, StringComparison comparisonType) 
   4:  at Microsoft.SharePoint.ApplicationRuntime.
   5: SPRequestModule.PostAuthenticateRequestHandler(Object oSender, EventArgs ea) 
   6:  at System.Web.HttpApplication.
   7: SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() 
   8:  at System.Web.HttpApplication.
   9: ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) 

I.e. String.EndsWith() call inside SPRequestModule.PostAuthenticateRequestHandler() raised exception “Value cannot be null”. So initial error “WPSC is undefined” was just side effect of SPRequestModule’s failure during processing of request to ie55up.js script.

There were not much search results for this error. And no one of them contained neither the explanation of the reason nor the solution. As temporary fix people tried to change <compilation batch=”false”> on <compilation batch=”true”> in web.config located in 12/template/layouts folder – but it just expanded time between errors. I tried it also – and error really disappeared on short time but only on 1 of 2 WFEs in the farm. After several requests error occurred again (similar behavior is described for example here).

After that I decided to investigate error from other side – using Reflector. First of all I opened SPRequestModule.PostAuthenticateRequestHandler() and checked all occurrences of String.EndsWith()  calls. There are only 3 calls:

   1: private void PostAuthenticateRequestHandler(object oSender,
   2: EventArgs ea)
   3: {
   4: ...
   5: if (absolutePath.EndsWith(LoginUrl, StringComparison.OrdinalIgnoreCase) &&
   6:     (filePath.EndsWith(".css", StringComparison.OrdinalIgnoreCase) ||
   7:     filePath.EndsWith(".js", StringComparison.OrdinalIgnoreCase)))
   8:     {
   9:         context.SkipAuthorization = true;
  10:     }
  11: }
  12: ...
  13: }

As 2 calls contained constants as 1st parameter of String.EndsWith()  then only one of these 3 calls could cause “Value cannot be null” exception:

   1: absolutePath.EndsWith(LoginUrl, StringComparison.OrdinalIgnoreCase)

So LoginUrl was null because of some reasons. There is only 1 assignement of non null value to SPRequestModule.LoginUrl variable – in SPRequestModule.EnsureInitialize() method:

   1: private void EnsureInitialize(HttpRequest request)
   2: {
   3: ...
   4: application = SPWebApplication.Lookup(SPFarm.Local,
   5:     request.Url, true, out url, out info, out flag);
   6: if (null != application)
   7: {
   8:     LoginUrl = FormsAuthentication.LoginUrl;
   9:     ...
  10: }
  11: ...
  12: }

I checked that FormsAuthentication.LoginUrl contains valid non null value (just traced it in simple application layout page). So the only reason of having LoginUrl non initialized was returning of null in the following call:

   1: application = SPWebApplication.Lookup(SPFarm.Local,
   2:     request.Url, true, out url, out info, out flag);

After looking inside SPWebApplication.Lookup() method I’ve found that it uses host headers somehow:

   1: internal static SPWebApplication Lookup(...)
   2: {
   3: ...
   4: hostHeaderSiteInfo = SPSite.LookupHostHeaderSite(requestUri, farm);
   5: if (hostHeaderSiteInfo == null)
   6: {
   7:     ...
   8: }
   9: else
  10: {
  11:     contextWebApplication = (SPWebApplication)
  12:         farm.GetObject(hostHeaderSiteInfo.ApplicationId);
  13:     alternateUrl = null;
  14: }
  15: ...
  16: return contextWebApplication;
  17: }

And now I got it – one of the configuration change was removing host headers from IIS web sites on both WFEs on farm (there were some technical reasons for this). So I recover host headers of web sites in IIS settings, remove temporary files from Temporary ASP.Net files folder and restarted IIS. After that error disappeared.

PS. This is not 1st time when Reflector greatly helped me. In one of my previous posts I described how Sharepoint sets CurrentThread.CurrentUICulture depending on Language of SPWeb. And it was investigated also with help of Reflector.